src/gview/transfer_local.c

    Implementation of “pseudo-websocket” local transfers

    The server and client run on separate threads, but in the same process i.e. the communication is purely “local” to the process.

    #include <pthread.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdint.h>
    #include <sys/types.h>

    This implements both the client interface in transfer.h used by display.c and the server interface in ../wsServer/include/ws.h used by ../display.h.

    #include "transfer.h"
    #include "../wsServer/include/ws.h"
    
    #define LOCAL_FD 1
    
    struct local_message {
      struct local_message *next;
      char *data;
      size_t size;
      int type;
    };
    
    struct local_queue {
      struct local_message *first;
      struct local_message *last;
      pthread_mutex_t mutex;
    };
    
    struct local_transfer {
      bool closed;
    };
    
    static struct {
      bool server_open;
      bool closed;
    
      struct local_queue to_gview;
      struct local_queue to_server;

    Temporary buffer used by ws_sendframe_init()/ws_send().

      char *send_data;
      size_t send_size;
      size_t send_pos;
      int send_type;
      pthread_mutex_t send_mutex;
    } local = {
      .server_open = false,
      .closed = false,
    
      .to_gview = {
        .first = NULL,
        .last = NULL,
        .mutex = PTHREAD_MUTEX_INITIALIZER
      },
    
      .to_server = {
        .first = NULL,
        .last = NULL,
        .mutex = PTHREAD_MUTEX_INITIALIZER
      },
    
      .send_data = NULL,
      .send_size = 0,
      .send_pos = 0,
      .send_type = 0,
      .send_mutex = PTHREAD_MUTEX_INITIALIZER
    };

    Queue a copy of a message.

    Ownership of the queued copy belongs to the queue until it is returned to the receiving side.

    static int queue_put (struct local_queue *queue,
                          const void *data, size_t size, int type)
    {
      struct local_message *message;
    
      message = malloc (sizeof(*message));
      if (!message)
        return -1;
    
      message->data = NULL;
      if (size) {
        message->data = malloc (size);
        if (!message->data) {
          free (message);
          return -1;
        }
        memcpy (message->data, data, size);
      }
    
      message->size = size;
      message->type = type;
      message->next = NULL;
    
      pthread_mutex_lock (&queue->mutex);
    
      if (queue->last)
        queue->last->next = message;
      else
        queue->first = message;
    
      queue->last = message;
    
      pthread_mutex_unlock (&queue->mutex);
    
      return 0;
    }
    
    static struct local_message * queue_get (struct local_queue *queue)
    {
      struct local_message *message;
    
      pthread_mutex_lock (&queue->mutex);
    
      message = queue->first;
      if (message) {
        queue->first = message->next;
        if (!queue->first)
          queue->last = NULL;
      }
    
      pthread_mutex_unlock (&queue->mutex);
    
      return message;
    }
    
    static void queue_clear (struct local_queue *queue)
    {
      struct local_message *message;
    
      while ((message = queue_get (queue))) {
        free (message->data);
        free (message);
      }
    }

    wsServer interface

    int ws_socket_open (uint16_t port)
    {
      (void) port;
    
      if (local.server_open)
        return LOCAL_FD;
    
      local.server_open = true;
      local.closed = false;
    
      return LOCAL_FD;
    }
    
    int ws_sendframe_init (int fd, ssize_t size,
                           bool broadcast, int type)
    {
      (void) broadcast;
    
      if (fd != LOCAL_FD || size < 0 || local.closed)
        return -1;
    
      pthread_mutex_lock (&local.send_mutex);
    
      if (local.send_data) {
        pthread_mutex_unlock (&local.send_mutex);
        return -1;
      }
    
      local.send_data = NULL;
    
      if (size > 0) {
        local.send_data = malloc ((size_t) size);
        if (!local.send_data) {
          pthread_mutex_unlock (&local.send_mutex);
          return -1;
        }
      }
    
      local.send_size = (size_t) size;
      local.send_pos = 0;
      local.send_type = type;

    An empty frame is complete immediately.

      if (size == 0) {
        if (queue_put (&local.to_gview, NULL, 0, type) < 0) {
          free (local.send_data);
          local.send_data = NULL;
          local.send_size = 0;
          local.send_type = 0;
          pthread_mutex_unlock (&local.send_mutex);
          return -1;
        }
    
        local.send_data = NULL;
        local.send_size = 0;
        local.send_pos = 0;
        local.send_type = 0;
      }
    
      pthread_mutex_unlock (&local.send_mutex);
    
      return 0;
    }
    
    ssize_t ws_send (int fd, const void *buf, size_t len)
    {
      int status = 0;
    
      if (fd != LOCAL_FD || local.closed)
        return -1;
    
      pthread_mutex_lock (&local.send_mutex);
    
      if (!local.send_data && local.send_size != 0) {
        pthread_mutex_unlock (&local.send_mutex);
        return -1;
      }
    
      if (local.send_pos + len > local.send_size) {
        pthread_mutex_unlock (&local.send_mutex);
        return -1;
      }
    
      if (len)
        memcpy (local.send_data + local.send_pos, buf, len);
    
      local.send_pos += len;

    ws_sendframe_init() specifies the complete frame size. Publish the message once all of it has been supplied.

      if (local.send_pos == local.send_size) {
        if (queue_put (&local.to_gview,
                       local.send_data,
                       local.send_size,
                       local.send_type) < 0)
          status = -1;
    
        free (local.send_data);
        local.send_data = NULL;
        local.send_size = 0;
        local.send_pos = 0;
        local.send_type = 0;
      }
    
      pthread_mutex_unlock (&local.send_mutex);
    
      return status < 0 ? -1 : (ssize_t) len;
    }
    
    int ws_sendframe (int fd, const char *msg, ssize_t size,
                      bool broadcast, int type)
    {
      (void) broadcast;
    
      if (fd != LOCAL_FD || size < 0 || local.closed)
        return -1;
    
      return queue_put (&local.to_gview, msg, (size_t) size, type);
    }
    
    int ws_sendframe_txt (int fd, const char *msg, bool broadcast)
    {
      return ws_sendframe (fd, msg, strlen (msg),
                           broadcast, WS_FR_OP_TXT);
    }
    
    struct ws_message * ws_socket_poll (int fd, int timeout)
    {
      struct local_message *message;
      struct ws_message *messages;
    
      (void) timeout;
    
      if (fd != LOCAL_FD)
        return NULL;
    
      message = queue_get (&local.to_server);
      if (!message)
        return NULL;

    ws_socket_poll() returns a NULL-terminated array in the wsServer convention: the final entry has fd < 0.

      messages = malloc (2*sizeof(*messages));
      if (!messages) {
        free (message->data);
        free (message);
        return NULL;
      }
    
      messages[0].fd = LOCAL_FD;
      messages[0].msg = message->data;
      messages[0].size = message->size;
      messages[0].type = message->type;
    
      messages[1].fd = -1;
      messages[1].msg = NULL;
      messages[1].size = 0;
      messages[1].type = 0;
    
      free (message);
    
      return messages;
    }

    transfer interface

    struct transfer * transfer_open (const char *host, int port,
                                     unsigned int reconnect_ms)
    {
      struct local_transfer *transfer;
    
      (void) host;
      (void) port;
      (void) reconnect_ms;

    In the local implementation the server must already have been opened by ws_socket_open(). There is no asynchronous connection or reconnection.

      if (!local.server_open || local.closed)
        return NULL;
    
      transfer = calloc (1, sizeof(*transfer));
      if (!transfer)
        return NULL;
    
      if (queue_put (&local.to_server, NULL, 0, WS_FR_OP_OPEN) < 0) {
        free (transfer);
        return NULL;
      }
      
      return (struct transfer *) transfer;
    }
    
    int transfer_send (struct transfer *transfer,
                       const void *data, size_t size, bool binary)
    {
      struct local_transfer *t = (struct local_transfer *) transfer;
      int type;
    
      if (!t || t->closed || local.closed)
        return -1;
    
      type = binary ? WS_FR_OP_BIN : WS_FR_OP_TXT;
    
      return queue_put (&local.to_server, data, size, type);
    }
    
    struct transfer_message * transfer_receive (struct transfer *transfer)
    {
      struct local_transfer *t = (struct local_transfer *) transfer;
      struct local_message *message;
      struct transfer_message *result;
    
      if (!t || t->closed)
        return NULL;
    
      message = queue_get (&local.to_gview);
      if (!message)
        return NULL;
    
      result = malloc (sizeof(*result));
      if (!result) {
        free (message->data);
        free (message);
        return NULL;
      }
    
      if (message->type == WS_FR_OP_CLSE) {
        result->type = TRANSFER_MESSAGE_CLOSED;
        result->data = NULL;
        result->size = 0;
        result->binary = false;
    
        free (message->data);
      }
      else {
        result->type = TRANSFER_MESSAGE_DATA;
        result->data = message->data;
        result->size = message->size;
        result->binary = message->type == WS_FR_OP_BIN;
      }
    
      free (message);
    
      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)
    {
      (void) transfer;
      (void) high_bytes;
      (void) low_bytes;
      (void) high_messages;
      (void) low_messages;

    There is no network peer to throttle in the local implementation.

      return 0;
    }
    
    void transfer_disable_throttle (struct transfer *transfer)
    {
      (void) transfer;
    }
    
    void transfer_message_free (struct transfer_message *message)
    {
      if (!message)
        return;
    
      free (message->data);
      free (message);
    }
    
    int transfer_close (struct transfer *transfer)
    {
      struct local_transfer *t = (struct local_transfer *) transfer;
    
      if (!t)
        return -1;

    There is only one connection and its lifetime ends with the program, so no reconnection or intermediate connection state is required.

      t->closed = true;
      local.closed = true;
    
      pthread_mutex_lock (&local.send_mutex);
    
      free (local.send_data);
      local.send_data = NULL;
      local.send_size = 0;
      local.send_pos = 0;
      local.send_type = 0;
    
      pthread_mutex_unlock (&local.send_mutex);
    
      queue_clear (&local.to_gview);
      queue_clear (&local.to_server);
    
      free (t);
    
      return 0;
    }