A Place Where No Dreams Come True...

Current Project: After many years of serving a webcam it appears that the latest, greatest, browser sanity check mechanisms reject any services that do not use 'FLASH'... The last supported plugin... Until HTML5.

What Does This Code Do.

HTML5 now supports a new interface. WebSockets. Allowing the Client browser to form, and maintain, a persistant connection, to a server for the transfer of spontaneous information. Usually through JavaScript.

This has huge benefits for real-time exchange of text and graphic information. A.K.A. Chat... To the next level.

What Doesn't This Code Do...

This is not the planned complete interface. Currently it has no outside methods of setting interface parameters. Additionally there are some hard-coded values used for the purpose of ongoing debugging.

//-----------------------------------------------------------------------------
// server.c
//
//  WebSocket server implementation.
//
// Copyright (c) 2015 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <openssl/sha.h>

#include "wsutils.h"
#include "report.h"

//-----------------------------------------------------------------------------

#define MAXCONNECTIONS 16
#define SOCKETBUFFERSIZE 2048

#define ECHOSERVER 1

//-----------------------------------------------------------------------------

struct _client {
  int sock;
  int connected;
  int type;
  int octin;
  int octout;
  int pntin;
  int pntout;
  int paylen;
  struct sockaddr_in cliaddr;
  unsigned char in[SOCKETBUFFERSIZE];
  unsigned char out[SOCKETBUFFERSIZE];
};

struct _server {
  int sock;
  int port;
  unsigned int connections;
  socklen_t socklen;
  struct sockaddr_in srvaddr;
  fd_set active_fd_set;
  fd_set read_fd_set;
  struct _client client[MAXCONNECTIONS];
  int octets;
  unsigned char message[48192];
};

//-----------------------------------------------------------------------------

static int port;
static char dev[128];

//-----------------------------------------------------------------------------

static int wsclient_connect(struct _client *client)
{
  if(!client->connected)
  {
    report(dbglevl_msg, "websocket_client(%i) connecting...\n", client->sock);
    pWsHeader hdr = ws_parse_handshake((char*)client->in, client->octin);
    ws_reply_handshake(hdr, (char*)client->out);
    report(dbglevl_debug, (char*)client->out);
    send(client->sock, client->out, strlen((char*)client->out), 0);
    client->connected = 1;
  }
  return client->connected;
}

//-----------------------------------------------------------------------------

static int wsclient_parse(struct _client *client)
{
  client->pntin = 0;
  client->octin = recv(client->sock, client->in, SOCKETBUFFERSIZE - 2, 0);
  return client->octin;
}

//-----------------------------------------------------------------------------

static int interpret_client(struct _client *client)
{
  int rtn = 0;
  WsHead head;

  if(client->pntin < client->octin)
  {
    ws_get_frame_header(&client->in[client->pntin], &head);
    client->type = head.opcode;
    switch(client->type)
    {
      case WS_FRAME_CLOSE:
        report(dbglevl_msg, "websocket_client(%i) close requested.\n", client->sock);
        client->connected = 0;
        break;

      case WS_FRAME_TEXT:
        memmove(client->out, head.data, head.paylen);
        client->paylen = head.paylen;
        ws_mask_unmask(client->out, client->out, head.mask, head.paylen);
        client->out[head.paylen] = '\0';
        client->octout = head.paylen;
        report(dbglevl_debug, "websocket_client(%i) Text Frame - %s.\n",
               client->sock, client->out);
        break;

      case WS_FRAME_BINARY:
        memmove(client->out, head.data, head.paylen);
        client->paylen = head.paylen;
        ws_mask_unmask(client->out, client->out, head.mask, head.paylen);
        client->out[head.paylen] = '\0';
        client->octout = head.paylen;
        /*
          client->octout = head.size;
          memmove(client->out, &client->in[client->pntin], client->octout);
        */
        break;

      default:
        report(dbglevl_msg, "Ignored message opcode - %X.\n",
               ws_get_frame_opcode(client->in));
        break;
    }
    client->pntin += head.size;
    rtn = client->octin - client->pntin;
  }
  else
  {
    report_error("error : websocket_client():recv(%i) failed ", client->sock);
    rtn = -1;
  }
  return rtn;
}

//-----------------------------------------------------------------------------

static int server_find_available(struct _server *server)
{
  int i;

  for(i = 0; i < MAXCONNECTIONS; i++)
  {
    if(server->client[i].sock == 0)
    {
      break;
    }
  }
  if(i >= MAXCONNECTIONS)
  {
    i = -1;
  }
  return i;
}

//-----------------------------------------------------------------------------

static int server_find_client(struct _server *server, int sock)
{
  int i;

  for(i = 0; i < MAXCONNECTIONS; i++)
  {
    if(server->client[i].sock == sock)
    {
      break;
    }
  }
  if(i >= MAXCONNECTIONS)
  {
    i = -1;
  }
  return i;
}

//-----------------------------------------------------------------------------

