1 /*
2 * coap_ws.c -- WebSockets functions for libcoap
3 *
4 * Copyright (C) 2023 Olaf Bergmann <bergmann@tzi.org>
5 * Copyright (C) 2023 Jon Shallow <supjps-libcoap@jpshallow.com>
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 *
9 * This file is part of the CoAP library libcoap. Please see README for terms
10 * of use.
11 */
12
13 /**
14 * @file coap_ws.c
15 * @brief CoAP WebSocket handling functions
16 */
17
18 #include "coap3/coap_internal.h"
19
20 #if COAP_WS_SUPPORT
21 #include <stdio.h>
22 #include <ctype.h>
23
24 #ifdef _WIN32
25 #define strcasecmp _stricmp
26 #define strncasecmp _strnicmp
27 #endif
28
29 #define COAP_WS_RESPONSE \
30 "HTTP/1.1 101 Switching Protocols\r\n" \
31 "Upgrade: websocket\r\n" \
32 "Connection: Upgrade\r\n" \
33 "Sec-WebSocket-Accept: %s\r\n" \
34 "Sec-WebSocket-Protocol: coap\r\n" \
35 "\r\n"
36
37 int
coap_ws_is_supported(void)38 coap_ws_is_supported(void) {
39 #if defined(COAP_WITH_LIBOPENSSL) || defined(COAP_WITH_LIBGNUTLS) || defined(COAP_WITH_LIBMBEDTLS)
40 /* Have SHA1 hash support */
41 return coap_tcp_is_supported();
42 #else /* !COAP_WITH_LIBOPENSSL && !COAP_WITH_LIBGNUTLS && !COAP_WITH_LIBMBEDTLS */
43 return 0;
44 #endif /* !COAP_WITH_LIBOPENSSL && !COAP_WITH_LIBGNUTLS && !COAP_WITH_LIBMBEDTLS */
45 }
46
47 int
coap_wss_is_supported(void)48 coap_wss_is_supported(void) {
49 return coap_tls_is_supported();
50 }
51
52 static const char
53 basis_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
54
55 static int
coap_base64_encode_buffer(const uint8_t * string,size_t len,char * encoded,const size_t max_encoded_len)56 coap_base64_encode_buffer(const uint8_t *string, size_t len, char *encoded,
57 const size_t max_encoded_len) {
58 size_t i;
59 char *p;
60
61 if ((((len + 2) / 3 * 4) + 1) > max_encoded_len) {
62 assert(0);
63 return 0;
64 }
65
66 p = encoded;
67 for (i = 0; i < len - 2; i += 3) {
68 *p++ = basis_64[(string[i] >> 2) & 0x3F];
69 *p++ = basis_64[((string[i] & 0x3) << 4) |
70 ((int)(string[i + 1] & 0xF0) >> 4)];
71 *p++ = basis_64[((string[i + 1] & 0xF) << 2) |
72 ((int)(string[i + 2] & 0xC0) >> 6)];
73 *p++ = basis_64[string[i + 2] & 0x3F];
74 }
75 if (i < len) {
76 *p++ = basis_64[(string[i] >> 2) & 0x3F];
77 if (i == (len - 1)) {
78 *p++ = basis_64[((string[i] & 0x3) << 4)];
79 *p++ = '=';
80 } else {
81 *p++ = basis_64[((string[i] & 0x3) << 4) |
82 ((int)(string[i + 1] & 0xF0) >> 4)];
83 *p++ = basis_64[((string[i + 1] & 0xF) << 2)];
84 }
85 *p++ = '=';
86 }
87
88 *p++ = '\0';
89 return 1;
90 }
91
92 static int
coap_base64_decode_buffer(const char * bufcoded,size_t * len,uint8_t * bufplain,const size_t max_decoded_len)93 coap_base64_decode_buffer(const char *bufcoded, size_t *len, uint8_t *bufplain,
94 const size_t max_decoded_len) {
95 size_t nbytesdecoded;
96 const uint8_t *bufin;
97 uint8_t *bufout;
98 size_t nprbytes;
99 static const uint8_t pr2six[256] = {
100 /* ASCII table */
101 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
102 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
103 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
104 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
105 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
106 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
107 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
108 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
109 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
110 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
111 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
112 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
113 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
114 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
115 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
116 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
117 };
118
119 bufin = (const uint8_t *)bufcoded;
120 while (pr2six[*(bufin++)] <= 63);
121 nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
122 nbytesdecoded = ((nprbytes + 3) / 4) * 3;
123 if ((nbytesdecoded - ((4 - nprbytes) & 3)) > max_decoded_len)
124 return 0;
125
126 bufout = bufplain;
127 bufin = (const uint8_t *)bufcoded;
128
129 while (nprbytes > 4) {
130 *(bufout++) =
131 (uint8_t)(pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
132 *(bufout++) =
133 (uint8_t)(pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
134 *(bufout++) =
135 (uint8_t)(pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
136 bufin += 4;
137 nprbytes -= 4;
138 }
139
140 /* Note: (nprbytes == 1) would be an error, so just ignore that case */
141 if (nprbytes > 1) {
142 *(bufout++) = (uint8_t)(pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
143 }
144 if (nprbytes > 2) {
145 *(bufout++) = (uint8_t)(pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
146 }
147 if (nprbytes > 3) {
148 *(bufout++) = (uint8_t)(pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
149 }
150
151 if (len)
152 *len = nbytesdecoded - ((4 - nprbytes) & 3);
153 return 1;
154 }
155
156 static void
coap_ws_log_header(const coap_session_t * session,const uint8_t * header)157 coap_ws_log_header(const coap_session_t *session, const uint8_t *header) {
158 #if COAP_MAX_LOGGING_LEVEL < _COAP_LOG_DEBUG
159 (void)session;
160 (void)header;
161 #else /* COAP_MAX_LOGGING_LEVEL >= _COAP_LOG_DEBUG */
162 char buf[3*COAP_MAX_FS + 1];
163 int i;
164 ssize_t bytes_size;
165 int extra_hdr_len = 2;
166
167 bytes_size = header[1] & WS_B1_LEN_MASK;
168 if (bytes_size == 127) {
169 extra_hdr_len += 8;
170 } else if (bytes_size == 126) {
171 extra_hdr_len += 2;
172 }
173 if (header[1] & WS_B1_MASK_BIT) {
174 extra_hdr_len +=4;
175 }
176 for (i = 0; i < extra_hdr_len; i++) {
177 snprintf(&buf[i*3], 4, " %02x", header[i]);
178 }
179 coap_log_debug("* %s: WS header:%s\n", coap_session_str(session), buf);
180 #endif /* COAP_MAX_LOGGING_LEVEL >= _COAP_LOG_DEBUG */
181 }
182
183 static void
coap_ws_log_key(const coap_session_t * session)184 coap_ws_log_key(const coap_session_t *session) {
185 char buf[3*16 + 1];
186 size_t i;
187
188 for (i = 0; i < sizeof(session->ws->key); i++) {
189 snprintf(&buf[i*3], 4, " %02x", session->ws->key[i]);
190 }
191 coap_log_debug("WS: key:%s\n", buf);
192 }
193
194 static void
coap_ws_mask_data(coap_session_t * session,uint8_t * data,size_t data_len)195 coap_ws_mask_data(coap_session_t *session, uint8_t *data, size_t data_len) {
196 coap_ws_state_t *ws = session->ws;
197 size_t i;
198
199 for (i = 0; i < data_len; i++) {
200 data[i] ^= ws->mask_key[i%4];
201 }
202 }
203
204 ssize_t
coap_ws_write(coap_session_t * session,const uint8_t * data,size_t datalen)205 coap_ws_write(coap_session_t *session, const uint8_t *data, size_t datalen) {
206 uint8_t ws_header[COAP_MAX_FS];
207 ssize_t hdr_len = 2;
208 ssize_t ret;
209
210 /* If lower layer not yet up, return error */
211 if (!session->ws) {
212 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
213 if (!session->ws) {
214 coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
215 return -1;
216 }
217 memset(session->ws, 0, sizeof(coap_ws_state_t));
218 }
219
220 if (!session->ws->up) {
221 coap_log_debug("WS: Layer not up\n");
222 return 0;
223 }
224 if (session->ws->sent_close)
225 return 0;
226
227 ws_header[0] = WS_B0_FIN_BIT | WS_OP_BINARY;
228 if (datalen <= 125) {
229 ws_header[1] = datalen & WS_B1_LEN_MASK;
230 } else if (datalen <= 0xffff) {
231 ws_header[1] = 126;
232 ws_header[2] = (datalen >> 8) & 0xff;
233 ws_header[3] = datalen & 0xff;
234 hdr_len += 2;
235 } else {
236 ws_header[1] = 127;
237 ws_header[2] = ((uint64_t)datalen >> 56) & 0xff;
238 ws_header[3] = ((uint64_t)datalen >> 48) & 0xff;
239 ws_header[4] = ((uint64_t)datalen >> 40) & 0xff;
240 ws_header[5] = ((uint64_t)datalen >> 32) & 0xff;
241 ws_header[6] = (datalen >> 24) & 0xff;
242 ws_header[7] = (datalen >> 16) & 0xff;
243 ws_header[8] = (datalen >> 8) & 0xff;
244 ws_header[9] = datalen & 0xff;
245 hdr_len += 8;
246 }
247 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
248 /* Need to set the Mask bit, and set the masking key */
249 ws_header[1] |= WS_B1_MASK_BIT;
250 /* TODO Masking Key and mask provided data */
251 coap_prng(&ws_header[hdr_len], 4);
252 memcpy(session->ws->mask_key, &ws_header[hdr_len], 4);
253 hdr_len += 4;
254 }
255 coap_ws_log_header(session, ws_header);
256 ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, ws_header, hdr_len);
257 if (ret != hdr_len) {
258 return -1;
259 }
260 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
261 /* Need to mask the data */
262 uint8_t *wdata = coap_malloc_type(COAP_STRING, datalen);
263
264 if (!wdata) {
265 errno = ENOMEM;
266 return -1;
267 }
268 session->ws->data_size = datalen;
269 memcpy(wdata, data, datalen);
270 coap_ws_mask_data(session, wdata, datalen);
271 ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, wdata, datalen);
272 coap_free_type(COAP_STRING, wdata);
273 } else {
274 ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, data, datalen);
275 }
276 if (ret <= 0) {
277 return ret;
278 }
279 if (ret == (ssize_t)datalen)
280 coap_log_debug("* %s: ws: sent %4zd bytes\n",
281 coap_session_str(session), ret);
282 else
283 coap_log_debug("* %s: ws: sent %4zd of %4zd bytes\n",
284 coap_session_str(session), ret, datalen);
285 return datalen;
286 }
287
288 static char *
coap_ws_split_rd_header(coap_session_t * session)289 coap_ws_split_rd_header(coap_session_t *session) {
290 char *cp = strchr((char *)session->ws->http_hdr, ' ');
291
292 if (!cp)
293 cp = strchr((char *)session->ws->http_hdr, '\t');
294
295 if (!cp)
296 return NULL;
297
298 *cp = '\000';
299 cp++;
300 while (isblank(*cp))
301 cp++;
302 return cp;
303 }
304
305 static int
coap_ws_rd_http_header_server(coap_session_t * session)306 coap_ws_rd_http_header_server(coap_session_t *session) {
307 coap_ws_state_t *ws = session->ws;
308 char *value;
309
310 if (!ws->seen_first) {
311 if (strcasecmp((char *)ws->http_hdr,
312 "GET /.well-known/coap HTTP/1.1") != 0) {
313 coap_log_info("WS: Invalid GET request %s\n", (char *)ws->http_hdr);
314 return 0;
315 }
316 ws->seen_first = 1;
317 return 1;
318 }
319 /* Process the individual header */
320 value = coap_ws_split_rd_header(session);
321 if (!value)
322 return 0;
323
324 if (strcasecmp((char *)ws->http_hdr, "Host:") == 0) {
325 if (ws->seen_host) {
326 coap_log_debug("WS: Duplicate Host: header\n");
327 return 0;
328 }
329 ws->seen_host = 1;
330 } else if (strcasecmp((char *)ws->http_hdr, "Upgrade:") == 0) {
331 if (ws->seen_upg) {
332 coap_log_debug("WS: Duplicate Upgrade: header\n");
333 return 0;
334 }
335 if (strcasecmp(value, "websocket") != 0) {
336 coap_log_debug("WS: Invalid Upgrade: header\n");
337 return 0;
338 }
339 ws->seen_upg = 1;
340 } else if (strcasecmp((char *)ws->http_hdr, "Connection:") == 0) {
341 if (ws->seen_conn) {
342 coap_log_debug("WS: Duplicate Connection: header\n");
343 return 0;
344 }
345 if (strcasecmp(value, "Upgrade") != 0) {
346 coap_log_debug("WS: Invalid Connection: header\n");
347 return 0;
348 }
349 ws->seen_conn = 1;
350 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Key:") == 0) {
351 size_t len;
352
353 if (ws->seen_key) {
354 coap_log_debug("WS: Duplicate Sec-WebSocket-Key: header\n");
355 return 0;
356 }
357 if (!coap_base64_decode_buffer(value, &len, ws->key,
358 sizeof(ws->key)) ||
359 len != sizeof(ws->key)) {
360 coap_log_info("WS: Invalid Sec-WebSocket-Key: %s\n", value);
361 return 0;
362 }
363 coap_ws_log_key(session);
364 ws->seen_key = 1;
365 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) {
366 if (ws->seen_proto) {
367 coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n");
368 return 0;
369 }
370 if (strcasecmp(value, "coap") != 0) {
371 coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n");
372 return 0;
373 }
374 ws->seen_proto = 1;
375 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Version:") == 0) {
376 if (ws->seen_ver) {
377 coap_log_debug("WS: Duplicate Sec-WebSocket-Version: header\n");
378 return 0;
379 }
380 if (strcasecmp(value, "13") != 0) {
381 coap_log_debug("WS: Invalid Sec-WebSocket-Version: header\n");
382 return 0;
383 }
384 ws->seen_ver = 1;
385 }
386 return 1;
387 }
388
389 #define COAP_WS_KEY_EXT "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
390
391 static int
coap_ws_build_key_hash(coap_session_t * session,char * hash,size_t max_hash_len)392 coap_ws_build_key_hash(coap_session_t *session, char *hash, size_t max_hash_len) {
393 char buf[28 + sizeof(COAP_WS_KEY_EXT)];
394 coap_bin_const_t info;
395 coap_bin_const_t *hashed = NULL;
396
397 if (max_hash_len < 29)
398 return 0;
399 if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
400 buf, sizeof(buf)))
401 return 0;
402 if (strlen(buf) >= 28)
403 return 0;
404 strcat(buf, COAP_WS_KEY_EXT);
405 info.s = (uint8_t *)buf;
406 info.length = strlen(buf);
407 if (!coap_crypto_hash(COSE_ALGORITHM_SHA_1, &info, &hashed))
408 return 0;
409
410 if (!coap_base64_encode_buffer(hashed->s, hashed->length,
411 hash, max_hash_len)) {
412 coap_delete_bin_const(hashed);
413 return 0;
414 }
415 coap_delete_bin_const(hashed);
416 return 1;
417 }
418
419 static int
coap_ws_rd_http_header_client(coap_session_t * session)420 coap_ws_rd_http_header_client(coap_session_t *session) {
421 coap_ws_state_t *ws = session->ws;
422 char *value;
423
424 if (!ws->seen_first) {
425 value = coap_ws_split_rd_header(session);
426
427 if (strcmp((char *)ws->http_hdr, "HTTP/1.1") != 0 ||
428 atoi(value) != 101) {
429 coap_log_info("WS: Invalid GET response %s\n", (char *)ws->http_hdr);
430 return 0;
431 }
432 ws->seen_first = 1;
433 return 1;
434 }
435 /* Process the individual header */
436 value = coap_ws_split_rd_header(session);
437 if (!value)
438 return 0;
439
440 if (strcasecmp((char *)ws->http_hdr, "Upgrade:") == 0) {
441 if (ws->seen_upg) {
442 coap_log_debug("WS: Duplicate Upgrade: header\n");
443 return 0;
444 }
445 if (strcasecmp(value, "websocket") != 0) {
446 coap_log_debug("WS: Invalid Upgrade: header\n");
447 return 0;
448 }
449 ws->seen_upg = 1;
450 } else if (strcasecmp((char *)ws->http_hdr, "Connection:") == 0) {
451 if (ws->seen_conn) {
452 coap_log_debug("WS: Duplicate Connection: header\n");
453 return 0;
454 }
455 if (strcasecmp(value, "Upgrade") != 0) {
456 coap_log_debug("WS: Invalid Connection: header\n");
457 return 0;
458 }
459 ws->seen_conn = 1;
460 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Accept:") == 0) {
461 char hash[30];
462
463 if (ws->seen_key) {
464 coap_log_debug("WS: Duplicate Sec-WebSocket-Accept: header\n");
465 return 0;
466 }
467 if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) {
468 return 0;
469 }
470 if (strcmp(hash, value) != 0) {
471 return 0;
472 }
473 ws->seen_key = 1;
474 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) {
475 if (ws->seen_proto) {
476 coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n");
477 return 0;
478 }
479 if (strcasecmp(value, "coap") != 0) {
480 coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n");
481 return 0;
482 }
483 ws->seen_proto = 1;
484 }
485 return 1;
486 }
487
488 /*
489 * Read in and parse WebSockets setup HTTP headers
490 *
491 * return 0 failure
492 * 1 success
493 */
494 static int
coap_ws_rd_http_header(coap_session_t * session)495 coap_ws_rd_http_header(coap_session_t *session) {
496 coap_ws_state_t *ws = session->ws;
497 ssize_t bytes;
498 ssize_t rem;
499 char *cp;
500
501 while (!ws->up) {
502 /*
503 * Can only read in up to COAP_MAX_FS at a time in case there is
504 * some frame info that needs to be subsequently processed
505 */
506 rem = ws->http_ofs > (sizeof(ws->http_hdr) - 1 - COAP_MAX_FS) ?
507 sizeof(ws->http_hdr) - ws->http_ofs : COAP_MAX_FS;
508 bytes = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
509 &ws->http_hdr[ws->http_ofs],
510 rem);
511 if (bytes < 0)
512 return 0;
513 if (bytes == 0)
514 return 1;
515
516 ws->http_ofs += (uint32_t)bytes;
517 ws->http_hdr[ws->http_ofs] = '\000';
518 /* Force at least one check */
519 cp = (char *)ws->http_hdr;
520 while (cp) {
521 cp = strchr((char *)ws->http_hdr, '\n');
522 if (cp) {
523 /* Whole header record in */
524 *cp = '\000';
525 if (cp != (char *)ws->http_hdr) {
526 if (cp[-1] == '\r')
527 cp[-1] = '\000';
528 }
529
530 coap_log_debug("WS: HTTP: %s\n", ws->http_hdr);
531 if (ws->http_hdr[0] != '\000') {
532 if (ws->state == COAP_SESSION_TYPE_SERVER) {
533 if (!coap_ws_rd_http_header_server(session)) {
534 return 0;
535 }
536 } else {
537 if (!coap_ws_rd_http_header_client(session)) {
538 return 0;
539 }
540 }
541 }
542
543 rem = ws->http_ofs - ((uint8_t *)cp + 1 - ws->http_hdr);
544 if (ws->http_hdr[0] == '\000') {
545 /* Found trailing empty header line */
546 if (ws->state == COAP_SESSION_TYPE_SERVER) {
547 if (!(ws->seen_first && ws->seen_host && ws->seen_upg &&
548 ws->seen_conn && ws->seen_key && ws->seen_proto &&
549 ws->seen_ver)) {
550 coap_log_info("WS: Missing protocol header(s)\n");
551 return 0;
552 }
553 } else {
554 if (!(ws->seen_first && ws->seen_upg && ws->seen_conn &&
555 ws->seen_key && ws->seen_proto)) {
556 coap_log_info("WS: Missing protocol header(s)\n");
557 return 0;
558 }
559 }
560 ws->up = 1;
561 ws->hdr_ofs = (int)rem;
562 if (rem > 0)
563 memcpy(ws->rd_header, cp + 1, rem);
564 return 1;
565 }
566 ws->http_ofs = (uint32_t)rem;
567 memmove(ws->http_hdr, cp + 1, rem);
568 ws->http_hdr[ws->http_ofs] = '\000';
569 }
570 }
571 }
572 return 1;
573 }
574
575 /*
576 * return >=0 Number of bytes processed.
577 * -1 Error (error in errno).
578 */
579 ssize_t
coap_ws_read(coap_session_t * session,uint8_t * data,size_t datalen)580 coap_ws_read(coap_session_t *session, uint8_t *data, size_t datalen) {
581 ssize_t bytes_size = 0;
582 ssize_t extra_hdr_len = 0;
583 ssize_t ret;
584 uint8_t op_code;
585
586 if (!session->ws) {
587 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
588 if (!session->ws) {
589 coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
590 return -1;
591 }
592 memset(session->ws, 0, sizeof(coap_ws_state_t));
593 }
594
595 if (!session->ws->up) {
596 char buf[250];
597
598 if (!coap_ws_rd_http_header(session)) {
599 snprintf(buf, sizeof(buf), "HTTP/1.1 400 Invalid request\r\n\r\n");
600 coap_log_debug("WS: Response (Fail)\n%s", buf);
601 if (coap_netif_available(session)) {
602 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
603 strlen(buf));
604 }
605 coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
606 return -1;
607 }
608
609 if (!session->ws->up)
610 return 0;
611
612 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
613 char hash[30];
614
615 if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) {
616 return 0;
617 }
618 snprintf(buf, sizeof(buf), COAP_WS_RESPONSE, hash);
619 coap_log_debug("WS: Response\n%s", buf);
620 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
621 strlen(buf));
622
623 coap_handle_event(session->context, COAP_EVENT_WS_CONNECTED, session);
624 coap_log_debug("WS: established\n");
625 } else {
626 /* TODO Process the GET response - error on failure */
627
628 coap_handle_event(session->context, COAP_EVENT_WS_CONNECTED, session);
629 }
630 session->sock.lfunc[COAP_LAYER_WS].l_establish(session);
631 if (session->ws->hdr_ofs == 0)
632 return 0;
633 }
634
635 /* Get WebSockets frame if not already completely in */
636 if (!session->ws->all_hdr_in) {
637 ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
638 &session->ws->rd_header[session->ws->hdr_ofs],
639 sizeof(session->ws->rd_header) - session->ws->hdr_ofs);
640 if (ret < 0)
641 return ret;
642 session->ws->hdr_ofs += (int)ret;
643 /* Enough of the header in ? */
644 if (session->ws->hdr_ofs < 2)
645 return 0;
646
647 if (session->ws->state == COAP_SESSION_TYPE_SERVER &&
648 !(session->ws->rd_header[1] & WS_B1_MASK_BIT)) {
649 /* Client has failed to mask the data */
650 session->ws->close_reason = 1002;
651 coap_ws_close(session);
652 return 0;
653 }
654
655 bytes_size = session->ws->rd_header[1] & WS_B1_LEN_MASK;
656 if (bytes_size == 127) {
657 extra_hdr_len += 8;
658 } else if (bytes_size == 126) {
659 extra_hdr_len += 2;
660 }
661 if (session->ws->rd_header[1] & WS_B1_MASK_BIT) {
662 memcpy(session->ws->mask_key, &session->ws->rd_header[2 + extra_hdr_len], 4);
663 extra_hdr_len +=4;
664 }
665 if (session->ws->hdr_ofs < 2 + extra_hdr_len)
666 return 0;
667
668 /* Header frame is fully in */
669 coap_ws_log_header(session, session->ws->rd_header);
670
671 op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
672 if (op_code != WS_OP_BINARY && op_code != WS_OP_CLOSE) {
673 /* Remote has failed to use correct opcode */
674 session->ws->close_reason = 1003;
675 coap_ws_close(session);
676 return 0;
677 }
678 if (op_code == WS_OP_CLOSE) {
679 coap_log_debug("WS: Close received\n");
680 session->ws->recv_close = 1;
681 coap_ws_close(session);
682 return 0;
683 }
684
685 session->ws->all_hdr_in = 1;
686
687 /* Get WebSockets frame size */
688 if (bytes_size == 127) {
689 bytes_size = ((uint64_t)session->ws->rd_header[2] << 56) +
690 ((uint64_t)session->ws->rd_header[3] << 48) +
691 ((uint64_t)session->ws->rd_header[4] << 40) +
692 ((uint64_t)session->ws->rd_header[5] << 32) +
693 ((uint64_t)session->ws->rd_header[6] << 24) +
694 ((uint64_t)session->ws->rd_header[7] << 16) +
695 ((uint64_t)session->ws->rd_header[8] << 8) +
696 session->ws->rd_header[9];
697 } else if (bytes_size == 126) {
698 bytes_size = ((uint16_t)session->ws->rd_header[2] << 8) +
699 session->ws->rd_header[3];
700 }
701 session->ws->data_size = bytes_size;
702 if ((size_t)bytes_size > datalen) {
703 coap_log_err("coap_ws_read: packet size bigger than provided data space"
704 " (%zu > %zu)\n", bytes_size, datalen);
705 coap_handle_event(session->context, COAP_EVENT_WS_PACKET_SIZE, session);
706 session->ws->close_reason = 1009;
707 coap_ws_close(session);
708 return 0;
709 }
710 coap_log_debug("* %s: Packet size %zu\n", coap_session_str(session),
711 bytes_size);
712
713 /* Handle any data read in as a part of the header */
714 ret = session->ws->hdr_ofs - 2 - extra_hdr_len;
715 if (ret > 0) {
716 assert(2 + extra_hdr_len < (ssize_t)sizeof(session->ws->rd_header));
717 /* data in latter part of header */
718 if (ret <= bytes_size) {
719 /* copy across all the available data */
720 memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], ret);
721 session->ws->data_ofs = ret;
722 if (ret == bytes_size) {
723 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
724 /* Need to unmask the data */
725 coap_ws_mask_data(session, data, bytes_size);
726 }
727 session->ws->all_hdr_in = 0;
728 session->ws->hdr_ofs = 0;
729 op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
730 if (op_code == WS_OP_CLOSE) {
731 session->ws->close_reason = (data[0] << 8) + data[1];
732 coap_log_debug("* %s: WS: Close received (%u)\n",
733 coap_session_str(session),
734 session->ws->close_reason);
735 session->ws->recv_close = 1;
736 if (!session->ws->sent_close)
737 coap_ws_close(session);
738 return 0;
739 }
740 return bytes_size;
741 }
742 } else {
743 /* more information in header than given data size */
744 memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], bytes_size);
745 session->ws->data_ofs = bytes_size;
746 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
747 /* Need to unmask the data */
748 coap_ws_mask_data(session, data, bytes_size);
749 }
750 /* set up partial header for the next read */
751 memmove(session->ws->rd_header,
752 &session->ws->rd_header[2 + extra_hdr_len + bytes_size],
753 ret - bytes_size);
754 session->ws->all_hdr_in = 0;
755 session->ws->hdr_ofs = (int)(ret - bytes_size);
756 return bytes_size;
757 }
758 } else {
759 session->ws->data_ofs = 0;
760 }
761 }
762
763 /* Get in (remaining) data */
764 ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
765 &data[session->ws->data_ofs],
766 session->ws->data_size - session->ws->data_ofs);
767 if (ret <= 0)
768 return ret;
769 session->ws->data_ofs += ret;
770 if (session->ws->data_ofs == session->ws->data_size) {
771 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
772 /* Need to unmask the data */
773 coap_ws_mask_data(session, data, session->ws->data_size);
774 }
775 session->ws->all_hdr_in = 0;
776 session->ws->hdr_ofs = 0;
777 session->ws->data_ofs = 0;
778 coap_log_debug("* %s: ws: recv %4zd bytes\n",
779 coap_session_str(session), session->ws->data_size);
780 return session->ws->data_size;
781 }
782 /* Need to get in all of the data */
783 coap_log_debug("* %s: Waiting Packet size %zu (got %zu)\n", coap_session_str(session),
784 session->ws->data_size, session->ws->data_ofs);
785 return 0;
786 }
787
788 #define COAP_WS_REQUEST \
789 "GET /.well-known/coap HTTP/1.1\r\n" \
790 "Host: %s\r\n" \
791 "Upgrade: websocket\r\n" \
792 "Connection: Upgrade\r\n" \
793 "Sec-WebSocket-Key: %s\r\n" \
794 "Sec-WebSocket-Protocol: coap\r\n" \
795 "Sec-WebSocket-Version: 13\r\n" \
796 "\r\n"
797
798 void
coap_ws_establish(coap_session_t * session)799 coap_ws_establish(coap_session_t *session) {
800 if (!session->ws) {
801 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
802 if (!session->ws) {
803 coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
804 return;
805 }
806 memset(session->ws, 0, sizeof(coap_ws_state_t));
807 }
808 if (session->type == COAP_SESSION_TYPE_CLIENT) {
809 char buf[270];
810 char base64[28];
811 char host[80];
812 int port = 0;
813
814 session->ws->state = COAP_SESSION_TYPE_CLIENT;
815 if (!session->ws_host) {
816 coap_log_err("WS Host not defined\n");
817 coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
818 return;
819 }
820 coap_prng(session->ws->key, sizeof(session->ws->key));
821 coap_ws_log_key(session);
822 if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
823 base64, sizeof(base64)))
824 return;
825 if (session->proto == COAP_PROTO_WS &&
826 coap_address_get_port(&session->addr_info.remote) != 80) {
827 port = coap_address_get_port(&session->addr_info.remote);
828 } else if (session->proto == COAP_PROTO_WSS &&
829 coap_address_get_port(&session->addr_info.remote) != 443) {
830 port = coap_address_get_port(&session->addr_info.remote);
831 }
832 if (strchr((const char *)session->ws_host->s, ':')) {
833 if (port) {
834 snprintf(host, sizeof(host), "[%s]:%d", session->ws_host->s, port);
835 } else {
836 snprintf(host, sizeof(host), "[%s]", session->ws_host->s);
837 }
838 } else {
839 if (port) {
840 snprintf(host, sizeof(host), "%s:%d", session->ws_host->s, port);
841 } else {
842 snprintf(host, sizeof(host), "%s", session->ws_host->s);
843 }
844 }
845 snprintf(buf, sizeof(buf), COAP_WS_REQUEST, host, base64);
846 coap_log_debug("WS Request\n%s", buf);
847 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
848 strlen(buf));
849 } else {
850 session->ws->state = COAP_SESSION_TYPE_SERVER;
851 }
852 }
853
854 void
coap_ws_close(coap_session_t * session)855 coap_ws_close(coap_session_t *session) {
856 if (!coap_netif_available(session) ||
857 session->state == COAP_SESSION_STATE_NONE) {
858 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
859 return;
860 }
861 if (session->ws && session->ws->up) {
862 int count;
863
864 if (!session->ws->sent_close) {
865 size_t hdr_len = 2;
866 uint8_t ws_header[COAP_MAX_FS];
867 size_t ret;
868
869 ws_header[0] = WS_B0_FIN_BIT | WS_OP_CLOSE;
870 ws_header[1] = 2;
871 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
872 /* Need to set the Mask bit, and set the masking key */
873 ws_header[1] |= WS_B1_MASK_BIT;
874 coap_prng(&ws_header[hdr_len], 4);
875 memcpy(session->ws->mask_key, &ws_header[hdr_len], 4);
876 hdr_len += 4;
877 }
878 coap_ws_log_header(session, ws_header);
879 if (session->ws->close_reason == 0)
880 session->ws->close_reason = 1000;
881
882 ws_header[hdr_len] = session->ws->close_reason >> 8;
883 ws_header[hdr_len+1] = session->ws->close_reason & 0xff;
884 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
885 coap_ws_mask_data(session, &ws_header[hdr_len], 2);
886 }
887 session->ws->sent_close = 1;
888 coap_log_debug("* %s: WS: Close sent (%u)\n",
889 coap_session_str(session),
890 session->ws->close_reason);
891 ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, ws_header, hdr_len+2);
892 if (ret != hdr_len+2) {
893 return;
894 }
895 }
896 count = 5;
897 while (!session->ws->recv_close && count > 0 && coap_netif_available(session)) {
898 uint8_t buf[100];
899 fd_set readfds;
900 int result;
901 struct timeval tv;
902
903 FD_ZERO(&readfds);
904 FD_SET(session->sock.fd, &readfds);
905 tv.tv_sec = 0;
906 tv.tv_usec = 1000;
907 result = select((int)(session->sock.fd+1), &readfds, NULL, NULL, &tv);
908
909 if (result < 0) {
910 break;
911 } else if (result > 0) {
912 coap_ws_read(session, buf, sizeof(buf));
913 }
914 count --;
915 }
916 coap_handle_event(session->context, COAP_EVENT_WS_CLOSED, session);
917 }
918 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
919 }
920
921 int
coap_ws_set_host_request(coap_session_t * session,coap_str_const_t * ws_host)922 coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host) {
923 if (!session | !ws_host)
924 return 0;
925
926 session->ws_host = coap_new_str_const(ws_host->s, ws_host->length);
927 if (!session->ws_host)
928 return 0;
929 return 1;
930 }
931
932 #else /* !COAP_WS_SUPPORT */
933
934 int
coap_ws_is_supported(void)935 coap_ws_is_supported(void) {
936 return 0;
937 }
938
939 int
coap_wss_is_supported(void)940 coap_wss_is_supported(void) {
941 return 0;
942 }
943
944 int
coap_ws_set_host_request(coap_session_t * session,coap_str_const_t * ws_host)945 coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host) {
946 (void)session;
947 (void)ws_host;
948 return 0;
949 }
950
951 #endif /* !COAP_WS_SUPPORT */
952