libcoap 4.3.3
coap_ws.c
Go to the documentation of this file.
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
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
37int
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
47int
49 return coap_tls_is_supported();
50}
51
52static const char
53basis_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
54
55static int
56coap_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
92static int
93coap_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
156static void
157coap_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
183static void
184coap_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
194static void
195coap_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
204ssize_t
205coap_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) {
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);
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
288static char *
289coap_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
305static int
306coap_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
391static int
392coap_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
419static int
420coap_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 */
494static int
495coap_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 */
579ssize_t
580coap_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) {
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 }
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
624 coap_log_debug("WS: established\n");
625 } else {
626 /* TODO Process the GET response - error on failure */
627
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);
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
798void
800 if (!session->ws) {
801 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
802 if (!session->ws) {
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");
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
854void
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 }
917 }
918 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
919}
920
921int
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
934int
936 return 0;
937}
938
939int
941 return 0;
942}
943
944int
946 (void)session;
947 (void)ws_host;
948 return 0;
949}
950
951#endif /* !COAP_WS_SUPPORT */
uint16_t coap_address_get_port(const coap_address_t *addr)
Returns the port from addr in host byte order.
Definition: coap_address.c:38
Pulls together all the internal only header files.
@ COAP_NACK_WS_LAYER_FAILED
Definition: coap_io.h:77
@ COAP_LAYER_WS
@ COAP_STRING
Definition: coap_mem.h:38
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.
void coap_free_type(coap_memory_tag_t type, void *p)
Releases the memory that was allocated by coap_malloc_type().
int coap_tcp_is_supported(void)
Check whether TCP is available.
Definition: coap_tcp.c:37
int coap_prng(void *buf, size_t len)
Fills buf with len random bytes using the default pseudo random number generator.
Definition: coap_prng.c:140
int coap_handle_event(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:3917
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.
int coap_tls_is_supported(void)
Check whether TLS is available.
Definition: coap_notls.c:28
@ COAP_EVENT_WS_CONNECTED
Triggered when the WebSockets layer is up.
Definition: coap_event.h:125
@ COAP_EVENT_WS_CLOSED
Triggered when the WebSockets layer is closed.
Definition: coap_event.h:127
@ COAP_EVENT_WS_PACKET_SIZE
Triggered when there is an oversize WebSockets packet.
Definition: coap_event.h:123
#define coap_log_debug(...)
Definition: coap_debug.h:120
const char * coap_session_str(const coap_session_t *session)
Get session description.
#define coap_log_info(...)
Definition: coap_debug.h:108
#define coap_log_err(...)
Definition: coap_debug.h:96
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
Definition: oscore_cose.h:136
@ COAP_PROTO_WS
Definition: coap_pdu.h:310
@ COAP_PROTO_WSS
Definition: coap_pdu.h:311
void coap_session_disconnected(coap_session_t *session, coap_nack_reason_t reason)
Notify session that it has failed.
Definition: coap_session.c:848
@ COAP_SESSION_TYPE_SERVER
server-side
Definition: coap_session.h:46
@ COAP_SESSION_TYPE_CLIENT
client-side
Definition: coap_session.h:45
@ COAP_SESSION_STATE_NONE
Definition: coap_session.h:55
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_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
void coap_ws_establish(coap_session_t *session)
Layer function interface for layer below WebSockets accept/connect being established.
#define WS_B0_FIN_BIT
ssize_t coap_ws_write(coap_session_t *session, const uint8_t *data, size_t datalen)
Function interface for websockets data transmission.
#define WS_B1_MASK_BIT
#define WS_B1_LEN_MASK
void coap_ws_close(coap_session_t *session)
Layer function interface for WebSockets close for a session.
#define COAP_MAX_FS
#define WS_B0_OP_MASK
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_BINARY
int coap_ws_is_supported(void)
Check whether WebSockets is available.
Definition: coap_ws.c:935
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:945
int coap_wss_is_supported(void)
Check whether Secure WebSockets is available.
Definition: coap_ws.c:940
coap_address_t remote
remote address and port
Definition: coap_io.h:56
CoAP binary data definition with const data.
Definition: coap_str.h:64
size_t length
length of binary data
Definition: coap_str.h:65
const uint8_t * s
read-only binary data
Definition: coap_str.h:66
coap_layer_read_t l_read
coap_layer_write_t l_write
coap_layer_establish_t l_establish
coap_layer_close_t l_close
Abstraction of virtual session that can be attached to coap_context_t (client) or coap_endpoint_t (se...
coap_socket_t sock
socket object for the session, if any
coap_session_state_t state
current state of relationship with peer
coap_addr_tuple_t addr_info
remote/local address info
coap_proto_t proto
protocol used
coap_session_type_t type
client or server side socket
coap_context_t * context
session's context
coap_layer_func_t lfunc[COAP_LAYER_LAST]
Layer functions to use.
CoAP string data definition with const data.
Definition: coap_str.h:46
const uint8_t * s
read-only string data
Definition: coap_str.h:48
size_t length
length of string
Definition: coap_str.h:47
WebSockets session state.
uint8_t http_hdr[80]
(Partial) HTTP header
uint8_t up
WebSockets established.
uint8_t key[16]
Random, but agreed key value.
uint8_t seen_host
Seen Host: HTTP header (server)
uint8_t seen_ver
Seen version: HTTP header (server)
uint8_t seen_key
Seen Key: HTTP header.
uint8_t seen_conn
Seen Connection: HTTP header.
uint8_t seen_upg
Seen Upgrade: HTTP header.
uint32_t http_ofs
Current offset into http_hdr.
int hdr_ofs
Current offset into rd_header.
coap_session_type_t state
Client or Server.
uint8_t rd_header[COAP_MAX_FS]
(Partial) frame
uint8_t seen_proto
Seen Protocol: HTTP header.
uint8_t mask_key[4]
Masking key.
uint8_t seen_first
Seen first request/response HTTP header.