sock.h raw

   1  // Copyright (c) 2020-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #ifndef LIMENKA_UTIL_SOCK_H
   6  #define LIMENKA_UTIL_SOCK_H
   7  
   8  #include <compat/compat.h>
   9  #include <util/threadinterrupt.h>
  10  #include <util/time.h>
  11  
  12  #include <chrono>
  13  #include <memory>
  14  #include <string>
  15  #include <unordered_map>
  16  
  17  /**
  18   * Maximum time to wait for I/O readiness.
  19   * It will take up until this time to break off in case of an interruption.
  20   */
  21  static constexpr auto MAX_WAIT_FOR_IO = 1s;
  22  
  23  /**
  24   * RAII helper class that manages a socket and closes it automatically when it goes out of scope.
  25   */
  26  class Sock
  27  {
  28  public:
  29      Sock() = delete;
  30  
  31      /**
  32       * Take ownership of an existent socket.
  33       */
  34      explicit Sock(SOCKET s);
  35  
  36      /**
  37       * Copy constructor, disabled because closing the same socket twice is undesirable.
  38       */
  39      Sock(const Sock&) = delete;
  40  
  41      /**
  42       * Move constructor, grab the socket from another object and close ours (if set).
  43       */
  44      Sock(Sock&& other);
  45  
  46      /**
  47       * Destructor, close the socket or do nothing if empty.
  48       */
  49      virtual ~Sock();
  50  
  51      /**
  52       * Copy assignment operator, disabled because closing the same socket twice is undesirable.
  53       */
  54      Sock& operator=(const Sock&) = delete;
  55  
  56      /**
  57       * Move assignment operator, grab the socket from another object and close ours (if set).
  58       */
  59      virtual Sock& operator=(Sock&& other);
  60  
  61      /**
  62       * send(2) wrapper. Equivalent to `send(m_socket, data, len, flags);`. Code that uses this
  63       * wrapper can be unit tested if this method is overridden by a mock Sock implementation.
  64       */
  65      [[nodiscard]] virtual ssize_t Send(const void* data, size_t len, int flags) const;
  66  
  67      /**
  68       * recv(2) wrapper. Equivalent to `recv(m_socket, buf, len, flags);`. Code that uses this
  69       * wrapper can be unit tested if this method is overridden by a mock Sock implementation.
  70       */
  71      [[nodiscard]] virtual ssize_t Recv(void* buf, size_t len, int flags) const;
  72  
  73      /**
  74       * connect(2) wrapper. Equivalent to `connect(m_socket, addr, addrlen)`. Code that uses this
  75       * wrapper can be unit tested if this method is overridden by a mock Sock implementation.
  76       */
  77      [[nodiscard]] virtual int Connect(const sockaddr* addr, socklen_t addr_len) const;
  78  
  79      /**
  80       * bind(2) wrapper. Equivalent to `bind(m_socket, addr, addr_len)`. Code that uses this
  81       * wrapper can be unit tested if this method is overridden by a mock Sock implementation.
  82       */
  83      [[nodiscard]] virtual int Bind(const sockaddr* addr, socklen_t addr_len) const;
  84  
  85      /**
  86       * listen(2) wrapper. Equivalent to `listen(m_socket, backlog)`. Code that uses this
  87       * wrapper can be unit tested if this method is overridden by a mock Sock implementation.
  88       */
  89      [[nodiscard]] virtual int Listen(int backlog) const;
  90  
  91      /**
  92       * accept(2) wrapper. Equivalent to `std::make_unique<Sock>(accept(m_socket, addr, addr_len))`.
  93       * Code that uses this wrapper can be unit tested if this method is overridden by a mock Sock
  94       * implementation.
  95       * The returned unique_ptr is empty if `accept()` failed in which case errno will be set.
  96       */
  97      [[nodiscard]] virtual std::unique_ptr<Sock> Accept(sockaddr* addr, socklen_t* addr_len) const;
  98  
  99      /**
 100       * getsockopt(2) wrapper. Equivalent to
 101       * `getsockopt(m_socket, level, opt_name, opt_val, opt_len)`. Code that uses this
 102       * wrapper can be unit tested if this method is overridden by a mock Sock implementation.
 103       */
 104      [[nodiscard]] virtual int GetSockOpt(int level,
 105                                           int opt_name,
 106                                           void* opt_val,
 107                                           socklen_t* opt_len) const;
 108  
 109      /**
 110       * setsockopt(2) wrapper. Equivalent to
 111       * `setsockopt(m_socket, level, opt_name, opt_val, opt_len)`. Code that uses this
 112       * wrapper can be unit tested if this method is overridden by a mock Sock implementation.
 113       */
 114      [[nodiscard]] virtual int SetSockOpt(int level,
 115                                           int opt_name,
 116                                           const void* opt_val,
 117                                           socklen_t opt_len) const;
 118  
 119      /**
 120       * getsockname(2) wrapper. Equivalent to
 121       * `getsockname(m_socket, name, name_len)`. Code that uses this
 122       * wrapper can be unit tested if this method is overridden by a mock Sock implementation.
 123       */
 124      [[nodiscard]] virtual int GetSockName(sockaddr* name, socklen_t* name_len) const;
 125  
 126      /**
 127       * Set the non-blocking option on the socket.
 128       * @return true if set successfully
 129       */
 130      [[nodiscard]] virtual bool SetNonBlocking() const;
 131  
 132      /**
 133       * Check if the underlying socket can be used for `select(2)` (or the `Wait()` method).
 134       * @return true if selectable
 135       */
 136      [[nodiscard]] virtual bool IsSelectable() const;
 137  
 138      using Event = uint8_t;
 139  
 140      /**
 141       * If passed to `Wait()`, then it will wait for readiness to read from the socket.
 142       */
 143      static constexpr Event RECV = 0b001;
 144  
 145      /**
 146       * If passed to `Wait()`, then it will wait for readiness to send to the socket.
 147       */
 148      static constexpr Event SEND = 0b010;
 149  
 150      /**
 151       * Ignored if passed to `Wait()`, but could be set in the occurred events if an
 152       * exceptional condition has occurred on the socket or if it has been disconnected.
 153       */
 154      static constexpr Event ERR = 0b100;
 155  
 156      /**
 157       * Wait for readiness for input (recv) or output (send).
 158       * @param[in] timeout Wait this much for at least one of the requested events to occur.
 159       * @param[in] requested Wait for those events, bitwise-or of `RECV` and `SEND`.
 160       * @param[out] occurred If not nullptr and the function returns `true`, then this
 161       * indicates which of the requested events occurred (`ERR` will be added, even if
 162       * not requested, if an exceptional event occurs on the socket).
 163       * A timeout is indicated by return value of `true` and `occurred` being set to 0.
 164       * @return true on success (or timeout, if `occurred` of 0 is returned), false otherwise
 165       */
 166      [[nodiscard]] virtual bool Wait(std::chrono::milliseconds timeout,
 167                                      Event requested,
 168                                      Event* occurred = nullptr) const;
 169  
 170      /**
 171       * Auxiliary requested/occurred events to wait for in `WaitMany()`.
 172       */
 173      struct Events {
 174          explicit Events(Event req) : requested{req} {}
 175          Event requested;
 176          Event occurred{0};
 177      };
 178  
 179      struct HashSharedPtrSock {
 180          size_t operator()(const std::shared_ptr<const Sock>& s) const
 181          {
 182              return s ? s->m_socket : std::numeric_limits<SOCKET>::max();
 183          }
 184      };
 185  
 186      struct EqualSharedPtrSock {
 187          bool operator()(const std::shared_ptr<const Sock>& lhs,
 188                          const std::shared_ptr<const Sock>& rhs) const
 189          {
 190              if (lhs && rhs) {
 191                  return lhs->m_socket == rhs->m_socket;
 192              }
 193              if (!lhs && !rhs) {
 194                  return true;
 195              }
 196              return false;
 197          }
 198      };
 199  
 200      /**
 201       * On which socket to wait for what events in `WaitMany()`.
 202       * The `shared_ptr` is copied into the map to ensure that the `Sock` object
 203       * is not destroyed (its destructor would close the underlying socket).
 204       * If this happens shortly before or after we call `poll(2)` and a new
 205       * socket gets created under the same file descriptor number then the report
 206       * from `WaitMany()` will be bogus.
 207       */
 208      using EventsPerSock = std::unordered_map<std::shared_ptr<const Sock>, Events, HashSharedPtrSock, EqualSharedPtrSock>;
 209  
 210      /**
 211       * Same as `Wait()`, but wait on many sockets within the same timeout.
 212       * @param[in] timeout Wait this long for at least one of the requested events to occur.
 213       * @param[in,out] events_per_sock Wait for the requested events on these sockets and set
 214       * `occurred` for the events that actually occurred.
 215       * @return true on success (or timeout, if all `what[].occurred` are returned as 0),
 216       * false otherwise
 217       */
 218      [[nodiscard]] virtual bool WaitMany(std::chrono::milliseconds timeout,
 219                                          EventsPerSock& events_per_sock) const;
 220  
 221      /* Higher level, convenience, methods. These may throw. */
 222  
 223      /**
 224       * Send the given data, retrying on transient errors.
 225       * @param[in] data Data to send.
 226       * @param[in] timeout Timeout for the entire operation.
 227       * @param[in] interrupt If this is signaled then the operation is canceled.
 228       * @throws std::runtime_error if the operation cannot be completed. In this case only some of
 229       * the data will be written to the socket.
 230       */
 231      virtual void SendComplete(Span<const unsigned char> data,
 232                                std::chrono::milliseconds timeout,
 233                                CThreadInterrupt& interrupt) const;
 234  
 235      /**
 236       * Convenience method, equivalent to `SendComplete(MakeUCharSpan(data), timeout, interrupt)`.
 237       */
 238      virtual void SendComplete(Span<const char> data,
 239                                std::chrono::milliseconds timeout,
 240                                CThreadInterrupt& interrupt) const;
 241  
 242      /**
 243       * Read from socket until a terminator character is encountered. Will never consume bytes past
 244       * the terminator from the socket.
 245       * @param[in] terminator Character up to which to read from the socket.
 246       * @param[in] timeout Timeout for the entire operation.
 247       * @param[in] interrupt If this is signaled then the operation is canceled.
 248       * @param[in] max_data The maximum amount of data (in bytes) to receive. If this many bytes
 249       * are received and there is still no terminator, then this method will throw an exception.
 250       * @return The data that has been read, without the terminating character.
 251       * @throws std::runtime_error if the operation cannot be completed. In this case some bytes may
 252       * have been consumed from the socket.
 253       */
 254      [[nodiscard]] virtual std::string RecvUntilTerminator(uint8_t terminator,
 255                                                            std::chrono::milliseconds timeout,
 256                                                            CThreadInterrupt& interrupt,
 257                                                            size_t max_data) const;
 258  
 259      /**
 260       * Check if still connected.
 261       * @param[out] errmsg The error string, if the socket has been disconnected.
 262       * @return true if connected
 263       */
 264      [[nodiscard]] virtual bool IsConnected(std::string& errmsg) const;
 265  
 266      /**
 267       * Check if the internal socket is equal to `s`. Use only in tests.
 268       */
 269      bool operator==(SOCKET s) const;
 270  
 271  protected:
 272      /**
 273       * Contained socket. `INVALID_SOCKET` designates the object is empty.
 274       */
 275      SOCKET m_socket;
 276  
 277  private:
 278      /**
 279       * Close `m_socket` if it is not `INVALID_SOCKET`.
 280       */
 281      void Close();
 282  };
 283  
 284  /** Return readable error string for a network error code */
 285  std::string NetworkErrorString(int err);
 286  
 287  #endif // LIMENKA_UTIL_SOCK_H
 288