static int connect_wsclient(struct _server *server)
{
  int rtn = 0;
  int cli = server_find_available(server);
  if(cli > -1)
  {
    // New connection.
    server->socklen = sizeof(server->client[cli].cliaddr);
    server->client[cli].sock = accept(server->sock,
                                      (struct sockaddr*)&server->client[cli].cliaddr,
                                      &server->socklen);
    if(server->client[cli].sock > 0)
    {
      report(dbglevl_msg, "websocket_server(%i):accept(%i)=%i : address=%s port=%i.\n",
             server->port,
             server->sock,
             server->client[cli].sock,
             inet_ntoa(server->client[cli].cliaddr.sin_addr),
             ntohs(server->client[cli].cliaddr.sin_port));
             FD_SET(server->client[cli].sock, &server->active_fd_set);
             rtn = 1;
    }
    else
    {
      report_error("error : websocket_server(%i):accept(%i) failed ",
                   server->port, server->sock);
    }
  }
  else
  {
    report(dbglevl_msg, "websocket_server() - Max connections reached Unable to add new client.\n");
  }
  return rtn;
}

//-----------------------------------------------------------------------------

static int push_wstext(struct _server *server)
{
  int i;
  int rtn = 0;

  for(i = 0; i < MAXCONNECTIONS; i++)
  {
    if(server->client[i].sock != 0)
    {
      rtn = send(server->client[i].sock, server->message, server->octets, 0);
      report(dbglevl_debug, "Echo to %i %i bytes.\n", server->client[i].sock, rtn);
    }
  }
  return rtn;
}

//-----------------------------------------------------------------------------

static int push_wsbinary(struct _server *server, int cli)
{
  int i;
  int rtn = 0;

  for(i = 0; i < MAXCONNECTIONS; i++)
  {
    if((server->client[i].sock != 0) && (i != cli))
    {
      rtn = send(server->client[i].sock, server->message, server->octets, 0);
      report(dbglevl_debug, "Echo to %i %i bytes.\n", server->client[i].sock, rtn);
    }
  }
  return rtn;
}

//-----------------------------------------------------------------------------

int websocket_server(int port)
{
  int i;
  int cli;
  int rtn = 0;

  struct _server *server = malloc(sizeof(struct _server));
  if(server)
  {
    memset(server, 0, sizeof(struct _server));
    server->port = port;
    server->sock = socket(AF_INET, SOCK_STREAM, 0);
    if(server->sock > 0)
    {
      report(dbglevl_none, "WebSocket server started : port=%i : socket:%i\n",
             server->port, server->sock);
      server->srvaddr.sin_family = AF_INET;
      server->srvaddr.sin_port = htons(server->port);
      server->srvaddr.sin_addr.s_addr = INADDR_ANY;
      if(bind(server->sock, (struct sockaddr*)&server->srvaddr, sizeof(struct sockaddr_in)) != -1) 
      {
	if(listen(server->sock, 1) != -1)
	{
	  // Initialize the set of active sockets.
	  FD_ZERO(&server->active_fd_set);
	  FD_SET(server->sock, &server->active_fd_set);
	  while(1)
	  {
	    // Block until input arrives on one or more active sockets.
	    server->read_fd_set = server->active_fd_set;
	    if(select(FD_SETSIZE, &server->read_fd_set, NULL, NULL, NULL) < 0)
	    {
	      report_error("error : websocket_server(%i):select() failed ", server->port);
	      break;
	    }
	    for(i = 0; i < FD_SETSIZE; i++)
	    {
	      if(FD_ISSET(i, &server->read_fd_set))
	      {
		if(i == server->sock)
		{
                  connect_wsclient(server);
		}
		else
		{
                  cli = server_find_client(server, i);
                  if(cli > -1)
                  {
                    wsclient_parse(&server->client[cli]);
                    if(!server->client[cli].connected)
                    {
                      wsclient_connect(&server->client[cli]);
                      if(server->client[cli].connected)
                      {
                        sprintf((char*)server->message, "Connection from IP:%s on Port:%i\n",
                                inet_ntoa(server->client[cli].cliaddr.sin_addr),
                                ntohs(server->client[cli].cliaddr.sin_port));
                        ws_set_frame(server->message, server->message,
                                     WS_FRAME_TEXT, 0, strlen((char*)server->message));
                        server->octets = ws_get_frame_size(server->message);
                        push_wstext(server);
                      }
                    }
                    else
                    {
                      do
                      {
                        rtn = interpret_client(&server->client[cli]);
                        switch(server->client[cli].type)
                        {
                          case WS_FRAME_TEXT:
                            ws_set_frame(server->message, server->client[cli].out,
                                         WS_FRAME_TEXT, 0, server->client[cli].octout);
                            server->octets = ws_get_frame_size(server->message);
                            push_wstext(server);
                            break;

                          case WS_FRAME_BINARY:
                            ws_set_frame(server->message, server->client[cli].out,
                                         WS_FRAME_BINARY, 0, server->client[cli].octout);
                            server->octets = ws_get_frame_size(server->message);
                            push_wsbinary(server, cli);
                            break;

                          default:
                            // FIXME: More options... Close connection for now.
		            FD_CLR(server->client[cli].sock, &server->active_fd_set);
		            report(dbglevl_msg, "websocket_server(%i):close(%i).\n",
                                   server->port, server->client[cli].sock);
		            close(server->client[cli].sock);
                            server->client[cli].sock = 0;
                            server->client[cli].connected = 0;
                            break;
                        }
                      }
                      while(rtn > 0);
                    }
                  }
		}
	      }
            }
          }
        }
        else
        {
          report_error("error : websocket_server(%i):listen(%i) failed ",
                       server->port, server->sock);
        }
      }
      else
      {
        report_error("error : websocket_server(%i):bind(%i) failed ",
                     server->port, server->sock);
      }
    }
    else
    {
      report_error("error : websocket_server(%i):socket() failed ", server->port);
    }
    free(server);
  }
  return rtn;
}

