Loading...
Searching...
No Matches
coap_ws.c
Go to the documentation of this file.
/*
* coap_ws.c -- WebSockets functions for libcoap
*
* Copyright (C) 2023-2024 Olaf Bergmann <bergmann@tzi.org>
* Copyright (C) 2023-2024 Jon Shallow <supjps-libcoap@jpshallow.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*
* This file is part of the CoAP library libcoap. Please see README for terms
* of use.
*/
#include "coap3/coap_libcoap_build.h"
#if COAP_WS_SUPPORT
#include <stdio.h>
#include <ctype.h>
#ifdef _WIN32
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#endif
#define COAP_WS_RESPONSE \
"HTTP/1.1 101 Switching Protocols\r\n" \
"Upgrade: websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \
"Sec-WebSocket-Protocol: coap\r\n" \
"\r\n"
int
coap_ws_is_supported(void) {
return coap_tcp_is_supported();
}
int
coap_wss_is_supported(void) {
return coap_tls_is_supported();
}
static const char
basis_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int
coap_base64_encode_buffer(const uint8_t *string, size_t len, char *encoded,
const size_t max_encoded_len) {
size_t i;
char *p;
if ((((len + 2) / 3 * 4) + 1) > max_encoded_len) {
assert(0);
return 0;
}
p = encoded;
for (i = 0; i < len - 2; i += 3) {
*p++ = basis_64[(string[i] >> 2) & 0x3F];
*p++ = basis_64[((string[i] & 0x3) << 4) |
((int)(string[i + 1] & 0xF0) >> 4)];
*p++ = basis_64[((string[i + 1] & 0xF) << 2) |
((int)(string[i + 2] & 0xC0) >> 6)];
*p++ = basis_64[string[i + 2] & 0x3F];
}
if (i < len) {
*p++ = basis_64[(string[i] >> 2) & 0x3F];
if (i == (len - 1)) {
*p++ = basis_64[((string[i] & 0x3) << 4)];
*p++ = '=';
} else {
*p++ = basis_64[((string[i] & 0x3) << 4) |
((int)(string[i + 1] & 0xF0) >> 4)];
*p++ = basis_64[((string[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
*p++ = '\0';
return 1;
}
static int
coap_base64_decode_buffer(const char *bufcoded, size_t *len, uint8_t *bufplain,
const size_t max_decoded_len) {
size_t nbytesdecoded;
const uint8_t *bufin;
uint8_t *bufout;
size_t nprbytes;
static const uint8_t pr2six[256] = {
/* ASCII table */
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
bufin = (const uint8_t *)bufcoded;
while (pr2six[*(bufin++)] <= 63);
nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
nbytesdecoded = ((nprbytes + 3) / 4) * 3;
if ((nbytesdecoded - ((4 - nprbytes) & 3)) > max_decoded_len)
return 0;
bufout = bufplain;
bufin = (const uint8_t *)bufcoded;
while (nprbytes > 4) {
*(bufout++) =
(uint8_t)(pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
*(bufout++) =
(uint8_t)(pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
*(bufout++) =
(uint8_t)(pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
bufin += 4;
nprbytes -= 4;
}
/* Note: (nprbytes == 1) would be an error, so just ignore that case */
if (nprbytes > 1) {
*(bufout++) = (uint8_t)(pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
}
if (nprbytes > 2) {
*(bufout++) = (uint8_t)(pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
}
if (nprbytes > 3) {
*(bufout++) = (uint8_t)(pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
}
if (len)
*len = nbytesdecoded - ((4 - nprbytes) & 3);
return 1;
}
static void
coap_ws_log_header(const coap_session_t *session, const uint8_t *header) {
#if COAP_MAX_LOGGING_LEVEL < _COAP_LOG_DEBUG
(void)session;
(void)header;
#else /* COAP_MAX_LOGGING_LEVEL >= _COAP_LOG_DEBUG */
char buf[3*COAP_MAX_FS + 1];
int i;
ssize_t bytes_size;
int extra_hdr_len = 2;
bytes_size = header[1] & WS_B1_LEN_MASK;
if (bytes_size == 127) {
extra_hdr_len += 8;
} else if (bytes_size == 126) {
extra_hdr_len += 2;
}
if (header[1] & WS_B1_MASK_BIT) {
extra_hdr_len +=4;
}
for (i = 0; i < extra_hdr_len; i++) {
snprintf(&buf[i*3], 4, " %02x", header[i]);
}
coap_log_debug("* %s: ws: h recv %4d bytes\n",
coap_session_str(session), extra_hdr_len);
coap_log_debug("* %s: WS header:%s\n", coap_session_str(session), buf);
#endif /* COAP_MAX_LOGGING_LEVEL >= _COAP_LOG_DEBUG */
}
static void
coap_ws_log_key(const coap_session_t *session) {
char buf[3*16 + 1];
size_t i;
for (i = 0; i < sizeof(session->ws->key); i++) {
snprintf(&buf[i*3], 4, " %02x", session->ws->key[i]);
}
coap_log_debug("WS: key:%s\n", buf);
}
static void
coap_ws_mask_data(coap_session_t *session, uint8_t *data, size_t data_len) {
coap_ws_state_t *ws = session->ws;
size_t i;
for (i = 0; i < data_len; i++) {
data[i] ^= ws->mask_key[i%4];
}
}
ssize_t
coap_ws_write(coap_session_t *session, const uint8_t *data, size_t datalen) {
uint8_t ws_header[COAP_MAX_FS];
ssize_t hdr_len = 2;
ssize_t ret;
uint8_t *wdata;
/* If lower layer not yet up, return error */
if (!session->ws) {
session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
if (!session->ws) {
coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED);
return -1;
}
memset(session->ws, 0, sizeof(coap_ws_state_t));
}
if (!session->ws->up) {
coap_log_debug("WS: Layer not up\n");
return 0;
}
if (session->ws->sent_close)
return 0;
ws_header[0] = WS_B0_FIN_BIT | WS_OP_BINARY;
if (datalen <= 125) {
ws_header[1] = datalen & WS_B1_LEN_MASK;
} else if (datalen <= 0xffff) {
ws_header[1] = 126;
ws_header[2] = (datalen >> 8) & 0xff;
ws_header[3] = datalen & 0xff;
hdr_len += 2;
} else {
ws_header[1] = 127;
ws_header[2] = ((uint64_t)datalen >> 56) & 0xff;
ws_header[3] = ((uint64_t)datalen >> 48) & 0xff;
ws_header[4] = ((uint64_t)datalen >> 40) & 0xff;
ws_header[5] = ((uint64_t)datalen >> 32) & 0xff;
ws_header[6] = (datalen >> 24) & 0xff;
ws_header[7] = (datalen >> 16) & 0xff;
ws_header[8] = (datalen >> 8) & 0xff;
ws_header[9] = datalen & 0xff;
hdr_len += 8;
}
if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
/* Need to set the Mask bit, and set the masking key */
ws_header[1] |= WS_B1_MASK_BIT;
/* TODO Masking Key and mask provided data */
coap_prng_lkd(&ws_header[hdr_len], 4);
memcpy(session->ws->mask_key, &ws_header[hdr_len], 4);
hdr_len += 4;
}
coap_ws_log_header(session, ws_header);
wdata = coap_malloc_type(COAP_STRING, datalen + hdr_len);
if (!wdata) {
errno = ENOMEM;
return -1;
}
memcpy(wdata, ws_header, hdr_len);
memcpy(&wdata[hdr_len], data, datalen);
if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
/* Need to mask the data */
coap_ws_mask_data(session, &wdata[hdr_len], datalen);
}
ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, wdata, datalen + hdr_len);
coap_free_type(COAP_STRING, wdata);
if (ret < hdr_len) {
return ret;
}
coap_log_debug("* %s: ws h: sent %4zd bytes\n",
coap_session_str(session), hdr_len);
if (ret == (ssize_t)(datalen + hdr_len))
coap_log_debug("* %s: ws: sent %4zd bytes\n",
coap_session_str(session), ret - hdr_len);
else
coap_log_debug("* %s: ws: sent %4zd of %4zd bytes\n",
coap_session_str(session), ret, datalen - hdr_len);
return datalen;
}
static char *
coap_ws_split_rd_header(coap_session_t *session) {
char *cp = strchr((char *)session->ws->http_hdr, ' ');
if (!cp)
cp = strchr((char *)session->ws->http_hdr, '\t');
if (!cp)
return NULL;
*cp = '\000';
cp++;
while (isblank(*cp))
cp++;
return cp;
}
static int
coap_ws_rd_http_header_server(coap_session_t *session) {
coap_ws_state_t *ws = session->ws;
char *value;
if (!ws->seen_first) {
if (strcasecmp((char *)ws->http_hdr,
"GET /.well-known/coap HTTP/1.1") != 0) {
coap_log_info("WS: Invalid GET request %s\n", (char *)ws->http_hdr);
return 0;
}
ws->seen_first = 1;
return 1;
}
/* Process the individual header */
value = coap_ws_split_rd_header(session);
if (!value)
return 0;
if (strcasecmp((char *)ws->http_hdr, "Host:") == 0) {
if (ws->seen_host) {
coap_log_debug("WS: Duplicate Host: header\n");
return 0;
}
ws->seen_host = 1;
} else if (strcasecmp((char *)ws->http_hdr, "Upgrade:") == 0) {
if (ws->seen_upg) {
coap_log_debug("WS: Duplicate Upgrade: header\n");
return 0;
}
if (strcasecmp(value, "websocket") != 0) {
coap_log_debug("WS: Invalid Upgrade: header\n");
return 0;
}
ws->seen_upg = 1;
} else if (strcasecmp((char *)ws->http_hdr, "Connection:") == 0) {
if (ws->seen_conn) {
coap_log_debug("WS: Duplicate Connection: header\n");
return 0;
}
if (strcasecmp(value, "Upgrade") != 0 &&
strcasecmp(value, "keep-alive, Upgrade") != 0) {
coap_log_debug("WS: Invalid Connection: header\n");
return 0;
}
ws->seen_conn = 1;
} else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Key:") == 0) {
size_t len;
if (ws->seen_key) {
coap_log_debug("WS: Duplicate Sec-WebSocket-Key: header\n");
return 0;
}
if (!coap_base64_decode_buffer(value, &len, ws->key,
sizeof(ws->key)) ||
len != sizeof(ws->key)) {
coap_log_info("WS: Invalid Sec-WebSocket-Key: %s\n", value);
return 0;
}
coap_ws_log_key(session);
ws->seen_key = 1;
} else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) {
if (ws->seen_proto) {
coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n");
return 0;
}
if (strcasecmp(value, "coap") != 0) {
coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n");
return 0;
}
ws->seen_proto = 1;
} else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Version:") == 0) {
if (ws->seen_ver) {
coap_log_debug("WS: Duplicate Sec-WebSocket-Version: header\n");
return 0;
}
if (strcasecmp(value, "13") != 0) {
coap_log_debug("WS: Invalid Sec-WebSocket-Version: header\n");
return 0;
}
ws->seen_ver = 1;
}
return 1;
}
#define COAP_WS_KEY_EXT "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
static int
coap_ws_build_key_hash(coap_session_t *session, char *hash, size_t max_hash_len) {
char buf[28 + sizeof(COAP_WS_KEY_EXT)];
coap_bin_const_t info;
coap_bin_const_t *hashed = NULL;
if (max_hash_len < 29)
return 0;
if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
buf, sizeof(buf)))
return 0;
if (strlen(buf) >= 28)
return 0;
strcat(buf, COAP_WS_KEY_EXT);
info.s = (uint8_t *)buf;
info.length = strlen(buf);
if (!coap_crypto_hash(COSE_ALGORITHM_SHA_1, &info, &hashed))
return 0;
if (!coap_base64_encode_buffer(hashed->s, hashed->length,
hash, max_hash_len)) {
coap_delete_bin_const(hashed);
return 0;
}
coap_delete_bin_const(hashed);
return 1;
}
static int
coap_ws_rd_http_header_client(coap_session_t *session) {
coap_ws_state_t *ws = session->ws;
char *value;
if (!ws->seen_first) {
value = coap_ws_split_rd_header(session);
if (strcmp((char *)ws->http_hdr, "HTTP/1.1") != 0 ||
atoi(value) != 101) {
coap_log_info("WS: Invalid GET response %s\n", (char *)ws->http_hdr);
return 0;
}
ws->seen_first = 1;
return 1;
}
/* Process the individual header */
value = coap_ws_split_rd_header(session);
if (!value)
return 0;
if (strcasecmp((char *)ws->http_hdr, "Upgrade:") == 0) {
if (ws->seen_upg) {
coap_log_debug("WS: Duplicate Upgrade: header\n");
return 0;
}
if (strcasecmp(value, "websocket") != 0) {
coap_log_debug("WS: Invalid Upgrade: header\n");
return 0;
}
ws->seen_upg = 1;
} else if (strcasecmp((char *)ws->http_hdr, "Connection:") == 0) {
if (ws->seen_conn) {
coap_log_debug("WS: Duplicate Connection: header\n");
return 0;
}
if (strcasecmp(value, "Upgrade") != 0) {
coap_log_debug("WS: Invalid Connection: header\n");
return 0;
}
ws->seen_conn = 1;
} else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Accept:") == 0) {
char hash[30];
if (ws->seen_key) {
coap_log_debug("WS: Duplicate Sec-WebSocket-Accept: header\n");
return 0;
}
if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) {
return 0;
}
if (strcmp(hash, value) != 0) {
return 0;
}
ws->seen_key = 1;
} else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) {
if (ws->seen_proto) {
coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n");
return 0;
}
if (strcasecmp(value, "coap") != 0) {
coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n");
return 0;
}
ws->seen_proto = 1;
}
return 1;
}
/*
* Read in and parse WebSockets setup HTTP headers
*
* return 0 failure
* 1 success
*/
static int
coap_ws_rd_http_header(coap_session_t *session) {
coap_ws_state_t *ws = session->ws;
ssize_t bytes;
ssize_t rem;
char *cp;
while (!ws->up) {
/*
* Can only read in up to COAP_MAX_FS at a time in case there is
* some frame info that needs to be subsequently processed
*/
rem = ws->http_ofs > (sizeof(ws->http_hdr) - 1 - COAP_MAX_FS) ?
sizeof(ws->http_hdr) - ws->http_ofs : COAP_MAX_FS;
bytes = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
&ws->http_hdr[ws->http_ofs],
rem);
if (bytes < 0)
return 0;
if (bytes == 0)
return 1;
ws->http_ofs += (uint32_t)bytes;
ws->http_hdr[ws->http_ofs] = '\000';
/* Force at least one check */
cp = (char *)ws->http_hdr;
while (cp) {
cp = strchr((char *)ws->http_hdr, '\n');
if (cp) {
/* Whole header record in */
*cp = '\000';
if (cp != (char *)ws->http_hdr) {
if (cp[-1] == '\r')
cp[-1] = '\000';
}
coap_log_debug("WS: HTTP: %s\n", ws->http_hdr);
if (ws->http_hdr[0] != '\000') {
if (ws->state == COAP_SESSION_TYPE_SERVER) {
if (!coap_ws_rd_http_header_server(session)) {
return 0;
}
} else {
if (!coap_ws_rd_http_header_client(session)) {
return 0;
}
}
}
rem = ws->http_ofs - ((uint8_t *)cp + 1 - ws->http_hdr);
if (ws->http_hdr[0] == '\000') {
/* Found trailing empty header line */
if (ws->state == COAP_SESSION_TYPE_SERVER) {
if (!(ws->seen_first && ws->seen_host && ws->seen_upg &&
ws->seen_conn && ws->seen_key && ws->seen_proto &&
ws->seen_ver)) {
coap_log_info("WS: Missing protocol header(s)\n");
return 0;
}
} else {
if (!(ws->seen_first && ws->seen_upg && ws->seen_conn &&
ws->seen_key && ws->seen_proto)) {
coap_log_info("WS: Missing protocol header(s)\n");
return 0;
}
}
ws->up = 1;
ws->hdr_ofs = (int)rem;
if (rem > 0)
memcpy(ws->rd_header, cp + 1, rem);
return 1;
}
ws->http_ofs = (uint32_t)rem;
memmove(ws->http_hdr, cp + 1, rem);
ws->http_hdr[ws->http_ofs] = '\000';
}
}
}
return 1;
}
/*
* return >=0 Number of bytes processed.
* -1 Error (error in errno).
*/
ssize_t
coap_ws_read(coap_session_t *session, uint8_t *data, size_t datalen) {
ssize_t bytes_size = 0;
ssize_t extra_hdr_len = 0;
ssize_t ret;
uint8_t op_code;
if (!session->ws) {
session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
if (!session->ws) {
coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED);
return -1;
}
memset(session->ws, 0, sizeof(coap_ws_state_t));
}
if (!session->ws->up) {
char buf[250];
if (!coap_ws_rd_http_header(session)) {
snprintf(buf, sizeof(buf), "HTTP/1.1 400 Invalid request\r\n\r\n");
coap_log_debug("WS: Response (Fail)\n%s", buf);
if (coap_netif_available(session)) {
session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
strlen(buf));
}
coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED);
return -1;
}
if (!session->ws->up)
return 0;
if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
char hash[30];
if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) {
return 0;
}
snprintf(buf, sizeof(buf), COAP_WS_RESPONSE, hash);
coap_log_debug("WS: Response\n%s", buf);
session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
strlen(buf));
coap_handle_event_lkd(session->context, COAP_EVENT_WS_CONNECTED, session);
coap_log_debug("WS: established\n");
} else {
/* TODO Process the GET response - error on failure */
coap_handle_event_lkd(session->context, COAP_EVENT_WS_CONNECTED, session);
}
session->sock.lfunc[COAP_LAYER_WS].l_establish(session);
if (session->ws->hdr_ofs == 0)
return 0;
}
/* Get WebSockets frame if not already completely in */
if (!session->ws->all_hdr_in) {
ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
&session->ws->rd_header[session->ws->hdr_ofs],
sizeof(session->ws->rd_header) - session->ws->hdr_ofs);
if (ret < 0)
return ret;
session->ws->hdr_ofs += (int)ret;
/* Enough of the header in ? */
if (session->ws->hdr_ofs < 2)
return 0;
if (session->ws->state == COAP_SESSION_TYPE_SERVER &&
!(session->ws->rd_header[1] & WS_B1_MASK_BIT)) {
/* Client has failed to mask the data */
session->ws->close_reason = 1002;
coap_ws_close(session);
return 0;
}
bytes_size = session->ws->rd_header[1] & WS_B1_LEN_MASK;
if (bytes_size == 127) {
extra_hdr_len += 8;
} else if (bytes_size == 126) {
extra_hdr_len += 2;
}
if (session->ws->rd_header[1] & WS_B1_MASK_BIT) {
memcpy(session->ws->mask_key, &session->ws->rd_header[2 + extra_hdr_len], 4);
extra_hdr_len +=4;
}
if (session->ws->hdr_ofs < 2 + extra_hdr_len)
return 0;
/* Header frame is fully in */
coap_ws_log_header(session, session->ws->rd_header);
op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
if (op_code != WS_OP_BINARY && op_code != WS_OP_CLOSE) {
/* Remote has failed to use correct opcode */
session->ws->close_reason = 1003;
coap_ws_close(session);
return 0;
}
if (op_code == WS_OP_CLOSE) {
coap_log_debug("WS: Close received\n");
session->ws->recv_close = 1;
coap_ws_close(session);
return 0;
}
session->ws->all_hdr_in = 1;
/* Get WebSockets frame size */
if (bytes_size == 127) {
bytes_size = ((uint64_t)session->ws->rd_header[2] << 56) +
((uint64_t)session->ws->rd_header[3] << 48) +
((uint64_t)session->ws->rd_header[4] << 40) +
((uint64_t)session->ws->rd_header[5] << 32) +
((uint64_t)session->ws->rd_header[6] << 24) +
((uint64_t)session->ws->rd_header[7] << 16) +
((uint64_t)session->ws->rd_header[8] << 8) +
session->ws->rd_header[9];
} else if (bytes_size == 126) {
bytes_size = ((uint16_t)session->ws->rd_header[2] << 8) +
session->ws->rd_header[3];
}
session->ws->data_size = bytes_size;
if ((size_t)bytes_size > datalen) {
coap_log_err("coap_ws_read: packet size bigger than provided data space"
" (%zu > %zu)\n", bytes_size, datalen);
coap_handle_event_lkd(session->context, COAP_EVENT_WS_PACKET_SIZE, session);
session->ws->close_reason = 1009;
coap_ws_close(session);
return 0;
}
coap_log_debug("* %s: Packet size %zu\n", coap_session_str(session),
bytes_size);
/* Handle any data read in as a part of the header */
ret = session->ws->hdr_ofs - 2 - extra_hdr_len;
if (ret > 0) {
assert(2 + extra_hdr_len < (ssize_t)sizeof(session->ws->rd_header));
/* data in latter part of header */
if (ret <= bytes_size) {
/* copy across all the available data */
memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], ret);
session->ws->data_ofs = ret;
if (ret == bytes_size) {
if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
/* Need to unmask the data */
coap_ws_mask_data(session, data, bytes_size);
}
session->ws->all_hdr_in = 0;
session->ws->hdr_ofs = 0;
op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
if (op_code == WS_OP_CLOSE) {
session->ws->close_reason = (data[0] << 8) + data[1];
coap_log_debug("* %s: WS: Close received (%u)\n",
coap_session_str(session),
session->ws->close_reason);
session->ws->recv_close = 1;
if (!session->ws->sent_close)
coap_ws_close(session);
return 0;
}
return bytes_size;
}
} else {
/* more information in header than given data size */
memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], bytes_size);
session->ws->data_ofs = bytes_size;
if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
/* Need to unmask the data */
coap_ws_mask_data(session, data, bytes_size);
}
/* set up partial header for the next read */
memmove(session->ws->rd_header,
&session->ws->rd_header[2 + extra_hdr_len + bytes_size],
ret - bytes_size);
session->ws->all_hdr_in = 0;
session->ws->hdr_ofs = (int)(ret - bytes_size);
return bytes_size;
}
} else {
session->ws->data_ofs = 0;
}
}
/* Get in (remaining) data */
ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
&data[session->ws->data_ofs],
session->ws->data_size - session->ws->data_ofs);
if (ret <= 0)
return ret;
session->ws->data_ofs += ret;
if (session->ws->data_ofs == session->ws->data_size) {
if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
/* Need to unmask the data */
coap_ws_mask_data(session, data, session->ws->data_size);
}
session->ws->all_hdr_in = 0;
session->ws->hdr_ofs = 0;
session->ws->data_ofs = 0;
coap_log_debug("* %s: ws: recv %4zd bytes\n",
coap_session_str(session), session->ws->data_size);
return session->ws->data_size;
}
/* Need to get in all of the data */
coap_log_debug("* %s: Waiting Packet size %zu (got %zu)\n", coap_session_str(session),
session->ws->data_size, session->ws->data_ofs);
return 0;
}
#define COAP_WS_REQUEST \
"GET /.well-known/coap HTTP/1.1\r\n" \
"Host: %s\r\n" \
"Upgrade: websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Key: %s\r\n" \
"Sec-WebSocket-Protocol: coap\r\n" \
"Sec-WebSocket-Version: 13\r\n" \
"\r\n"
void
coap_ws_establish(coap_session_t *session) {
if (!session->ws) {
session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
if (!session->ws) {
coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED);
return;
}
memset(session->ws, 0, sizeof(coap_ws_state_t));
}
if (session->type == COAP_SESSION_TYPE_CLIENT) {
char buf[270];
char base64[28];
char host[80];
int port = 0;
session->ws->state = COAP_SESSION_TYPE_CLIENT;
if (!session->ws_host) {
coap_log_err("WS Host not defined\n");
coap_session_disconnected_lkd(session, COAP_NACK_WS_LAYER_FAILED);
return;
}
coap_prng_lkd(session->ws->key, sizeof(session->ws->key));
coap_ws_log_key(session);
if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
base64, sizeof(base64)))
return;
if (session->proto == COAP_PROTO_WS &&
coap_address_get_port(&session->addr_info.remote) != 80) {
port = coap_address_get_port(&session->addr_info.remote);
} else if (session->proto == COAP_PROTO_WSS &&
coap_address_get_port(&session->addr_info.remote) != 443) {
port = coap_address_get_port(&session->addr_info.remote);
}
if (strchr((const char *)session->ws_host->s, ':')) {
if (port) {
snprintf(host, sizeof(host), "[%s]:%d", session->ws_host->s, port);
} else {
snprintf(host, sizeof(host), "[%s]", session->ws_host->s);
}
} else {
if (port) {
snprintf(host, sizeof(host), "%s:%d", session->ws_host->s, port);
} else {
snprintf(host, sizeof(host), "%s", session->ws_host->s);
}
}
snprintf(buf, sizeof(buf), COAP_WS_REQUEST, host, base64);
coap_log_debug("WS Request\n%s", buf);
session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
strlen(buf));
} else {
session->ws->state = COAP_SESSION_TYPE_SERVER;
}
}
void
coap_ws_close(coap_session_t *session) {
if (!coap_netif_available(session) ||
session->state == COAP_SESSION_STATE_NONE) {
session->sock.lfunc[COAP_LAYER_WS].l_close(session);
return;
}
if (session->ws && session->ws->up) {
#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
int count;
#endif /* ! WITH_LWIP && ! WITH_CONTIKI */
if (!session->ws->sent_close) {
size_t hdr_len = 2;
uint8_t ws_header[COAP_MAX_FS];
size_t ret;
ws_header[0] = WS_B0_FIN_BIT | WS_OP_CLOSE;
ws_header[1] = 2;
if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
/* Need to set the Mask bit, and set the masking key */
ws_header[1] |= WS_B1_MASK_BIT;
coap_prng_lkd(&ws_header[hdr_len], 4);
memcpy(session->ws->mask_key, &ws_header[hdr_len], 4);
hdr_len += 4;
}
coap_ws_log_header(session, ws_header);
if (session->ws->close_reason == 0)
session->ws->close_reason = 1000;
ws_header[hdr_len] = session->ws->close_reason >> 8;
ws_header[hdr_len+1] = session->ws->close_reason & 0xff;
if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
coap_ws_mask_data(session, &ws_header[hdr_len], 2);
}
session->ws->sent_close = 1;
coap_log_debug("* %s: WS: Close sent (%u)\n",
coap_session_str(session),
session->ws->close_reason);
ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, ws_header, hdr_len+2);
if (ret != hdr_len+2) {
return;
}
}
#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
count = 5;
while (!session->ws->recv_close && count > 0 && coap_netif_available(session)) {
uint8_t buf[100];
fd_set readfds;
int result;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(session->sock.fd, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 1000;
result = select((int)(session->sock.fd+1), &readfds, NULL, NULL, &tv);
if (result < 0) {
break;
} else if (result > 0) {
coap_ws_read(session, buf, sizeof(buf));
}
count --;
}
#endif /* ! WITH_LWIP && ! WITH_CONTIKI */
coap_handle_event_lkd(session->context, COAP_EVENT_WS_CLOSED, session);
}
session->sock.lfunc[COAP_LAYER_WS].l_close(session);
}
int
coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host) {
if (!session | !ws_host)
return 0;
session->ws_host = coap_new_str_const(ws_host->s, ws_host->length);
if (!session->ws_host)
return 0;
return 1;
}
#else /* !COAP_WS_SUPPORT */
int
coap_ws_is_supported(void) {
return 0;
}
int
coap_wss_is_supported(void) {
return 0;
}
int
coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host) {
(void)session;
(void)ws_host;
return 0;
}
#endif /* !COAP_WS_SUPPORT */
coap_address_get_port
uint16_t coap_address_get_port(const coap_address_t *addr)
Returns the port from addr in host byte order.
Definition coap_address.c:44
COAP_NACK_WS_LAYER_FAILED
@ COAP_NACK_WS_LAYER_FAILED
Definition coap_io.h:70
COAP_LAYER_WS
@ COAP_LAYER_WS
Definition coap_layers_internal.h:24
coap_libcoap_build.h
Library specific build wrapper for coap_internal.h.
COAP_STRING
@ COAP_STRING
Definition coap_mem.h:39
coap_malloc_type
void * coap_malloc_type(coap_memory_tag_t type, size_t size)
Allocates a chunk of size bytes and returns a pointer to the newly allocated memory.
coap_free_type
void coap_free_type(coap_memory_tag_t type, void *p)
Releases the memory that was allocated by coap_malloc_type().
coap_prng_lkd
int coap_prng_lkd(void *buf, size_t len)
Fills buf with len random bytes using the default pseudo random number generator.
Definition coap_prng.c:178
coap_handle_event_lkd
int coap_handle_event_lkd(coap_context_t *context, coap_event_t event, coap_session_t *session)
Invokes the event handler of context for the given event and data.
Definition coap_net.c:4358
coap_crypto_hash
int coap_crypto_hash(cose_alg_t alg, const coap_bin_const_t *data, coap_bin_const_t **hash)
Create a hash of the provided data.
COAP_EVENT_WS_CONNECTED
@ COAP_EVENT_WS_CONNECTED
Triggered when the WebSockets layer is up.
Definition coap_event.h:125
COAP_EVENT_WS_CLOSED
@ COAP_EVENT_WS_CLOSED
Triggered when the WebSockets layer is closed.
Definition coap_event.h:127
COAP_EVENT_WS_PACKET_SIZE
@ COAP_EVENT_WS_PACKET_SIZE
Triggered when there is an oversize WebSockets packet.
Definition coap_event.h:123
coap_log_debug
#define coap_log_debug(...)
Definition coap_debug.h:120
coap_session_str
const char * coap_session_str(const coap_session_t *session)
Get session description.
Definition coap_session.c:2234
coap_log_info
#define coap_log_info(...)
Definition coap_debug.h:108
coap_log_err
#define coap_log_err(...)
Definition coap_debug.h:96
coap_netif_available
int coap_netif_available(coap_session_t *session)
Function interface to check whether netif for session is still available.
Definition coap_netif.c:25
COSE_ALGORITHM_SHA_1
@ COSE_ALGORITHM_SHA_1
Definition oscore_cose.h:136
COAP_PROTO_WS
@ COAP_PROTO_WS
Definition coap_pdu.h:318
COAP_PROTO_WSS
@ COAP_PROTO_WSS
Definition coap_pdu.h:319
coap_session_disconnected_lkd
void coap_session_disconnected_lkd(coap_session_t *session, coap_nack_reason_t reason)
Notify session that it has failed.
Definition coap_session.c:911
COAP_SESSION_TYPE_SERVER
@ COAP_SESSION_TYPE_SERVER
server-side
Definition coap_session.h:47
COAP_SESSION_TYPE_CLIENT
@ COAP_SESSION_TYPE_CLIENT
client-side
Definition coap_session.h:46
COAP_SESSION_STATE_NONE
@ COAP_SESSION_STATE_NONE
Definition coap_session.h:56
coap_delete_bin_const
void coap_delete_bin_const(coap_bin_const_t *s)
Deletes the given const binary data and releases any memory allocated.
Definition coap_str.c:120
coap_new_str_const
coap_str_const_t * coap_new_str_const(const uint8_t *data, size_t size)
Returns a new const string object with at least size+1 bytes storage allocated, and the provided data...
Definition coap_str.c:51
coap_tcp_is_supported
int coap_tcp_is_supported(void)
Check whether TCP is available.
Definition coap_tcp.c:20
coap_tls_is_supported
int coap_tls_is_supported(void)
Check whether TLS is available.
Definition coap_notls.c:41
coap_ws_is_supported
int coap_ws_is_supported(void)
Check whether WebSockets is available.
Definition coap_ws.c:933
coap_wss_is_supported
int coap_wss_is_supported(void)
Check whether Secure WebSockets is available.
Definition coap_ws.c:938
coap_ws_establish
void coap_ws_establish(coap_session_t *session)
Layer function interface for layer below WebSockets accept/connect being established.
WS_B0_FIN_BIT
#define WS_B0_FIN_BIT
Definition coap_ws_internal.h:84
coap_ws_write
ssize_t coap_ws_write(coap_session_t *session, const uint8_t *data, size_t datalen)
Function interface for websockets data transmission.
WS_B1_MASK_BIT
#define WS_B1_MASK_BIT
Definition coap_ws_internal.h:88
WS_B1_LEN_MASK
#define WS_B1_LEN_MASK
Definition coap_ws_internal.h:89
coap_ws_close
void coap_ws_close(coap_session_t *session)
Layer function interface for WebSockets close for a session.
COAP_MAX_FS
#define COAP_MAX_FS
Definition coap_ws_internal.h:32
WS_B0_OP_MASK
#define WS_B0_OP_MASK
Definition coap_ws_internal.h:86
coap_ws_read
ssize_t coap_ws_read(coap_session_t *session, uint8_t *data, size_t datalen)
Function interface for websockets data receiving.
WS_OP_CLOSE
@ WS_OP_CLOSE
Definition coap_ws_internal.h:95
WS_OP_BINARY
@ WS_OP_BINARY
Definition coap_ws_internal.h:94
coap_ws_set_host_request
int coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host)
Set the host for the HTTP Host: Header in the WebSockets Request.
Definition coap_ws.c:943
coap_addr_tuple_t::remote
coap_address_t remote
remote address and port
Definition coap_io.h:56
coap_bin_const_t
CoAP binary data definition with const data.
Definition coap_str.h:64
coap_bin_const_t::length
size_t length
length of binary data
Definition coap_str.h:65
coap_bin_const_t::s
const uint8_t * s
read-only binary data
Definition coap_str.h:66
coap_layer_func_t::l_read
coap_layer_read_t l_read
Definition coap_layers_internal.h:107
coap_layer_func_t::l_write
coap_layer_write_t l_write
Definition coap_layers_internal.h:108
coap_layer_func_t::l_establish
coap_layer_establish_t l_establish
Definition coap_layers_internal.h:109
coap_layer_func_t::l_close
coap_layer_close_t l_close
Definition coap_layers_internal.h:110
coap_session_t
Abstraction of virtual session that can be attached to coap_context_t (client) or coap_endpoint_t (se...
Definition coap_session_internal.h:68
coap_session_t::sock
coap_socket_t sock
socket object for the session, if any
Definition coap_session_internal.h:81
coap_session_t::state
coap_session_state_t state
current state of relationship with peer
Definition coap_session_internal.h:71
coap_session_t::addr_info
coap_addr_tuple_t addr_info
remote/local address info
Definition coap_session_internal.h:79
coap_session_t::proto
coap_proto_t proto
protocol used
Definition coap_session_internal.h:69
coap_session_t::type
coap_session_type_t type
client or server side socket
Definition coap_session_internal.h:70
coap_session_t::context
coap_context_t * context
session's context
Definition coap_session_internal.h:86
coap_socket_t::lfunc
coap_layer_func_t lfunc[COAP_LAYER_LAST]
Layer functions to use.
Definition coap_io_internal.h:68
coap_socket_t::fd
coap_fd_t fd
Definition coap_io_internal.h:57
coap_str_const_t
CoAP string data definition with const data.
Definition coap_str.h:46
coap_str_const_t::s
const uint8_t * s
read-only string data
Definition coap_str.h:48
coap_str_const_t::length
size_t length
length of string
Definition coap_str.h:47
coap_ws_state_t
WebSockets session state.
Definition coap_ws_internal.h:37
coap_ws_state_t::up
uint8_t up
WebSockets established.
Definition coap_ws_internal.h:39
coap_ws_state_t::key
uint8_t key[16]
Random, but agreed key value.
Definition coap_ws_internal.h:58
coap_ws_state_t::seen_host
uint8_t seen_host
Seen Host: HTTP header (server).
Definition coap_ws_internal.h:41
coap_ws_state_t::seen_ver
uint8_t seen_ver
Seen version: HTTP header (server).
Definition coap_ws_internal.h:46
coap_ws_state_t::seen_key
uint8_t seen_key
Seen Key: HTTP header.
Definition coap_ws_internal.h:44
coap_ws_state_t::http_hdr
uint8_t http_hdr[160]
(Partial) HTTP header
Definition coap_ws_internal.h:55
coap_ws_state_t::seen_conn
uint8_t seen_conn
Seen Connection: HTTP header.
Definition coap_ws_internal.h:43
coap_ws_state_t::seen_upg
uint8_t seen_upg
Seen Upgrade: HTTP header.
Definition coap_ws_internal.h:42
coap_ws_state_t::http_ofs
uint32_t http_ofs
Current offset into http_hdr.
Definition coap_ws_internal.h:54
coap_ws_state_t::hdr_ofs
int hdr_ofs
Current offset into rd_header.
Definition coap_ws_internal.h:51
coap_ws_state_t::state
coap_session_type_t state
Client or Server.
Definition coap_ws_internal.h:38
coap_ws_state_t::rd_header
uint8_t rd_header[COAP_MAX_FS]
(Partial) frame
Definition coap_ws_internal.h:52
coap_ws_state_t::seen_proto
uint8_t seen_proto
Seen Protocol: HTTP header.
Definition coap_ws_internal.h:45
coap_ws_state_t::mask_key
uint8_t mask_key[4]
Masking key.
Definition coap_ws_internal.h:53
coap_ws_state_t::seen_first
uint8_t seen_first
Seen first request/response HTTP header.
Definition coap_ws_internal.h:40