src/gview/transfer_ws.c

    #define _POSIX_C_SOURCE 200809L
    
    #include "transfer.h"
    
    #include <arpa/inet.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <poll.h>
    #include <pthread.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <time.h>
    #include <unistd.h>
    #include <netdb.h>
    
    #define WS_RECV_BUFFER_SIZE 65536
    
    #define TRANSFER_CONTROL_READY      1
    #define TRANSFER_CONTROL_NOT_READY  0
    
    static const unsigned char throttle_magic[] = {
      'T', 'R', 'A', 'N', 'S', 'F', 'E', 'R',
      '_', 'C', 'T', 'R', 'L'
    };
    
    enum connection_state {
      CONNECTION_DISCONNECTED,
      CONNECTION_CONNECTING,
      CONNECTION_HANDSHAKING,
      CONNECTION_CONNECTED
    };
    
    struct message_node {
      struct transfer_message *message;
      struct message_node *next;
    };
    
    struct transfer {
      char *host;
      int port;
      unsigned int reconnect_ms;
    
      int fd;
      enum connection_state state;
    
      struct timespec next_retry;
    
      char handshake_request[1024];
      size_t handshake_length;
      size_t handshake_sent;
    
      char handshake_response[4096];
      size_t handshake_received;
    
      /*
      Data received together with the HTTP handshake but belonging
      to the WebSocket stream.
      */
      unsigned char recv_buffer[WS_RECV_BUFFER_SIZE];
      size_t recv_start;
      size_t recv_end;
    
      pthread_t thread;
      bool thread_running;
      bool running;
    
      /*
      Protects the receive queue and connection state.
      */
      pthread_mutex_t mutex;
    
      /*
      Serialises WebSocket frame transmission. In particular, this
      prevents a throttling control frame from being interleaved with
      a frame sent by the application thread.
      */
      pthread_mutex_t send_mutex;
    
      struct message_node *queue_first;
      struct message_node *queue_last;
    
      size_t queue_bytes;
      size_t queue_messages;
    
      bool throttle;
      bool ready;
    
      size_t throttle_high_bytes;
      size_t throttle_low_bytes;
    
      size_t throttle_high_messages;
      size_t throttle_low_messages;
    };
    
    
    /*
     * ----------------------------------------------------------------------
     * Time and connection helpers
     * ----------------------------------------------------------------------
     */
    
    static long long now_ms (void)
    {
      struct timespec ts;
    
      clock_gettime (CLOCK_MONOTONIC, &ts);
    
      return (long long) ts.tv_sec * 1000 +
             ts.tv_nsec / 1000000;
    }
    
    static void set_next_retry (struct transfer *transfer)
    {
      long long t;
    
      t = now_ms () + transfer->reconnect_ms;
    
      transfer->next_retry.tv_sec = t / 1000;
      transfer->next_retry.tv_nsec = (t % 1000) * 1000000;
    }
    
    static bool retry_due (struct transfer *transfer)
    {
      long long retry;
    
      retry = (long long) transfer->next_retry.tv_sec * 1000 +
              transfer->next_retry.tv_nsec / 1000000;
    
      return now_ms () >= retry;
    }
    
    static int set_nonblocking (int fd)
    {
      int flags;
    
      flags = fcntl (fd, F_GETFL, 0);
    
      if (flags < 0)
        return -1;
    
      return fcntl (fd, F_SETFL, flags | O_NONBLOCK);
    }
    
    static int set_blocking (int fd)
    {
      int flags;
    
      flags = fcntl (fd, F_GETFL, 0);
    
      if (flags < 0)
        return -1;
    
      return fcntl (fd, F_SETFL, flags & ~O_NONBLOCK);
    }
    
    static void connection_failed (struct transfer *transfer)
    {
      if (transfer->fd >= 0) {
        shutdown (transfer->fd, SHUT_RDWR);
        close (transfer->fd);
      }
    
      transfer->fd = -1;
      transfer->state = CONNECTION_DISCONNECTED;
    
      transfer->handshake_length = 0;
      transfer->handshake_sent = 0;
      transfer->handshake_received = 0;
    
      transfer->recv_start = 0;
      transfer->recv_end = 0;
    
      if (transfer->reconnect_ms > 0)
        set_next_retry (transfer);
    }
    
    static int start_connection (struct transfer *transfer)
    {
      struct addrinfo hints, *result, *rp;
      char port[16];
      int fd = -1;
      int error;
      int connect_result;
    
      memset (&hints, 0, sizeof(hints));
      hints.ai_family = AF_INET;
      hints.ai_socktype = SOCK_STREAM;
    
      snprintf (port, sizeof(port), "%d", transfer->port);
    
      error = getaddrinfo (transfer->host, port, &hints, &result);
      if (error != 0)
        return -1;
    
      for (rp = result; rp; rp = rp->ai_next) {
        fd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (fd < 0)
          continue;
    
        if (set_nonblocking (fd) < 0) {
          close (fd);
          fd = -1;
          continue;
        }
    
        connect_result = connect (fd, rp->ai_addr, rp->ai_addrlen);
    
        if (connect_result == 0) {
          transfer->fd = fd;
          transfer->state = CONNECTION_HANDSHAKING;
          freeaddrinfo (result);
          return 0;
        }
    
        if (errno == EINPROGRESS) {
          transfer->fd = fd;
          transfer->state = CONNECTION_CONNECTING;
          freeaddrinfo (result);
          return 0;
        }
    
        close (fd);
        fd = -1;
      }
    
      freeaddrinfo (result);
      return -1;
    }
    
    static int finish_connect (struct transfer *transfer, int timeout)
    {
      struct pollfd pfd;
      int error;
      socklen_t error_size;
      int result;
    
      pfd.fd = transfer->fd;
      pfd.events = POLLOUT;
      pfd.revents = 0;
    
      result = poll (&pfd, 1, timeout);
    
      if (result <= 0)
        return result;
    
      if (!(pfd.revents & (POLLOUT | POLLERR | POLLHUP)))
        return 0;
    
      error = 0;
      error_size = sizeof(error);
    
      if (getsockopt (transfer->fd, SOL_SOCKET, SO_ERROR,
                      &error, &error_size) < 0 || error != 0)
        return -1;
    
      transfer->state = CONNECTION_HANDSHAKING;
    
      return 1;
    }
    
    
    /*
     * ----------------------------------------------------------------------
     * WebSocket handshake
     * ----------------------------------------------------------------------
     */
    
    static int start_handshake (struct transfer *transfer)
    {
      int length;
    
      /*
       * The key is deliberately fixed. The client and server are under
       * our control and the server does not require validation of the
       * WebSocket key.
       */
      length = snprintf (transfer->handshake_request,
                         sizeof(transfer->handshake_request),
                         "GET / HTTP/1.1\r\n"
                         "Host: %s\r\n"
                         "Upgrade: websocket\r\n"
                         "Connection: Upgrade\r\n"
                         "Sec-WebSocket-Key: SGVsbG9XZWJTb2NrZXQ=\r\n"
                         "Sec-WebSocket-Version: 13\r\n"
                         "\r\n",
                         transfer->host);
    
      if (length < 0 ||
          (size_t) length >= sizeof(transfer->handshake_request))
        return -1;
    
      transfer->handshake_length = length;
      transfer->handshake_sent = 0;
      transfer->handshake_received = 0;
    
      transfer->recv_start = 0;
      transfer->recv_end = 0;
    
      return 0;
    }
    
    static int progress_handshake (struct transfer *transfer)
    {
      ssize_t n;
      char buffer[1024];
      char *end;
      size_t header_size;
      size_t extra;
    
      while (transfer->handshake_sent < transfer->handshake_length) {
        n = send (transfer->fd,
                  transfer->handshake_request +
                  transfer->handshake_sent,
                  transfer->handshake_length -
                  transfer->handshake_sent,
                  MSG_NOSIGNAL | MSG_DONTWAIT);
    
        if (n > 0) {
          transfer->handshake_sent += n;
          continue;
        }
    
        if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
          return 0;
    
        return -1;
      }
    
      for (;;) {
        if (transfer->handshake_received >=
            sizeof(transfer->handshake_response) - 1)
          return -1;
    
        n = recv (transfer->fd, buffer, sizeof(buffer), MSG_DONTWAIT);
    
        if (n > 0) {
          if (transfer->handshake_received + (size_t) n >=
              sizeof(transfer->handshake_response))
            return -1;
    
          memcpy (transfer->handshake_response +
                  transfer->handshake_received,
                  buffer, n);
    
          transfer->handshake_received += n;
    
          transfer->handshake_response[
            transfer->handshake_received] = '\0';
    
          end = strstr (transfer->handshake_response, "\r\n\r\n");
    
          if (!end)
            continue;
    
          header_size = (size_t) (end + 4 -
                                  transfer->handshake_response);
    
          if (strncmp (transfer->handshake_response,
                       "HTTP/1.1 101", 12) != 0)
            return -1;
    
          /*
           * Preserve any bytes following the HTTP headers. They already
           * belong to the WebSocket stream.
           */
          extra = transfer->handshake_received - header_size;
    
          if (extra > WS_RECV_BUFFER_SIZE)
            return -1;
    
          if (extra)
            memcpy (transfer->recv_buffer,
                    transfer->handshake_response + header_size,
                    extra);
    
          transfer->recv_start = 0;
          transfer->recv_end = extra;
    
          if (set_blocking (transfer->fd) < 0)
            return -1;
    
          transfer->state = CONNECTION_CONNECTED;
    
          return 1;
        }
    
        if (n == 0)
          return -1;
    
        if (errno == EAGAIN || errno == EWOULDBLOCK)
          return 0;
    
        return -1;
      }
    }
    
    
    /*
     * ----------------------------------------------------------------------
     * Receive queue
     * ----------------------------------------------------------------------
     */
    
    static bool queue_push (struct transfer *transfer,
                            struct transfer_message *message);
    
    static struct transfer_message * queue_pop (struct transfer *transfer);
    
    
    /*
     * ----------------------------------------------------------------------
     * WebSocket transmission
     * ----------------------------------------------------------------------
     */
    
    static int send_all (int fd, const void *buf, size_t n)
    {
      const char *p = buf;
    
      while (n) {
        ssize_t r;
    
        r = send (fd, p, n, MSG_NOSIGNAL);
    
        if (r <= 0)
          return -1;
    
        p += r;
        n -= r;
      }
    
      return 0;
    }
    
    static int ws_send (struct transfer *transfer,
                        const void *data, size_t size, int opcode)
    {
      uint8_t header[10];
      uint8_t mask[4];
      uint8_t buffer[4096];
      const uint8_t *src = data;
      size_t header_size;
      size_t n;
      size_t i;
    
      /*
       * A WebSocket client must mask frames sent to the server.
       *451/span>
       * This is deliberately not cryptographically random; the protocol
       * only requires a different masking key for each frame.
       */
      mask[0] = 0x12;
      mask[1] = 0x34;
      mask[2] = 0x56;
      mask[3] = 0x78;
    
      header[0] = 0x80 | opcode;
    
      if (size <= 125) {
        header[1] = 0x80 | size;
        header_size = 2;
      }
      else if (size <= 65535) {
        header[1] = 0x80 | 126;
        header[2] = size >> 8;
        header[3] = size;
        header_size = 4;
      }
      else {
        header[1] = 0x80 | 127;
    
        for (i = 0; i < 8; i++)
          header[2 + i] = size >> (56 - 8*i);
    
        header_size = 10;
      }
    
      if (send_all (transfer->fd, header, header_size) < 0 ||
          send_all (transfer->fd, mask, 4) < 0)
        return -1;
    
      while (size) {
        n = size < sizeof(buffer) ? size : sizeof(buffer);
    
        for (i = 0; i < n; i++)
          buffer[i] = src[i] ^ mask[i & 3];
    
        if (send_all (transfer->fd, buffer, n) < 0)
          return -1;
    
        src += n;
        size -= n;
      }
    
      return 0;
    }
    
    static int transfer_send_control (struct transfer *transfer,
                                      bool ready)
    {
      unsigned char message[sizeof(throttle_magic) + 1];
      int result;
    
      memcpy (message, throttle_magic, sizeof(throttle_magic));
    
      message[sizeof(throttle_magic)] =
        ready ? TRANSFER_CONTROL_READY :
                TRANSFER_CONTROL_NOT_READY;
    
      pthread_mutex_lock (&transfer->send_mutex);
    
      pthread_mutex_lock (&transfer->mutex);
    
      if (transfer->state != CONNECTION_CONNECTED) {
        pthread_mutex_unlock (&transfer->mutex);
        pthread_mutex_unlock (&transfer->send_mutex);
        return -1;
      }
    
      pthread_mutex_unlock (&transfer->mutex);
    
      result = ws_send (transfer, message, sizeof(message), 0x2);
    
      pthread_mutex_unlock (&transfer->send_mutex);
    
      return result;
    }
    
    
    /*
     * ----------------------------------------------------------------------
     * Queue implementation
     * ----------------------------------------------------------------------
     */
    
    static bool queue_push (struct transfer *transfer,
                            struct transfer_message *message)
    {
      struct message_node *node;
      bool send_not_ready = false;
    
      node = malloc (sizeof(*node));
    
      if (!node) {
        transfer_message_free (message);
        return false;
      }
    
      node->message = message;
      node->next = NULL;
    
      pthread_mutex_lock (&transfer->mutex);
    
      if (transfer->queue_last)
        transfer->queue_last->next = node;
      else
        transfer->queue_first = node;
    
      transfer->queue_last = node;
    
      transfer->queue_bytes += message->size;
      transfer->queue_messages++;
    
      if (transfer->throttle &&
          transfer->ready &&
          ((transfer->throttle_high_bytes &&
            transfer->queue_bytes >= transfer->throttle_high_bytes) ||
           (transfer->throttle_high_messages &&
            transfer->queue_messages >=
            transfer->throttle_high_messages))) {
        transfer->ready = false;
        send_not_ready = true;
      }
    
      pthread_mutex_unlock (&transfer->mutex);
    
      if (send_not_ready)
        transfer_send_control (transfer, false);
    
      return true;
    }
    
    static struct transfer_message * queue_pop (struct transfer *transfer)
    {
      struct message_node *node;
      struct transfer_message *message;
      bool send_ready = false;
    
      pthread_mutex_lock (&transfer->mutex);
    
      node = transfer->queue_first;
    
      if (node) {
        transfer->queue_first = node->next;
    
        if (!transfer->queue_first)
          transfer->queue_last = NULL;
    
        message = node->message;
    
        transfer->queue_bytes -= message->size;
        transfer->queue_messages--;
    
        if (transfer->throttle &&
            !transfer->ready &&
            (!transfer->throttle_low_bytes ||
             transfer->queue_bytes <= transfer->throttle_low_bytes) &&
            (!transfer->throttle_low_messages ||
             transfer->queue_messages <=
             transfer->throttle_low_messages)) {
          transfer->ready = true;
          send_ready = true;
        }
      }
    
      pthread_mutex_unlock (&transfer->mutex);
    
      if (!node)
        return NULL;
    
      free (node);
    
      if (send_ready)
        transfer_send_control (transfer, true);
    
      return message;
    }
    
    
    /*
     * ----------------------------------------------------------------------
     * WebSocket reception
     * ----------------------------------------------------------------------
     */
    
    static ssize_t ws_buffer_recv (struct transfer *transfer,
                                   void *buf, size_t size)
    {
      size_t available;
      size_t n;
    
      available = transfer->recv_end - transfer->recv_start;
    
      if (available) {
        n = available < size ? available : size;
    
        memcpy (buf,
                transfer->recv_buffer + transfer->recv_start,
                n);
    
        transfer->recv_start += n;
    
        if (transfer->recv_start == transfer->recv_end) {
          transfer->recv_start = 0;
          transfer->recv_end = 0;
        }
    
        return n;
      }
    
      return recv (transfer->fd, buf, size, 0);
    }
    
    static int recv_all (struct transfer *transfer,
                         void *buf, size_t n)
    {
      char *p = buf;
    
      while (n) {
        ssize_t r;
    
        r = ws_buffer_recv (transfer, p, n);
    
        if (r <= 0)
          return -1;
    
        p += r;
        n -= r;
      }
    
      return 0;
    }
    
    static int ws_receive (struct transfer *transfer)
    {
      uint8_t header[2];
      uint8_t *payload = NULL;
      uint8_t opcode;
      uint64_t length;
      size_t payload_size;
      struct transfer_message *message;
    
      if (recv_all (transfer, header, 2) < 0)
        return -1;
    
      opcode = header[0] & 0x0f;
      length = header[1] & 0x7f;
    
      /*
       * The server sends unmasked frames.
       */
      if (header[1] & 0x80)
        return -1;
    
      if (length == 126) {
        uint8_t extended[2];
    
        if (recv_all (transfer, extended, 2) < 0)
          return -1;
    
        length = ((uint64_t) extended[0] << 8) |
                 (uint64_t) extended[1];
      }
      else if (length == 127) {
        uint8_t extended[8];
    
        if (recv_all (transfer, extended, 8) < 0)
          return -1;
    
        length = ((uint64_t) extended[0] << 56) |
                 ((uint64_t) extended[1] << 48) |
                 ((uint64_t) extended[2] << 40) |
                 ((uint64_t) extended[3] << 32) |
                 ((uint64_t) extended[4] << 24) |
                 ((uint64_t) extended[5] << 16) |
                 ((uint64_t) extended[6] << 8) |
                 (uint64_t) extended[7];
      }
    
      if (length > SIZE_MAX)
        return -1;
    
      payload_size = (size_t) length;
    
      if (payload_size) {
        payload = malloc (payload_size);
    
        if (!payload)
          return -1;
    
        if (recv_all (transfer, payload, payload_size) < 0) {
          free (payload);
          return -1;
        }
      }
    
      switch (opcode) {
    
      case 0x1:
      case 0x2:
        message = malloc (sizeof(*message));
    
        if (!message) {
          free (payload);
          return -1;
        }
    
        message->type = TRANSFER_MESSAGE_DATA;
        message->data = payload;
        message->size = payload_size;
        message->binary = opcode == 0x2;
    
        if (!queue_push (transfer, message))
          return -1;
    
        return 1;
    
      case 0x8:
        /*
         * The remote side closed the connection.
         */
        free (payload);
        return 0;
    
      case 0x9:
        /*
         * Ping. We do not expose it to the application.
         *781/span>
         * Respond with pong.
         */
        pthread_mutex_lock (&transfer->send_mutex);
    
        if (transfer->state == CONNECTION_CONNECTED)
          ws_send (transfer, payload, payload_size, 0xa);
    
        pthread_mutex_unlock (&transfer->send_mutex);
    
        free (payload);
        return 1;
    
      case 0xa:
        /*
         * Pong.
         */
        free (payload);
        return 1;
    
      default:
        free (payload);
        return 1;
      }
    }
    
    
    /*
     * ----------------------------------------------------------------------
     * Receiver thread
     * ----------------------------------------------------------------------
     */
    
    static void queue_closed (struct transfer *transfer)
    {
      struct transfer_message *message;
    
      message = malloc (sizeof(*message));
    
      if (!message)
        return;
    
      message->type = TRANSFER_MESSAGE_CLOSED;
      message->data = NULL;
      message->size = 0;
      message->binary = false;
    
      queue_push (transfer, message);
    }
    
    static void *receive_thread (void *arg)
    {
      struct transfer *transfer = arg;
      int fd;
      int result;
      bool notify_closed = false;
    
      pthread_mutex_lock (&transfer->mutex);
    
      fd = transfer->fd;
    
      pthread_mutex_unlock (&transfer->mutex);
    
      for (;;) {
        result = ws_receive (transfer);
    
        if (result <= 0)
          break;
      }
    
      pthread_mutex_lock (&transfer->mutex);
    
      if (transfer->fd == fd) {
        close (transfer->fd);
        transfer->fd = -1;
      }
    
      transfer->state = CONNECTION_DISCONNECTED;
      transfer->thread_running = false;
    
      if (transfer->running) {
        notify_closed = true;
    
        if (transfer->reconnect_ms > 0)
          set_next_retry (transfer);
      }
    
      pthread_mutex_unlock (&transfer->mutex);
    
      if (notify_closed)
        queue_closed (transfer);
    
      return NULL;
    }
    
    static int start_receiver (struct transfer *transfer)
    {
      if (pthread_create (&transfer->thread, NULL,
                          receive_thread, transfer) != 0)
        return -1;
    
      transfer->thread_running = true;
    
      return 0;
    }
    
    
    /*
     * ----------------------------------------------------------------------
     * Public interface
     * ----------------------------------------------------------------------
     */
    
    struct transfer * transfer_open (const char *host, int port,
                                     unsigned int reconnect_ms)
    {
      struct transfer *transfer;
    
      transfer = calloc (1, sizeof(*transfer));
    
      if (!transfer)
        return NULL;
    
      transfer->host = strdup (host);
    
      if (!transfer->host) {
        free (transfer);
        return NULL;
      }
    
      transfer->port = port;
      transfer->reconnect_ms = reconnect_ms;
      transfer->fd = -1;
      transfer->state = CONNECTION_DISCONNECTED;
      transfer->running = true;
      transfer->ready = true;
    
      if (pthread_mutex_init (&transfer->mutex, NULL) != 0) {
        free (transfer->host);
        free (transfer);
        return NULL;
      }
    
      if (pthread_mutex_init (&transfer->send_mutex, NULL) != 0) {
        pthread_mutex_destroy (&transfer->mutex);
        free (transfer->host);
        free (transfer);
        return NULL;
      }
    
      /*
       * With reconnect_ms == 0, establish the connection synchronously.
       */
      if (reconnect_ms == 0) {
        struct pollfd pfd;
        int result;
    
        if (start_connection (transfer) < 0)
          goto fail;
    
        if (transfer->state == CONNECTION_CONNECTING) {
          result = finish_connect (transfer, -1);
    
          if (result <= 0)
            goto fail;
        }
    
        if (start_handshake (transfer) < 0)
          goto fail;
    
        /*
         * progress_handshake() is non-blocking, so wait for the socket
         * whenever the handshake needs more data.
         */
        while (transfer->state == CONNECTION_HANDSHAKING) {
          pfd.fd = transfer->fd;
          pfd.events = POLLIN | POLLOUT;
          pfd.revents = 0;
    
          if (poll (&pfd, 1, -1) <= 0)
            goto fail;
    
          result = progress_handshake (transfer);
    
          if (result < 0)
            goto fail;
        }
    
        if (transfer->state != CONNECTION_CONNECTED)
          goto fail;
    
        /*
         * The asynchronous path starts the receiver immediately after
         * the handshake. Do the same here.
         */
        if (start_receiver (transfer) < 0)
          goto fail;
      }
    
      return transfer;
    
    fail:
      if (transfer->fd >= 0) {
        shutdown (transfer->fd, SHUT_RDWR);
        close (transfer->fd);
      }
    
      pthread_mutex_destroy (&transfer->send_mutex);
      pthread_mutex_destroy (&transfer->mutex);
    
      free (transfer->host);
      free (transfer);
    
      return NULL;
    }
    
    struct transfer_message * transfer_receive (struct transfer *transfer)
    {
      struct transfer_message *message;
      int result;
    
      /*
       * Messages already received take priority.
       */
      message = queue_pop (transfer);
    
      if (message)
        return message;
    
      pthread_mutex_lock (&transfer->mutex);
    
      switch (transfer->state) {
    
      case CONNECTION_DISCONNECTED:
    
        /*
         * With reconnect_ms == 0, transfer_open() made the only
         * connection attempt. Never reconnect here.
         */
        if (transfer->reconnect_ms == 0) {
          pthread_mutex_unlock (&transfer->mutex);
          return NULL;
        }
    
        if (!retry_due (transfer)) {
          pthread_mutex_unlock (&transfer->mutex);
          return NULL;
        }
    
        set_next_retry (transfer);
    
        result = start_connection (transfer);
    
        if (result < 0) {
          pthread_mutex_unlock (&transfer->mutex);
          return NULL;
        }
    
        if (transfer->state == CONNECTION_HANDSHAKING) {
          if (start_handshake (transfer) < 0) {
            connection_failed (transfer);
            pthread_mutex_unlock (&transfer->mutex);
            return NULL;
          }
        }
    
        break;
    
      case CONNECTION_CONNECTING:
    
        result = finish_connect (transfer, 0);
    
        if (result < 0) {
          connection_failed (transfer);
          pthread_mutex_unlock (&transfer->mutex);
          return NULL;
        }
    
        if (transfer->state == CONNECTION_HANDSHAKING) {
          if (start_handshake (transfer) < 0) {
            connection_failed (transfer);
            pthread_mutex_unlock (&transfer->mutex);
            return NULL;
          }
        }
    
        break;
    
      case CONNECTION_HANDSHAKING:
    
        result = progress_handshake (transfer);
    
        if (result < 0) {
          connection_failed (transfer);
          pthread_mutex_unlock (&transfer->mutex);
          return NULL;
        }
    
        if (result > 0) {
          if (start_receiver (transfer) < 0) {
            connection_failed (transfer);
            pthread_mutex_unlock (&transfer->mutex);
            return NULL;
          }
        }
    
        break;
    
      case CONNECTION_CONNECTED:
        break;
      }
    
      pthread_mutex_unlock (&transfer->mutex);
    
      return queue_pop (transfer);
    }
    
    int transfer_send (struct transfer *transfer,
                       const void *data, size_t size, bool binary)
    {
      int result;
    
      pthread_mutex_lock (&transfer->send_mutex);
    
      pthread_mutex_lock (&transfer->mutex);
    
      if (transfer->state != CONNECTION_CONNECTED) {
        pthread_mutex_unlock (&transfer->mutex);
        pthread_mutex_unlock (&transfer->send_mutex);
        return -1;
      }
    
      pthread_mutex_unlock (&transfer->mutex);
    
      result = ws_send (transfer, data, size,
                        binary ? 0x2 : 0x1);
    
      pthread_mutex_unlock (&transfer->send_mutex);
    
      if (result < 0) {
        pthread_mutex_lock (&transfer->mutex);
        connection_failed (transfer);
        pthread_mutex_unlock (&transfer->mutex);
      }
    
      return result;
    }
    
    int transfer_set_throttle (struct transfer *transfer,
                               size_t high_bytes, size_t low_bytes,
                               size_t high_messages, size_t low_messages)
    {
      bool send_not_ready = false;
    
      if (!transfer)
        return -1;
    
      if (high_bytes && low_bytes >= high_bytes)
        return -1;
    
      if (high_messages && low_messages >= high_messages)
        return -1;
    
      pthread_mutex_lock (&transfer->mutex);
    
      transfer->throttle_high_bytes = high_bytes;
      transfer->throttle_low_bytes = low_bytes;
    
      transfer->throttle_high_messages = high_messages;
      transfer->throttle_low_messages = low_messages;
    
      transfer->throttle = true;
      transfer->ready = true;
    
      if ((high_bytes &&
           transfer->queue_bytes >= high_bytes) ||
          (high_messages &&
           transfer->queue_messages >= high_messages)) {
        transfer->ready = false;
        send_not_ready = true;
      }
    
      pthread_mutex_unlock (&transfer->mutex);
    
      if (send_not_ready)
        return transfer_send_control (transfer, false);
    
      return 0;
    }
    
    void transfer_disable_throttle (struct transfer *transfer)
    {
      if (!transfer)
        return;
    
      pthread_mutex_lock (&transfer->mutex);
    
      transfer->throttle = false;
      transfer->ready = true;
    
      pthread_mutex_unlock (&transfer->mutex);
    }
    
    void transfer_message_free (struct transfer_message *message)
    {
      if (!message)
        return;
    
      free (message->data);
      free (message);
    }
    
    int transfer_close (struct transfer *transfer)
    {
      struct transfer_message *message;
      pthread_t thread;
      bool thread_running;
    
      if (!transfer)
        return 0;
    
      pthread_mutex_lock (&transfer->mutex);
    
      transfer->running = false;
    
      thread_running = transfer->thread_running;
      thread = transfer->thread;
    
      if (transfer->fd >= 0)
        shutdown (transfer->fd, SHUT_RDWR);
    
      pthread_mutex_unlock (&transfer->mutex);
    
      if (thread_running)
        pthread_join (thread, NULL);
    
      pthread_mutex_lock (&transfer->mutex);
    
      if (transfer->fd >= 0) {
        close (transfer->fd);
        transfer->fd = -1;
      }
    
      pthread_mutex_unlock (&transfer->mutex);
    
      while ((message = queue_pop (transfer)))
        transfer_message_free (message);
    
      pthread_mutex_destroy (&transfer->send_mutex);
      pthread_mutex_destroy (&transfer->mutex);
    
      free (transfer->host);
      free (transfer);
    
      return 0;
    }