//-----------------------------------------------------------------------------

static void usage(void)
{
  report(dbglevl_none, "Usage : wsserver -[dmhvw?] -p port# -D device-path.\n");
  report(dbglevl_none, "   -D /dev/video-device : Request video device.\n");
  report(dbglevl_none, "-p -P port# : Set the port the server listens on.\n");
  report(dbglevl_none, "-d    : Show debug messages.\n");
  report(dbglevl_none, "-m -M : Show general messages.\n");
  report(dbglevl_none, "-h -? : Show this message.\n");
  report(dbglevl_none, "-v -V : Verbose message reporting.\n");
  report(dbglevl_none, "-w -W : Show warning messages.\n");
}

//-----------------------------------------------------------------------------

static void show_options(void)
{
  report(dbglevl_msg, "Options :\n");
  report(dbglevl_msg, " Video device : %s.\n", dev);
  report(dbglevl_msg, " Server port : %i.\n", port);
}

//-----------------------------------------------------------------------------

int main(int argc, char **argv)
{
  int  arg;
  int rtn = 0;

  report_init(stdout);
  strcpy(dev, "/dev/video0");
  port = 8889;

  for(arg = 1; arg < argc; arg++)
  {
    if(argv[arg][0] == '-')
    {
      switch(argv[arg][1])
      {
        case 'D':
	  // Set Server Port.
          if(arg < (argc - 1))
          {
	    if(!sscanf(argv[arg + 1], "%s", dev))
            {
              report(dbglevl_none, "-D \"%s\" didnt translate device path.\n", argv[arg + 1]);
              rtn = 1;
            }
            else
            {
              //Skip over next operand.
              arg++;
            }
          }
          else
          {
            rtn = 1;
          }
	  break;

        case 'd':
	  // Debug messages.
	  report_set_level(dbglevl_debug);
	  break;

        case 'm':
        case 'M':
	  // Messages.
	  report_set_level(dbglevl_msg);
	  break;

        case 'p':
        case 'P':
	  // Set Server Port.
          if(arg < (argc - 1))
          {
	    if(!sscanf(argv[arg + 1], "%i", &port))
            {
              report(dbglevl_none, "-p(P) \"%s\" didn't translate to number.\n", argv[arg + 1]);
              rtn = 1;
            }
            else
            {
              //Skip over next operand.
              arg++;
            }
          }
          else
          {
            rtn = 1;
          }
	  break;

        case 'v':
        case 'V':
	  // Verbose debug reporting.
	  report_set_level(dbglevl_verbose);
	  break;

        case 'w':
        case 'W':
	  // Warning messages.
	  report_set_level(dbglevl_warn);
	  break;

        case 'h':
        case '?':
        default:
	  rtn = 1;
	  break;
      }
    }
    if(rtn)
    {
      // Error somewhere.
      usage();
      break;
    }
  }
  if(!rtn)
  {
    show_options();
    websocket_server(port);
  }
  return rtn;
}

//-----------------------------------------------------------------------------
// end: server.c

  

An Additional WebSocket Utility Helper Library...

//-----------------------------------------------------------------------------
// wsutils.c
//
//  WebSocket interface utilities implementation.
//
// Copyright (c) 2015 - No Fun Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "report.h"
#include "wsutils.h"

//-----------------------------------------------------------------------------

#define HANDSHAKEFRAG "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

#define WSFLAGFINMASK 0x80
#define WSFLAGRSVMASK 0x70
#define WSOPCODEMASK  0x0F
#define WSFRAMEMASK   0x80
#define WSPAYLOADMASK 0x7F

#define WSOPCODECONTINUE  0x00
#define WSOPCODETEXT      0x01
#define WSOPCODEBINARY    0x02
#define WSOPCODECLOSE     0x08
#define WSOPCODEPING      0x09
#define WSOPCODEPONG      0x0A

struct _wshash {
  unsigned long length;
  unsigned char data[(HASHMAX * 2) + 40];
  unsigned char hash[HASHMAX];
  char b64[HASHMAX * 2];
};

