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