/******************************************************************************* * Copyright (c) 2018, 2022 Wind River Systems, Inc., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * https://www.eclipse.org/legal/epl-2.0/ * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Keith Holman - initial implementation and documentation * Ian Craggs - use memory tracking * Ian Craggs - fix for one MQTT packet spread over >1 ws frame * Sven Gambel - move WebSocket proxy support to generic proxy support *******************************************************************************/ #include #include #include #include "WebSocket.h" #include "Base64.h" #include "Log.h" #include "SHA1.h" #include "LinkedList.h" #include "MQTTProtocolOut.h" #include "SocketBuffer.h" #include "StackTrace.h" #if defined(__linux__) # include #elif defined(__APPLE__) # include # define htobe16(x) OSSwapHostToBigInt16(x) # define htobe32(x) OSSwapHostToBigInt32(x) # define htobe64(x) OSSwapHostToBigInt64(x) # define be16toh(x) OSSwapBigToHostInt16(x) # define be32toh(x) OSSwapBigToHostInt32(x) # define be64toh(x) OSSwapBigToHostInt64(x) #elif defined(__FreeBSD__) || defined(__NetBSD__) # include #elif defined(_WIN32) || defined(_WIN64) # pragma comment(lib, "rpcrt4.lib") # include # if !(defined(__MINGW32__)) # define strncasecmp(s1,s2,c) _strnicmp(s1,s2,c) # endif # if defined(__MINGW32__) # define htonll __builtin_bswap64 # define ntohll __builtin_bswap64 # else # define htonll(x) _byteswap_uint64(x) # define ntohll(x) _byteswap_uint64(x) # endif # if BYTE_ORDER == LITTLE_ENDIAN # define htobe16(x) htons(x) # define htobe32(x) htonl(x) # define htobe64(x) htonll(x) # define be16toh(x) ntohs(x) # define be32toh(x) ntohl(x) # define be64toh(x) ntohll(x) # elif BYTE_ORDER == BIG_ENDIAN # define htobe16(x) (x) # define htobe32(x) (x) # define htobe64(x) (x) # define be16toh(x) (x) # define be32toh(x) (x) # define be64toh(x) (x) # else # error "unknown endian" # endif /* For Microsoft Visual Studio < 2015 */ # if defined(_MSC_VER) && _MSC_VER < 1900 # define snprintf _snprintf # endif #endif #if defined(OPENSSL) #include "SSLSocket.h" #include #endif /* defined(OPENSSL) */ #include "Socket.h" #define HTTP_PROTOCOL(x) x ? "https" : "http" #if !(defined(_WIN32) || defined(_WIN64)) #if defined(USE_LIBUUID) #include #else /* if defined(USE_LIBUUID) */ #include #include #include /** @brief raw uuid type */ typedef unsigned char uuid_t[16]; /** * @brief generates a uuid, compatible with RFC 4122, version 4 (random) * @note Uses a very insecure algorithm but no external dependencies */ static void uuid_generate( uuid_t out ) { #if defined(OPENSSL) int rc = RAND_bytes( out, sizeof(uuid_t)); if ( !rc ) #endif /* defined (OPENSSL) */ { /* very insecure, but generates a random uuid */ int i; srand(time(NULL)); for ( i = 0; i < 16; ++i ) out[i] = (unsigned char)(rand() % UCHAR_MAX); out[6] = (out[6] & 0x0f) | 0x40; out[8] = (out[8] & 0x3F) | 0x80; } } /** @brief converts a uuid to a string */ static void uuid_unparse( uuid_t uu, char *out ) { int i; for ( i = 0; i < 16; ++i ) { if ( i == 4 || i == 6 || i == 8 || i == 10 ) { *out = '-'; ++out; } out += sprintf( out, "%02x", uu[i] ); } *out = '\0'; } #endif /* else if defined(LIBUUID) */ #endif /* if !(defined(_WIN32) || defined(_WIN64)) */ #include "Heap.h" /** raw websocket frame data */ struct ws_frame { size_t len; /**< length of frame */ size_t pos; /**< current position within the buffer */ }; /** Current frame being processed */ struct ws_frame *last_frame = NULL; /** Holds any received websocket frames, to be process */ static List* in_frames = NULL; static char * frame_buffer = NULL; static size_t frame_buffer_len = 0; static size_t frame_buffer_index = 0; static size_t frame_buffer_data_len = 0; /* static function declarations */ static const char *WebSocket_strcasefind( const char *buf, const char *str, size_t len); static char *WebSocket_getRawSocketData( networkHandles *net, size_t bytes, size_t* actual_len, int* rc); static void WebSocket_rewindData( void ); static void WebSocket_pong( networkHandles *net, char *app_data, size_t app_data_len); static int WebSocket_receiveFrame(networkHandles *net, size_t *actual_len); /** * calculates the amount of data required for the websocket header * * this function is used to calculate how much offset is required before calling * @p WebSocket_putdatas, as that function will write data before the passed in * buffer * * @param[in,out] net network connection * @param[in] mask_data whether to mask the data * @param[in] data_len amount of data in the payload * * @return the size in bytes of the websocket header required * * @see WebSocket_putdatas */ size_t WebSocket_calculateFrameHeaderSize(networkHandles *net, int mask_data, size_t data_len) { int ret = 0; if ( net && net->websocket ) { if ( data_len < 126u) ret = 2; /* header 2 bytes */ else if ( data_len < 65536u ) ret = 4; /* for extra 2-bytes for payload length */ else if ( data_len < 0xFFFFFFFFFFFFFFFF ) ret = 10; /* for extra 8-bytes for payload length */ if ( mask_data & 0x1 ) ret += sizeof(uint32_t); /* for mask */ } return ret; } /** * @brief builds a websocket frame for data transmission * * write a websocket header and will mask the payload in all the passed in * buffers * * @param[in,out] net network connection * @param[in] opcode websocket opcode for the packet * @param[in] mask_data whether to mask the data * @param[in,out] buf0 first buffer, will write before this * @param[in] buf0len size of first buffer * @param[in] count number of payload buffers * @param[in,out] buffers array of payload buffers * @param[in] buflens array of payload buffer sizes * @param[in] freeData array indicating to free payload buffers * * @return amount of data to write to socket */ struct frameData { char* wsbuf0; size_t wsbuf0len; }; static struct frameData WebSocket_buildFrame(networkHandles* net, int opcode, int mask_data, char** pbuf0, size_t* pbuf0len, PacketBuffers* bufs) { int buf_len = 0u; struct frameData rc; int new_mask = 0; FUNC_ENTRY; memset(&rc, '\0', sizeof(rc)); if ( net->websocket ) { size_t ws_header_size = 0u; size_t data_len = 0L; int i; /* Calculate total length of MQTT buffers */ data_len = *pbuf0len; for (i = 0; i < bufs->count; ++i) data_len += bufs->buflens[i]; /* add space for websocket frame header */ ws_header_size = WebSocket_calculateFrameHeaderSize(net, mask_data, data_len); if (*pbuf0) { rc.wsbuf0len = *pbuf0len + ws_header_size; rc.wsbuf0 = malloc(rc.wsbuf0len); if (rc.wsbuf0 == NULL) goto exit; memcpy(&rc.wsbuf0[ws_header_size], *pbuf0, *pbuf0len); } else { rc.wsbuf0 = malloc(ws_header_size); if (rc.wsbuf0 == NULL) goto exit; rc.wsbuf0len = ws_header_size; } if (mask_data && (bufs->mask[0] == 0)) { /* generate mask, since we are a client */ #if defined(OPENSSL) RAND_bytes(&bufs->mask[0], sizeof(bufs->mask)); #else /* if defined(OPENSSL) */ bufs->mask[0] = (rand() % UINT8_MAX); bufs->mask[1] = (rand() % UINT8_MAX); bufs->mask[2] = (rand() % UINT8_MAX); bufs->mask[3] = (rand() % UINT8_MAX); #endif /* else if defined(OPENSSL) */ new_mask = 1; } /* 1st byte */ rc.wsbuf0[buf_len] = (char)(1 << 7); /* final flag */ /* 3 bits reserved for negotiation of protocol */ rc.wsbuf0[buf_len] |= (char)(opcode & 0x0F); /* op code */ ++buf_len; /* 2nd byte */ rc.wsbuf0[buf_len] = (char)((mask_data & 0x1) << 7); /* masking bit */ /* payload length */ if ( data_len < 126u ) rc.wsbuf0[buf_len++] |= data_len & 0x7F; /* 3rd byte & 4th bytes - extended payload length */ else if ( data_len < 65536u ) { uint16_t len = htobe16((uint16_t)data_len); rc.wsbuf0[buf_len++] |= (126u & 0x7F); memcpy( &rc.wsbuf0[buf_len], &len, 2u ); buf_len += 2; } else if ( data_len < 0xFFFFFFFFFFFFFFFF ) { uint64_t len = htobe64((uint64_t)data_len); rc.wsbuf0[buf_len++] |= (127u & 0x7F); memcpy( &rc.wsbuf0[buf_len], &len, 8 ); buf_len += 8; } else { Log(TRACE_PROTOCOL, 1, "Data too large for websocket frame" ); buf_len = -1; } if (mask_data) { size_t idx = 0u; /* copy masking key into ws header */ memcpy( &rc.wsbuf0[buf_len], &bufs->mask, sizeof(uint32_t)); buf_len += sizeof(uint32_t); /* mask packet fixed header */ for (i = (int)ws_header_size; i < (int)rc.wsbuf0len; ++i, ++idx) rc.wsbuf0[i] ^= bufs->mask[idx % 4]; /* variable data buffers */ for (i = 0; i < bufs->count; ++i) { size_t j; if (new_mask == 0 && (i == 2 || i == bufs->count-1)) /* topic (2) and payload (last) buffers are already masked */ break; for ( j = 0u; j < bufs->buflens[i]; ++j, ++idx ) { bufs->buffers[i][j] ^= bufs->mask[idx % 4]; } } } } exit: FUNC_EXIT_RC(buf_len); return rc; } static void WebSocket_unmaskData(size_t idx, PacketBuffers* bufs) { int i; FUNC_ENTRY; for (i = 0; i < bufs->count; ++i) { size_t j; for (j = 0u; j < bufs->buflens[i]; ++j, ++idx) bufs->buffers[i][j] ^= bufs->mask[idx % 4]; } /* show that the mask has been removed */ bufs->mask[0] = bufs->mask[1] = bufs->mask[2] = bufs->mask[3] = 0; FUNC_EXIT; } /** * sends out a websocket request on the given uri * * @param[in] net network connection * @param[in] ssl ssl flag * @param[in] uri uri to connect to * * @retval SOCKET_ERROR on failure * @retval 1 on success * * @see WebSocket_upgrade */ int WebSocket_connect( networkHandles *net, int ssl, const char *uri) { int rc; char *buf = NULL; char *headers_buf = NULL; const MQTTClient_nameValue *headers = net->httpHeaders; int i, buf_len = 0; int headers_buf_len = 0; size_t hostname_len; int port = 80; const char *topic = NULL; #if defined(_WIN32) || defined(_WIN64) UUID uuid; #else /* if defined(_WIN32) || defined(_WIN64) */ uuid_t uuid; #endif /* else if defined(_WIN32) || defined(_WIN64) */ FUNC_ENTRY; /* Generate UUID */ if (net->websocket_key == NULL) net->websocket_key = malloc(25u); else net->websocket_key = realloc(net->websocket_key, 25u); if (net->websocket_key == NULL) { rc = PAHO_MEMORY_ERROR; goto exit; } #if defined(_WIN32) || defined(_WIN64) ZeroMemory( &uuid, sizeof(UUID) ); UuidCreate( &uuid ); Base64_encode( net->websocket_key, 25u, (const b64_data_t*)&uuid, sizeof(UUID) ); #else /* if defined(_WIN32) || defined(_WIN64) */ uuid_generate( uuid ); Base64_encode( net->websocket_key, 25u, uuid, sizeof(uuid_t) ); #endif /* else if defined(_WIN32) || defined(_WIN64) */ hostname_len = MQTTProtocol_addressPort(uri, &port, &topic, ssl ? WSS_DEFAULT_PORT : WS_DEFAULT_PORT); /* if no topic, use default */ if ( !topic ) topic = "/mqtt"; if ( headers ) { char *headers_buf_cur = NULL; while ( headers->name != NULL && headers->value != NULL ) { headers_buf_len += (int)(strlen(headers->name) + strlen(headers->value) + 4); headers++; } headers_buf_len++; if ((headers_buf = malloc(headers_buf_len)) == NULL) { rc = PAHO_MEMORY_ERROR; goto exit; } headers = net->httpHeaders; headers_buf_cur = headers_buf; while ( headers->name != NULL && headers->value != NULL ) { headers_buf_cur += snprintf(headers_buf_cur, headers_buf_len - (headers_buf_cur - headers_buf), "%s: %s\r\n", headers->name, headers->value); headers++; } *headers_buf_cur = '\0'; } for ( i = 0; i < 2; ++i ) { buf_len = snprintf( buf, (size_t)buf_len, "GET %s HTTP/1.1\r\n" "Host: %.*s:%d\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Origin: %s://%.*s:%d\r\n" "Sec-WebSocket-Key: %s\r\n" "Sec-WebSocket-Version: 13\r\n" "Sec-WebSocket-Protocol: mqtt\r\n" "%s" "\r\n", topic, (int)hostname_len, uri, port, #if defined(OPENSSL) HTTP_PROTOCOL(net->ssl), #else HTTP_PROTOCOL(0), #endif (int)hostname_len, uri, port, net->websocket_key, headers_buf ? headers_buf : ""); if ( i == 0 && buf_len > 0 ) { ++buf_len; /* need 1 extra byte for ending '\0' */ if ((buf = malloc( buf_len )) == NULL) { rc = PAHO_MEMORY_ERROR; goto exit; } } } if (headers_buf) free( headers_buf ); if ( buf ) { PacketBuffers nulbufs = {0, NULL, NULL, NULL, {0, 0, 0, 0}}; #if defined(OPENSSL) if (net->ssl) SSLSocket_putdatas(net->ssl, net->socket, buf, buf_len, nulbufs); else #endif Socket_putdatas(net->socket, buf, buf_len, nulbufs); free( buf ); rc = 1; } else { free(net->websocket_key); net->websocket_key = NULL; rc = SOCKET_ERROR; } exit: FUNC_EXIT_RC(rc); return rc; } /** * closes a websocket connection * * @param[in,out] net structure containing network connection * @param[in] status_code websocket close status code * @param[in] reason reason for closing connection (optional) */ void WebSocket_close(networkHandles *net, int status_code, const char *reason) { struct frameData fd; PacketBuffers nulbufs = {0, NULL, NULL, NULL, {0, 0, 0, 0}}; FUNC_ENTRY; if ( net->websocket ) { char *buf0; size_t buf0len = sizeof(uint16_t); uint16_t status_code_be; const int mask_data = 1; /* all frames from client must be masked */ if ( status_code < WebSocket_CLOSE_NORMAL || status_code > WebSocket_CLOSE_TLS_FAIL ) status_code = WebSocket_CLOSE_GOING_AWAY; if ( reason ) buf0len += strlen(reason); buf0 = malloc(buf0len); if ( !buf0 ) goto exit; /* encode status code */ status_code_be = htobe16((uint16_t)status_code); memcpy(buf0, &status_code_be, sizeof(uint16_t)); /* encode reason, if provided */ if ( reason ) strcpy( &buf0[sizeof(uint16_t)], reason ); fd = WebSocket_buildFrame( net, WebSocket_OP_CLOSE, mask_data, &buf0, &buf0len, &nulbufs); #if defined(OPENSSL) if (net->ssl) SSLSocket_putdatas(net->ssl, net->socket, fd.wsbuf0, fd.wsbuf0len, nulbufs); else #endif Socket_putdatas(net->socket, fd.wsbuf0, fd.wsbuf0len, nulbufs); free(fd.wsbuf0); /* free temporary ws header */ /* websocket connection is now closed */ net->websocket = 0; free( buf0 ); } if ( net->websocket_key ) { free( net->websocket_key ); net->websocket_key = NULL; } exit: FUNC_EXIT; } /** * @brief receives 1 byte from a socket * * @param[in,out] net network connection * @param[out] c byte that was read * * @retval SOCKET_ERROR on error * @retval TCPSOCKET_INTERRUPTED no data available * @retval TCPSOCKET_COMPLETE on success * * @see WebSocket_getdata */ int WebSocket_getch(networkHandles *net, char* c) { int rc = SOCKET_ERROR; FUNC_ENTRY; if ( net->websocket ) { struct ws_frame *frame = NULL; if ( in_frames && in_frames->first ) frame = in_frames->first->content; if ( !frame || frame->len == frame->pos ) { size_t actual_len = 0u; rc = WebSocket_receiveFrame( net, &actual_len); if ( rc != TCPSOCKET_COMPLETE ) goto exit; /* we got a frame, let take off the top of queue */ if ( in_frames->first ) frame = in_frames->first->content; } /* set current working frame */ if (frame && frame->len > frame->pos) { unsigned char *buf = (unsigned char *)frame + sizeof(struct ws_frame); *c = buf[frame->pos++]; rc = TCPSOCKET_COMPLETE; } } #if defined(OPENSSL) else if ( net->ssl ) rc = SSLSocket_getch(net->ssl, net->socket, c); #endif else rc = Socket_getch(net->socket, c); exit: FUNC_EXIT_RC(rc); return rc; } size_t WebSocket_framePos() { if ( in_frames && in_frames->first ) { struct ws_frame *frame = in_frames->first->content; return frame->pos; } else { return 0; } } void WebSocket_framePosSeekTo(size_t pos) { if ( in_frames && in_frames->first ) { struct ws_frame *frame = in_frames->first->content; frame->pos = pos; } } /** * @brief receives data from a socket. * It should receive all data from the socket that is immediately available. * Because it is encapsulated in websocket frames which cannot be * * @param[in,out] net network connection * @param[in] bytes amount of data to get (0 if last packet) * @param[out] actual_len amount of data read * * @return a pointer to the read data * * @see WebSocket_getch */ char *WebSocket_getdata(networkHandles *net, size_t bytes, size_t* actual_len) { char *rv = NULL; int rc; FUNC_ENTRY; if ( net->websocket ) { struct ws_frame *frame = NULL; if ( bytes == 0u ) { /* done with current frame, move it to last frame */ if ( in_frames && in_frames->first ) frame = in_frames->first->content; /* return the data from the next frame, if we have one */ if ( frame && frame->pos == frame->len ) { rv = (char *)frame + sizeof(struct ws_frame) + frame->pos; *actual_len = frame->len - frame->pos; if ( last_frame ) free( last_frame ); last_frame = ListDetachHead(in_frames); } goto exit; } /* look at the first websocket frame */ if ( in_frames && in_frames->first ) frame = in_frames->first->content; /* no current frame, so let's go receive one for the network */ if ( !frame ) { const int rc = WebSocket_receiveFrame( net, actual_len ); if ( rc == TCPSOCKET_COMPLETE && in_frames && in_frames->first) frame = in_frames->first->content; } if ( frame ) { rv = (char *)frame + sizeof(struct ws_frame) + frame->pos; *actual_len = frame->len - frame->pos; /* use the rest of the frame */ while (*actual_len < bytes) { const int rc = WebSocket_receiveFrame(net, actual_len); if (rc != TCPSOCKET_COMPLETE) { goto exit; } /* refresh pointers */ frame = in_frames->first->content; rv = (char *)frame + sizeof(struct ws_frame) + frame->pos; *actual_len = frame->len - frame->pos; /* use the rest of the frame */ } /* end while */ if (*actual_len > bytes) { frame->pos += bytes; } else if (*actual_len == bytes && in_frames) { if ( last_frame ) free( last_frame ); last_frame = ListDetachHead(in_frames); } } } #if defined(OPENSSL) else if ( net->ssl ) rv = SSLSocket_getdata(net->ssl, net->socket, bytes, actual_len, &rc); #endif else rv = Socket_getdata(net->socket, bytes, actual_len, &rc); exit: FUNC_EXIT_RC(rv); return rv; } void WebSocket_rewindData( void ) { frame_buffer_index = 0; } /** * reads raw socket data for underlying layers * * @param[in] net network connection * @param[in] bytes number of bytes to read, 0 to complete packet * @param[in] actual_len amount of data read * * @return a buffer containing raw data */ char *WebSocket_getRawSocketData(networkHandles *net, size_t bytes, size_t* actual_len, int* rc) { char *rv = NULL; size_t bytes_requested = bytes; FUNC_ENTRY; if (bytes > 0) { if (frame_buffer_data_len - frame_buffer_index >= bytes) { *actual_len = bytes; rv = frame_buffer + frame_buffer_index; frame_buffer_index += bytes; *rc = (int)bytes; goto exit; } else { bytes = bytes - (frame_buffer_data_len - frame_buffer_index); } } *actual_len = 0; // not enough data in the buffer, get data from socket #if defined(OPENSSL) if ( net->ssl ) rv = SSLSocket_getdata(net->ssl, net->socket, bytes, actual_len, rc); else #endif rv = Socket_getdata(net->socket, bytes, actual_len, rc); if (*rc == 0) { *rc = SOCKET_ERROR; goto exit; } // clear buffer if (bytes == 0) { frame_buffer_index = 0; frame_buffer_data_len = 0; frame_buffer_len = 0; if (frame_buffer) { free (frame_buffer); frame_buffer = NULL; } } // append data to the buffer else if (rv != NULL && *actual_len != 0U) { // no buffer allocated if (!frame_buffer) { if ((frame_buffer = (char *)malloc(*actual_len)) == NULL) { rv = NULL; goto exit; } memcpy(frame_buffer, rv, *actual_len); frame_buffer_index = 0; frame_buffer_data_len = *actual_len; frame_buffer_len = *actual_len; } // buffer size is big enough else if (frame_buffer_data_len + *actual_len < frame_buffer_len) { memcpy(frame_buffer + frame_buffer_data_len, rv, *actual_len); frame_buffer_data_len += *actual_len; } // resize buffer else { frame_buffer = realloc(frame_buffer, frame_buffer_data_len + *actual_len); frame_buffer_len = frame_buffer_data_len + *actual_len; memcpy(frame_buffer + frame_buffer_data_len, rv, *actual_len); frame_buffer_data_len += *actual_len; } SocketBuffer_complete(net->socket); } else goto exit; bytes = bytes_requested; // if possible, return data from the buffer if (bytes > 0) { if (frame_buffer_data_len - frame_buffer_index >= bytes) { *actual_len = bytes; rv = frame_buffer + frame_buffer_index; frame_buffer_index += bytes; } else { *actual_len = frame_buffer_data_len - frame_buffer_index; rv = frame_buffer + frame_buffer_index; frame_buffer_index += *actual_len; } } exit: FUNC_EXIT; return rv; } /** * sends a "websocket pong" message * * @param[in] net network connection * @param[in] app_data application data to put in payload * @param[in] app_data_len application data length */ void WebSocket_pong(networkHandles *net, char *app_data, size_t app_data_len) { FUNC_ENTRY; if ( net->websocket ) { char *buf0 = NULL; size_t buf0len = 0; int freeData = 0; struct frameData fd; const int mask_data = 1; /* all frames from client must be masked */ PacketBuffers appbuf = {1, &app_data, &app_data_len, &freeData, {0, 0, 0, 0}}; fd = WebSocket_buildFrame( net, WebSocket_OP_PONG, mask_data, &buf0, &buf0len, &appbuf); Log(TRACE_PROTOCOL, 1, "Sending WebSocket PONG" ); #if defined(OPENSSL) if (net->ssl) SSLSocket_putdatas(net->ssl, net->socket, fd.wsbuf0, fd.wsbuf0len /*header_len + app_data_len*/, appbuf); else #endif Socket_putdatas(net->socket, fd.wsbuf0, fd.wsbuf0len /*header_len + app_data_len*/, appbuf); free(fd.wsbuf0); free(buf0); } FUNC_EXIT; } /** * writes data to a socket (websocket header will be prepended if required) * * @warning buf0 will be expanded (backwords before @p buf0 buffer, to add a * websocket frame header to the data if required). So use * @p WebSocket_calculateFrameHeader, to determine if extra space is needed * before the @p buf0 pointer. * * @param[in,out] net network connection * @param[in,out] buf0 first buffer * @param[in] buf0len size of first buffer * @param[in] count number of payload buffers * @param[in,out] buffers array of paylaod buffers * @param[in] buflens array of payload buffer sizes * @param[in] freeData array indicating to free payload buffers * * @return amount of data wrote to socket * * @see WebSocket_calculateFrameHeaderSize */ int WebSocket_putdatas(networkHandles* net, char** buf0, size_t* buf0len, PacketBuffers* bufs) { const int mask_data = 1; /* must mask websocket data from client */ int rc; FUNC_ENTRY; if (net->websocket) { struct frameData wsdata; wsdata = WebSocket_buildFrame(net, WebSocket_OP_BINARY, mask_data, buf0, buf0len, bufs); #if defined(OPENSSL) if (net->ssl) rc = SSLSocket_putdatas(net->ssl, net->socket, wsdata.wsbuf0, wsdata.wsbuf0len, *bufs); else #endif rc = Socket_putdatas(net->socket, wsdata.wsbuf0, wsdata.wsbuf0len, *bufs); if (rc != TCPSOCKET_INTERRUPTED) { if (mask_data) WebSocket_unmaskData(*buf0len, bufs); free(wsdata.wsbuf0); /* free temporary ws header */ } } else { #if defined(OPENSSL) if (net->ssl) rc = SSLSocket_putdatas(net->ssl, net->socket, *buf0, *buf0len, *bufs); else #endif rc = Socket_putdatas(net->socket, *buf0, *buf0len, *bufs); } FUNC_EXIT_RC(rc); return rc; } /** * receives incoming socket data and parses websocket frames * Copes with socket reads returning partial websocket frames by using the * SocketBuffer mechanism. * * @param[in] net network connection * @param[out] actual_len amount of data actually read * * @retval TCPSOCKET_COMPLETE packet received * @retval TCPSOCKET_INTERRUPTED incomplete packet received * @retval SOCKET_ERROR an error was encountered */ int WebSocket_receiveFrame(networkHandles *net, size_t *actual_len) { struct ws_frame *res = NULL; int rc = TCPSOCKET_COMPLETE; int opcode = 0; FUNC_ENTRY; if ( !in_frames ) in_frames = ListInitialize(); /* see if there is frame currently on queue */ if ( in_frames->first ) res = in_frames->first->content; //while( !res ) //{ opcode = WebSocket_OP_BINARY; do { /* obtain all frames in the sequence */ int is_final = 0; while ( is_final == 0 ) { char *b; size_t len = 0u; int tmp_opcode; int has_mask; size_t cur_len = 0u; uint8_t mask[4] = { 0u, 0u, 0u, 0u }; size_t payload_len; int rcs; /* socket return code */ b = WebSocket_getRawSocketData(net, 2u, &len, &rcs); if (rcs == SOCKET_ERROR) { rc = rcs; goto exit; } if ( !b ) { rc = TCPSOCKET_INTERRUPTED; goto exit; } else if (len < 2u ) { rc = TCPSOCKET_INTERRUPTED; goto exit; } /* 1st byte */ is_final = (b[0] & 0xFF) >> 7; tmp_opcode = (b[0] & 0x0F); if ( tmp_opcode ) /* not a continuation frame */ opcode = tmp_opcode; /* invalid websocket packet must return error */ if ( opcode < WebSocket_OP_CONTINUE || opcode > WebSocket_OP_PONG || ( opcode > WebSocket_OP_BINARY && opcode < WebSocket_OP_CLOSE ) ) { rc = SOCKET_ERROR; goto exit; } /* 2nd byte */ has_mask = (b[1] & 0xFF) >> 7; payload_len = (b[1] & 0x7F); /* determine payload length */ if ( payload_len == 126 ) { /* If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length. */ b = WebSocket_getRawSocketData(net, 2u, &len, &rcs); if (rcs == SOCKET_ERROR) { rc = rcs; goto exit; } if ( !b ) { rc = SOCKET_ERROR; goto exit; } else if (len < 2u ) { rc = TCPSOCKET_INTERRUPTED; goto exit; } /* convert from big endian 16 to host */ payload_len = be16toh(*(uint16_t*)b); } else if ( payload_len == 127 ) { /* If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the most significant bit MUST be 0) are the payload length */ b = WebSocket_getRawSocketData(net, 8u, &len, &rcs); if (rcs == SOCKET_ERROR) { rc = rcs; goto exit; } if ( !b ) { rc = SOCKET_ERROR; goto exit; } else if (len < 8u ) { rc = TCPSOCKET_INTERRUPTED; goto exit; } /* convert from big-endian 64 to host */ payload_len = (size_t)be64toh(*(uint64_t*)b); } if ( has_mask ) { uint8_t mask[4]; b = WebSocket_getRawSocketData(net, 4u, &len, &rcs); if (rcs == SOCKET_ERROR) { rc = rcs; goto exit; } if ( !b ) { rc = SOCKET_ERROR; goto exit; } if (len < 4u ) { rc = TCPSOCKET_INTERRUPTED; goto exit; } memcpy( &mask[0], b, sizeof(uint32_t)); } /* use the socket buffer to read in the whole websocket frame */ b = WebSocket_getRawSocketData(net, payload_len, &len, &rcs); if (rcs == SOCKET_ERROR) { rc = rcs; goto exit; } if (!b) { rc = SOCKET_ERROR; goto exit; } if (len < payload_len ) { rc = TCPSOCKET_INTERRUPTED; goto exit; } /* unmask data */ if ( has_mask ) { size_t i; for ( i = 0u; i < payload_len; ++i ) b[i] ^= mask[i % 4]; } if ( res ) cur_len = res->len; if (res == NULL) { if ((res = malloc( sizeof(struct ws_frame) + cur_len + len)) == NULL) { rc = PAHO_MEMORY_ERROR; goto exit; } res->pos = 0u; } else { if ((res = realloc( res, sizeof(struct ws_frame) + cur_len + len )) == NULL) { rc = PAHO_MEMORY_ERROR; goto exit; } } if (in_frames && in_frames->first) in_frames->first->content = res; /* realloc moves the data */ memcpy( (unsigned char *)res + sizeof(struct ws_frame) + cur_len, b, len ); res->len = cur_len + len; WebSocket_getRawSocketData(net, 0u, &len, &rcs); if (rcs == SOCKET_ERROR) { rc = rcs; goto exit; } } if ( opcode == WebSocket_OP_PING || opcode == WebSocket_OP_PONG ) { /* respond to a "ping" with a "pong" */ if ( opcode == WebSocket_OP_PING ) WebSocket_pong( net, (char *)res + sizeof(struct ws_frame), res->len ); /* discard message */ free( res ); res = NULL; } else if ( opcode == WebSocket_OP_CLOSE ) { /* server end closed websocket connection */ free( res ); WebSocket_close( net, WebSocket_CLOSE_GOING_AWAY, NULL ); rc = SOCKET_ERROR; /* closes socket */ goto exit; } } while ( opcode == WebSocket_OP_PING || opcode == WebSocket_OP_PONG ); //} if (in_frames->count == 0) ListAppend( in_frames, res, sizeof(struct ws_frame) + res->len); *actual_len = res->len - res->pos; exit: if (rc == TCPSOCKET_INTERRUPTED) { WebSocket_rewindData(); } FUNC_EXIT_RC(rc); return rc; } /** * case-insensitive string search * * similar to @p strcase, but takes a maximum length * * @param[in] buf buffer to search * @param[in] str string to find * @param[in] len length of the buffer * * @retval !NULL location of string found * @retval NULL string not found */ const char *WebSocket_strcasefind(const char *buf, const char *str, size_t len) { const char *res = NULL; if ( buf && len > 0u && str ) { const size_t str_len = strlen( str ); while ( len >= str_len && !res ) { if ( strncasecmp( buf, str, str_len ) == 0 ) res = buf; ++buf; --len; } } return res; } /** * releases resources used by the websocket sub-system */ void WebSocket_terminate( void ) { FUNC_ENTRY; /* clean up and un-processed websocket frames */ if ( in_frames ) { struct ws_frame *f = ListDetachHead( in_frames ); while ( f ) { free( f ); f = ListDetachHead( in_frames ); } ListFree( in_frames ); in_frames = NULL; } if ( last_frame ) { free( last_frame ); last_frame = NULL; } if ( frame_buffer ) { free( frame_buffer ); frame_buffer = NULL; } frame_buffer_len = 0; frame_buffer_index = 0; frame_buffer_data_len = 0; Socket_outTerminate(); #if defined(OPENSSL) SSLSocket_terminate(); #endif FUNC_EXIT; } /** * handles the websocket upgrade response * * @param[in,out] net network connection to upgrade * * @retval SOCKET_ERROR failed to upgrade network connection * @retval TCPSOCKET_INTERRUPTED upgrade not complete, but not failed. Try again * @retval 1 socket upgraded to use websockets * * @see WebSocket_connect */ int WebSocket_upgrade( networkHandles *net ) { static const char *const ws_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; int rc = SOCKET_ERROR; FUNC_ENTRY; if ( net->websocket_key ) { SHA_CTX ctx; char ws_key[62u] = { 0 }; unsigned char sha_hash[SHA1_DIGEST_LENGTH]; size_t rcv = 0u; char *read_buf; /* calculate the expected websocket key, expected from server */ snprintf( ws_key, sizeof(ws_key), "%s%s", net->websocket_key, ws_guid ); SHA1_Init( &ctx ); SHA1_Update( &ctx, ws_key, strlen(ws_key)); SHA1_Final( sha_hash, &ctx ); Base64_encode( ws_key, sizeof(ws_key), sha_hash, SHA1_DIGEST_LENGTH ); read_buf = WebSocket_getRawSocketData( net, 12u, &rcv, &rc); if (rc == SOCKET_ERROR) goto exit; if ((read_buf == NULL) || rcv < 12u) { Log(TRACE_PROTOCOL, 1, "WebSocket upgrade read not complete %lu", rcv ); rc = TCPSOCKET_INTERRUPTED; goto exit; } if (strncmp( read_buf, "HTTP/1.1", 8u ) == 0) { if (strncmp( &read_buf[9], "101", 3u ) != 0) { Log(TRACE_PROTOCOL, 1, "WebSocket HTTP rc %.3s", &read_buf[9]); rc = SOCKET_ERROR; goto exit; } } if (strncmp( read_buf, "HTTP/1.1 101", 12u ) == 0) { const char *p; read_buf = WebSocket_getRawSocketData(net, 1024u, &rcv, &rc); if (rc == SOCKET_ERROR) goto exit; /* Did we read the whole response? */ if (read_buf && rcv > 4 && memcmp(&read_buf[rcv-4], "\r\n\r\n", 4) != 0) { Log(TRACE_PROTOCOL, -1, "WebSocket HTTP upgrade response read not complete %lu", rcv); rc = SOCKET_ERROR; goto exit; } /* check for upgrade */ p = WebSocket_strcasefind( read_buf, "Connection", rcv ); if ( p ) { const char *eol; eol = memchr( p, '\n', rcv-(read_buf-p) ); if ( eol ) p = WebSocket_strcasefind( p, "Upgrade", eol - p); else p = NULL; } /* check key hash */ if ( p ) p = WebSocket_strcasefind( read_buf, "sec-websocket-accept", rcv ); if ( p ) { const char *eol; eol = memchr( p, '\n', rcv-(read_buf-p) ); if ( eol ) { p = memchr( p, ':', eol-p ); if ( p ) { size_t hash_len = eol-p-1; while ( *p == ':' || *p == ' ' ) { ++p; --hash_len; } if ( strncmp( p, ws_key, hash_len ) != 0 ) p = NULL; } } else p = NULL; } if ( p ) { net->websocket = 1; Log(TRACE_PROTOCOL, 1, "WebSocket connection upgraded" ); rc = 1; } else { Log(TRACE_PROTOCOL, 1, "WebSocket failed to upgrade connection" ); rc = SOCKET_ERROR; } if ( net->websocket_key ) { free(net->websocket_key); net->websocket_key = NULL; } /* indicate that we done with the packet */ WebSocket_getRawSocketData( net, 0u, &rcv, &rc); } } exit: FUNC_EXIT_RC(rc); return rc; }