#define HDRREQUEST     "GET /"
#define HDRHOST        "Host"
#define HDRCONNECTION  "Connection"
#define HDRUPGRADE     "Upgrade"
#define HDRPROTOCOL    "Sec-WebSocket-Protocol"
#define HDRVERSION     "Sec-Websocket-Version"
#define HDRSECKEY      "Sec-Websocket-Key"

struct _wsheader {
  char request[HDRWSHKEY];
  char host[HDRWSHKEY];
  char connection[HDRWSHKEY];
  char upgrade[HDRWSHKEY];
  char protocol[HDRWSHKEY];
  char version[HDRWSHKEY];
  char seckey[HDRWSHKEY];
};

//-----------------------------------------------------------------------------

static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
                                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                                'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
                                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                                'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                                'w', 'x', 'y', 'z', '0', '1', '2', '3',
                                '4', '5', '6', '7', '8', '9', '+', '/'};

static int mod_table[] = {0, 2, 1};

//-----------------------------------------------------------------------------

int ws_handshake_hash(unsigned char *msg)
{
  int i;
  int j;
  uint32_t octet_a;
  uint32_t octet_b;
  uint32_t octet_c;
  uint32_t triple;
  struct _wshash wshash;
  
  memset(&wshash, 0, sizeof(struct _wshash));
  sprintf((char*)wshash.data,"%s%s", msg, HANDSHAKEFRAG);
  wshash.length = strlen((char*)wshash.data);
  // Compute SHA HASH.
  SHA1(wshash.data, wshash.length, wshash.hash);
  // Convert to BASE64.
  for(i = 0, j = 0; i < HASHMAX;)
  {
     octet_a = i < HASHMAX ? (unsigned char)wshash.hash[i++] : 0;
     octet_b = i < HASHMAX ? (unsigned char)wshash.hash[i++] : 0;
     octet_c = i < HASHMAX ? (unsigned char)wshash.hash[i++] : 0;
     triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;

     wshash.b64[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
     wshash.b64[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
     wshash.b64[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
     wshash.b64[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
   }
   j = strlen(wshash.b64);
   for(i = 0; i < mod_table[HASHMAX % 3]; i++)
   {
      wshash.b64[j - 1 - i] = '=';
   }
   memcpy(msg, wshash.b64, HASHMAX * 2);
   return 0;
}

//-----------------------------------------------------------------------------

static void parse_key(struct _wsheader *phdr, char *value, char *key)
{
   if(!strncmp(value, HDRREQUEST, 5))
   {
      strncpy(phdr->request, value, HDRWSHKEY - 1);
   }
   if(!strcasecmp(value, HDRHOST))
   {
      strncpy(phdr->host, key, HDRWSHKEY - 1);
   }
   if(!strcasecmp(value, HDRCONNECTION))
   {
      strncpy(phdr->connection, key, HDRWSHKEY - 1);
   }
   if(!strcasecmp(value, HDRUPGRADE))
   {
      strncpy(phdr->upgrade, key, HDRWSHKEY - 1);
   }
   if(!strcasecmp(value, HDRPROTOCOL))
   {
      strncpy(phdr->protocol, key, HDRWSHKEY - 1);
   }
   if(!strcasecmp(value, HDRVERSION))
   {
      strncpy(phdr->version, key, HDRWSHKEY - 1);
   }
   if(!strcasecmp(value, HDRSECKEY))
   {
      strncpy(phdr->seckey, key, HDRWSHKEY - 1);
   }
}

//-----------------------------------------------------------------------------

void print_headers(struct _wsheader *phdr)
{
  if(*phdr->request)
  {
     report(dbglevl_none, "Found Request:%s\n", phdr->request);
  }
  if(*phdr->host)
  {
     report(dbglevl_none, "Found Host:%s\n", phdr->host);
  }
  if(*phdr->connection)
  {
     report(dbglevl_none, "Found Connection:%s\n", phdr->connection);
  }
  if(*phdr->upgrade)
  {
     report(dbglevl_none, "Found Upgrade:%s\n", phdr->upgrade);
  }
  if(*phdr->protocol)
  {
     report(dbglevl_none, "Found Protocol:%s\n", phdr->protocol);
  }
  if(*phdr->version)
  {
     report(dbglevl_none, "Found Version:%s\n", phdr->version);
  }
  if(*phdr->seckey)
  {
     report(dbglevl_none, "Found Security Hash:%s\n", phdr->seckey);
  }
}

//-----------------------------------------------------------------------------

struct _wsheader *ws_parse_handshake(char *header, unsigned int length)
{
  int vl;
  int kl;
  int cnt;
  char value[HDRVALUEMAX + 1];
  char key[HDRKEYMAX + 1];
  char *head;
  struct _wsheader *phdr;

  phdr = malloc(sizeof(struct _wsheader));
  if(phdr)
  {
    cnt = 0;
    memset(phdr, 0, sizeof(struct _wsheader));
    while(header[cnt] && (cnt < length))
    {
      head = &header[cnt];
      for(vl = 0; head[vl] != ':'; vl++)
      {
        if(!head[vl] || (vl >= HDRVALUEMAX) || (head[vl] == '\r'))
        {
           break;
        }
        value[vl] = head[vl];
      }
      value[vl] = '\0';
      while(head[vl] == ':' || head[vl] == ' ')
      {
        vl++;
      };
      for(kl = 0; head[vl] != '\r'; vl++, kl++)
      {
        if(!head[vl] || (kl >= HDRKEYMAX) || (head[vl] == '\r'))
        {
          break;
        }
        key[kl] = head[vl];
      }
      key[kl] = '\0';
      report(dbglevl_debug, "value=%s\n", value);
      report(dbglevl_debug, "key=%s\n", key);
      parse_key(phdr, value, key);
      while(head[vl] == '\r' || head[vl] == '\n')
      {
        vl++;
      };
      cnt += vl;
    };
    //print_headers(phdr);
  }
  return phdr;
}

//-----------------------------------------------------------------------------

void ws_reply_handshake(struct _wsheader *phdr, char *header)
{
   char hash[HASHBUFFER + 1];

  if(phdr)
  {
    if(header)
    {
      strncpy(hash, phdr->seckey, HASHBUFFER);
      ws_handshake_hash((unsigned char*)hash);
      sprintf(header, "HTTP/1.1 101 Switching Protocols\r\n"
                      "Upgrade: %s\r\n"
                      "Connection: Upgrade\r\n"
                      "Sec-WebSocket-Accept: %s\r\n"
                      "Sec-WebSocket-Protocol: %s\r\n\r\n",
              phdr->upgrade, hash, phdr->protocol);
    }
    free(phdr);
  }
}

//-----------------------------------------------------------------------------

unsigned long ws_get_frame_size(const unsigned char *frame)
{
  unsigned pnt;
  unsigned long len;

  pnt = 2;
  if((frame[1] & WSPAYLOADMASK) == 126)
  {
    len = frame[pnt++] << 8;
    len |= frame[pnt++];
  }
  else if((frame[1] & WSPAYLOADMASK) == 127)
  {
    len  = (unsigned long)(frame[pnt++]) << 56;
    len |= (unsigned long)(frame[pnt++]) << 48;
    len |= (unsigned long)(frame[pnt++]) << 40;
    len |= (unsigned long)(frame[pnt++]) << 32;
    len |= (unsigned long)(frame[pnt++]) << 24;
    len |= (unsigned long)(frame[pnt++]) << 16;
    len |= (unsigned long)(frame[pnt++]) << 8;
    len |= (unsigned long)(frame[pnt++]);
  }
  else
  {
    len = frame[1] & WSPAYLOADMASK;
  }
  if(frame[1] & WSFRAMEMASK)
  {
    pnt += 4;
  }
  return len + pnt;
}

//-----------------------------------------------------------------------------

unsigned long ws_get_frame_length(const unsigned char *frame)
{
  unsigned pnt;
  unsigned long len;

  pnt = 2;
  if((frame[1] & WSPAYLOADMASK) == 126)
  {
    len = frame[pnt++] << 8;
    len |= frame[pnt++];
  }
  else if((frame[1] & WSPAYLOADMASK) == 127)
  {
    len  = (unsigned long)(frame[pnt++]) << 56;
    len |= (unsigned long)(frame[pnt++]) << 48;
    len |= (unsigned long)(frame[pnt++]) << 40;
    len |= (unsigned long)(frame[pnt++]) << 32;
    len |= (unsigned long)(frame[pnt++]) << 24;
    len |= (unsigned long)(frame[pnt++]) << 16;
    len |= (unsigned long)(frame[pnt++]) << 8;
    len |= (unsigned long)(frame[pnt++]);
  }
  else
  {
    len = frame[1] & WSPAYLOADMASK;
  }
  return len;
}

//-----------------------------------------------------------------------------

unsigned int ws_get_frame_mask(const unsigned char *frame)
{
  unsigned pnt;
  unsigned int mask;

  mask = 0;
  if(frame[1] & WSFRAMEMASK)
  {
    pnt = 2;
    if((frame[1] & WSPAYLOADMASK) == 126)
    {
      pnt += 2;
    }
    else if((frame[1] & WSPAYLOADMASK) == 127)
    {
      pnt += 8;
    }
    mask = frame[pnt] << 24 | frame[pnt + 1] << 16 | frame[pnt + 2] << 8 | frame[pnt + 3];
  }
  return mask;
}

//-----------------------------------------------------------------------------

unsigned ws_get_frame_opcode(const unsigned char *frame)
{
  return frame[0] & WSOPCODEMASK;
}

//-----------------------------------------------------------------------------

void ws_get_frame_header(unsigned char *frame, struct _wshead *head)
{
  if(head && frame)
  {
    memset(head, 0, sizeof(struct _wshead));
    head->size   = ws_get_frame_size(frame);
    head->final  = (frame[0] & WSFLAGFINMASK) != 0;
    head->opcode = ws_get_frame_opcode(frame);
    head->mask   = ws_get_frame_mask(frame);
    head->paylen = ws_get_frame_length(frame);
    head->data   = frame + (head->size - head->paylen);
  }
}

//-----------------------------------------------------------------------------

void ws_set_frame_header(struct _wshead *head, unsigned char *frame, unsigned type,
                         uint32_t mask, uint16_t paylen)
{
  if(head)
  {
    memset(head, 0, sizeof(struct _wshead));
    head->size   = ws_get_frame_size(frame);
    head->final  = (frame[0] & WSFLAGFINMASK) != 0;
    head->opcode = ws_get_frame_opcode(frame);
    head->mask   = ws_get_frame_mask(frame);
    head->paylen = ws_get_frame_length(frame);
    head->data   = frame + (head->size - head->paylen);
  }
}

//-----------------------------------------------------------------------------

void ws_mask_unmask(unsigned char *src, unsigned char *dst, uint32_t mask, unsigned long len)
{
  int i;
  unsigned char msk[4];

  if(mask)
  {
    msk[0] = mask >> 24;
    msk[1] = mask >> 16;
    msk[2] = mask >> 8;
    msk[3] = mask;
    for(i = 0; i < len; i++)
    {
      dst[i] = src[i] ^ msk[i % 4];
    }
  }
}

//-----------------------------------------------------------------------------

void ws_set_frame(unsigned char *frame, unsigned char *data, unsigned type,
                  uint32_t mask, uint16_t paylen)
{
  int hdrlen = 2;
  if(paylen > 125)
  {
    hdrlen += 2;
  }
  if(mask)
  {
    hdrlen += 4;
  }
  memmove(&frame[hdrlen], data, paylen);
  frame[0] = WSFLAGFINMASK | (type & WSOPCODEMASK);
  if(paylen < 126)
  {
    frame[1] = paylen;
  }
  else
  {
    frame[1] = 126;
    frame[2] = paylen >> 8;
    frame[3] = paylen;
  }
  if(mask)
  {
    frame[4] = mask >> 24;
    frame[5] = mask >> 16;
    frame[6] = mask >> 8;
    frame[7] = mask;
    ws_mask_unmask(&frame[hdrlen], &frame[hdrlen], mask, paylen);
  }
}

//-----------------------------------------------------------------------------
// end: wsutils.c

  

A Client Interface Writen In Javascript...

/*--------------------------------------------------------------------------
 * Text and Whiteboard common support.
 */
var ws;
var wbArray;
var wbList;

function ServerMessage(message)
{
  var div = document.createElement("div");
  var node = document.createTextNode(message);
  var element = document.getElementById("msglog");
  div.appendChild(node);
  element.appendChild(div);
  element.scrollTop = element.scrollHeight;
}
function whiteboardOpen()
{
  if("WebSocket" in window)
  {
     var received_msg;
     ws = new WebSocket("ws://dysfunctionalfarms.com:8889/WebCams/basement","whiteboard");
     ws.binaryType = "arraybuffer";
     ws.onopen = function(evt)
     {
       wbArray = new ArrayBuffer(16);
       wbList = new Uint16Array(wbArray);
       var btn = document.getElementById("btnopen");
       btn.disabled=true;
       var btn = document.getElementById("btnclose");
       btn.disabled=false;
     };
     ws.onmessage = function(evt)
     {
       if(evt.data instanceof ArrayBuffer)
       {
         pullDraw(evt.data);
       }
       else
       {
         received_msg = evt.data;
         ServerMessage(received_msg);
       }
     };
     ws.onclose = function(evt)
     {
       ServerMessage("Connection is closed...");
       var btn = document.getElementById("btnopen");
       btn.disabled=false;
       var btn = document.getElementById("btnclose");
       btn.disabled=true;
     };
     ws.onerror = function(evt)
     {
       received_msg = evt.data;
       ServerMessage("Error " + received_msg);
     };
  }
  else
  {
     // The browser doesn't support WebSocket
     alert("WebSocket NOT supported by your Browser!");
  }
}
function whiteboardClose()
{
  ws.close();
}
function whiteboardTickle(evt)
{
  var chat = document.getElementById("msgchat");
  if(evt.keyCode == 13)
  {
    if(ws && ws.readyState == 1)
    {
      ws.send(chat.value);
    }
    chat.value = "";
  }
}

/*--------------------------------------------------------------------------
 * Whiteboard management.
 */
var mouseX = 0, mouseY = 0;
var newMouseX = 0, newMouseY = 0;
var oldMouseX = 0, oldMouseY = 0;
var mouseDown = false;
var color="black";
var weight=1;
var shape=1;
var update=1;
var canvas;
var wb;
var wbctx;
var wbColor;
var overlay;
var ovctx;

const DRAW_PEN = 1;
const DRAW_LINE = 2;
const DRAW_ELLIPSE = 3;
const DRAW_RECTANGLE = 4;
const DRAW_TEXT = 8;
const LINE_THIN = 1;
const LINE_MEDIUM = 2;
const LINE_THICK = 3;
const LINE_DASHED = 4;

function clearWB()
{
  wbctx.fillStyle = "white";
  wbctx.fillRect(0,0,640,480);
}
function preZeroHex(val)
{
  if(val < 16)
  {
    return "0";
  }
  else
  {
    return "";
  }
}
function selectWBColor()
{
  var clr = document.getElementById("clrchose");
  color = clr.options[clr.selectedIndex].value;
  clr.style.backgroundColor=color;
  if(color=="black")
  {
    clr.style.color="white";
  }
  else
  {
    clr.style.color="black";
  }
  var c = clr.options[clr.selectedIndex].label.toString(16)
  wbColor = parseInt(c.substr(1,2),16) << 24 |
            parseInt(c.substr(3,2),16) << 16 |
            parseInt(c.substr(5,2),16) << 8 |
            0;
}
function selectWBWeight(wide)
{
  if(weight == LINE_DASHED)
  {
    wbctx.setLineDash([5]);
  }
  else
  {
    weight = wide;
    wbctx.setLineDash([1,0]);
  }
}
function initWBWeight()
{
  var wgt = document.getElementById("wdash");
  if(!wgt.checked)
  {
    wgt = document.getElementById("wthick");
    if(!wgt.checked)
    {
      wgt = document.getElementById("wmedium");
      if(!wgt.checked)
      {
        wgt = document.getElementById("wthin");
        wgt.checked = true;
        weight = LINE_THIN;
      }
      else
      {
        weight = LINE_MEDIUM;
      }
    }
    else
    {
      weight = LINE_THICK;
    }
  }
  else
  {
    weight = LINE_DASHED;
  }
  selectWBWeight(weight);
}
function selectWBDraw(ls)
{
  shape = ls;
}
function initWBDraw()
{
  var shp = document.getElementById("srect");
  if(!shp.checked)
  {
    var shp = document.getElementById("scircle");
    if(!shp.checked)
    {
      var shp = document.getElementById("sline");
      if(!shp.checked)
      {
        var shp = document.getElementById("spen");
        shp.checked = true;
        shape = DRAW_PEN;
      }
      else
      {
        shape = DRAW_LINE;
      }
    }
    else
    {
      shape = DRAW_ELLIPSE;
    }
  }
  else
  {
    shape = DRAW_RECTANGLE;
  }
}
function selectWBUpdate(push)
{
  if(push)
  {
  }
  else
  {
    var upd = document.getElementById("update");
    var pud = document.getElementById("pushupdate");
    if(upd.checked)
    {
      pud.disabled=true;
    }
    else
    {
      pud.disabled=false;
    }
  }
}
function initOverlay()
{
  overlay = document.createElement("canvas");
  overlay.id = "overlay";
  overlay.width = wb.width;
  overlay.height = wb.height;
  wb.parentNode.appendChild(overlay);
  overlay.addEventListener("mousemove",onOverlayMouseMove,false);
  ovctx = overlay.getContext("2d");
  overlay.style.cursor = "crosshair";
  overlay.style.position = "absolute";
  overlay.style.zIndex = -1;
  overlay.style.top = 0;
  overlay.style.left = 0;
  overlay.style.border = "1px solid black";
  overlay.style.background = "transparent";
}
function initWB()
{
  wb = document.getElementById("whiteboard");
  wbctx = wb.getContext("2d");
  wbctx.lineWidth = LINE_THIN;
  wb.addEventListener("mousedown",onCanvasMouseDown,false);
  wb.addEventListener("mousemove",onCanvasMouseMove,false);
  document.addEventListener("mouseup",onDocumentMouseUp,false);
  wb.width = 640;
  wb.height = 480;
  clearWB();
  selectWBColor();
  selectWBUpdate(0);
  initWBWeight();
  initWBDraw();
  initOverlay();
}
function pdrawRect(x,y,w,h)
{
  wbctx.beginPath();
  wbctx.rect(x,y,w,h);
  wbctx.stroke();
}
function pdrawEllipse(x,y,xr,yr)
{
  var r = xr > yr ? xr : yr;
  wbctx.beginPath();
  wbctx.arc(x,y,r,0,360);
  wbctx.stroke();
}
function pdraw(x1,y1,x2,y2)
{
  wbctx.beginPath();
  wbctx.moveTo(x1,y1);
  wbctx.lineTo(x2,y2);
  wbctx.closePath();
  wbctx.stroke();
}
function pushDraw(x1,y1,x2,y2,clr)
{
  if(ws && ws.readyState == 1)
  {
    wbList[0] = clr >>> 16;
    wbList[1] = clr;
    wbList[2] = weight;
    wbList[3] = shape;
    wbList[4] = x1;
    wbList[5] = y1;
    wbList[6] = x2;
    wbList[7] = y2;
    ws.send(wbList);
  }
}
function pullDraw(packet)
{
  var dl = new Uint16Array(packet);
  var rgba = dl[0] << 16 | dl[1];
  var r = rgba >> 24 & 255;
  var g = rgba >> 16 & 255;
  var b = rgba >> 8 & 255;
  var a = rgba & 255;
  var clr = "#" + preZeroHex(r) + r.toString(16).toUpperCase() + preZeroHex(g)
                + g.toString(16).toUpperCase() + preZeroHex(b) + b.toString(16).toUpperCase();
  wbctx.strokeStyle = clr;
  wbctx.lineWidth = dl[2];
  if(dl[3] == DRAW_RECTANGLE)
  {
    pdrawRect(dl[4],dl[5],dl[6]-dl[4],dl[7]-dl[5]);
  }
  else if(dl[3] == DRAW_ELLIPSE)
  {
    pdrawEllipse(dl[4],dl[5],dl[6]-dl[4]>0?dl[6]-dl[4]:dl[4]-dl[6],dl[7]-dl[5]>0?dl[7]-dl[5]:dl[5]-dl[7]);
  }
  else
  {
    pdraw(dl[4],dl[5],dl[6],dl[7]);
  }
  delete dl;
}
function draw(x1,y1,x2,y2,clr)
{
  wbctx.strokeStyle = clr;
  wbctx.lineWidth = weight;
  if(shape == DRAW_RECTANGLE)
  {
    pdrawRect(x1,y1,x2-x1,y2-y1);
  }
  else if(shape == DRAW_ELLIPSE)
  {
    var xr = oldMouseX - mouseX > 0 ? oldMouseX - mouseX : mouseX - oldMouseX;
    var yr = oldMouseY - mouseY > 0 ? oldMouseY - mouseY : mouseY - oldMouseY;
    pdrawEllipse(mouseX,mouseY,xr,yr);
  }
  else
  {
    pdraw(x1,y1,x2,y2);
  }
}
function onCanvasMouseDown(event)
{
  mouseDown = true;
  event.preventDefault();
  mouseX = event.layerX - this.offsetLeft;
  mouseY = event.layerY - this.offsetTop;
  oldMouseX = mouseX;
  oldMouseY = mouseY;
  if(shape != DRAW_PEN)
  {
    overlay.style.zIndex = 99;
  }
}
function onCanvasMouseMove(event)
{
  if(mouseDown)
  {
    if(shape == DRAW_PEN)
    {
      oldMouseX = mouseX;
      oldMouseY = mouseY;
      mouseX = event.layerX - this.offsetLeft;
      mouseY = event.layerY - this.offsetTop;
      draw(oldMouseX,oldMouseY,mouseX,mouseY,color);
      pushDraw(oldMouseX,oldMouseY,mouseX,mouseY,wbColor);
    }
  }
}
function onOverlayMouseMove(event)
{
  if(mouseDown)
  {
    ovctx.clearRect(0,0,overlay.width,overlay.height);
    ovctx.strokeStyle = color;
    ovctx.lineWidth = weight;
    oldMouseX = event.layerX - this.offsetLeft;
    oldMouseY = event.layerY - this.offsetTop;
    if(shape == DRAW_LINE)
    {
      ovctx.beginPath();
      ovctx.moveTo(mouseX,mouseY);
      ovctx.lineTo(oldMouseX,oldMouseY);
      ovctx.closePath();
      ovctx.stroke();
    }
    if(shape == DRAW_ELLIPSE)
    {
      var x = oldMouseX - mouseX > 0 ? oldMouseX - mouseX : mouseX - oldMouseX;
      var y = oldMouseY - mouseY > 0 ? oldMouseY - mouseY : mouseY - oldMouseY;
      var r = x > y ? x : y;
      ovctx.beginPath();
      ovctx.arc(mouseX,mouseY,r,0,360);
      ovctx.stroke();
    }
    if(shape == DRAW_RECTANGLE)
    {
      ovctx.beginPath();
      ovctx.rect(mouseX,mouseY, oldMouseX - mouseX, oldMouseY - mouseY);
      ovctx.stroke();
    }
  }
}
function onDocumentMouseUp(event)
{
  if(mouseDown)
  {
   ovctx.clearRect(0,0,overlay.width,overlay.height);
   if(shape != DRAW_PEN)
   {
     draw(mouseX,mouseY,oldMouseX,oldMouseY,color);
     pushDraw(mouseX,mouseY,oldMouseX,oldMouseY,wbColor);
   }
   mouseDown = false;
  }
  overlay.style.zIndex=-1;
}

  

The Code Demonstrates The Following Programming Techniques...

Conclusion...

It is known that this code has many 'Freshman' mistakes in it. It is not claimed to be good code. Hopefully though; it demonstrates some of the less obvious interface mechanisms when communicating with both a Window-Manager and a Client program. A definition of how to use the code is also missing. Not purposely, an interface spec just can't be fit into my schedule. For now. Besides... This code is not really meant to be used. Although I use this code it is presented here purely for educational purposes.

11145