libcoap 4.3.5-develop-3dc2c81
Loading...
Searching...
No Matches
coap_ws.c
Go to the documentation of this file.
1/*
2 * coap_ws.c -- WebSockets functions for libcoap
3 *
4 * Copyright (C) 2023-2026 Olaf Bergmann <bergmann@tzi.org>
5 * Copyright (C) 2023-2026 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
17
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 %4" PRIdS " bytes\n",
271 coap_session_str(session), hdr_len);
272 if (ret == (ssize_t)(datalen + hdr_len))
273 coap_log_debug("* %s: ws: sent %4" PRIdS " bytes\n",
274 coap_session_str(session), ret - hdr_len);
275 else
276 coap_log_debug("* %s: ws: sent %4" PRIdS " of %4" PRIdS " 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 (!value)
422 return 0;
423 if (strcmp((char *)ws->http_hdr, "HTTP/1.1") != 0 ||
424 atoi(value) != 101) {
425 coap_log_info("WS: Invalid GET response %s\n", (char *)ws->http_hdr);
426 return 0;
427 }
428 ws->seen_first = 1;
429 return 1;
430 }
431 /* Process the individual header */
432 value = coap_ws_split_rd_header(session);
433 if (!value)
434 return 0;
435
436 if (strcasecmp((char *)ws->http_hdr, "Upgrade:") == 0) {
437 if (ws->seen_upg) {
438 coap_log_debug("WS: Duplicate Upgrade: header\n");
439 return 0;
440 }
441 if (strcasecmp(value, "websocket") != 0) {
442 coap_log_debug("WS: Invalid Upgrade: header\n");
443 return 0;
444 }
445 ws->seen_upg = 1;
446 } else if (strcasecmp((char *)ws->http_hdr, "Connection:") == 0) {
447 if (ws->seen_conn) {
448 coap_log_debug("WS: Duplicate Connection: header\n");
449 return 0;
450 }
451 if (strcasecmp(value, "Upgrade") != 0) {
452 coap_log_debug("WS: Invalid Connection: header\n");
453 return 0;
454 }
455 ws->seen_conn = 1;
456 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Accept:") == 0) {
457 char hash[30];
458
459 if (ws->seen_key) {
460 coap_log_debug("WS: Duplicate Sec-WebSocket-Accept: header\n");
461 return 0;
462 }
463 if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) {
464 return 0;
465 }
466 if (strcmp(hash, value) != 0) {
467 return 0;
468 }
469 ws->seen_key = 1;
470 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) {
471 if (ws->seen_proto) {
472 coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n");
473 return 0;
474 }
475 if (strcasecmp(value, "coap") != 0) {
476 coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n");
477 return 0;
478 }
479 ws->seen_proto = 1;
480 }
481 return 1;
482}
483
484/*
485 * Read in and parse WebSockets setup HTTP headers
486 *
487 * return 0 failure
488 * 1 success
489 */
490static int
491coap_ws_rd_http_header(coap_session_t *session) {
492 coap_ws_state_t *ws = session->ws;
493 ssize_t bytes;
494 ssize_t rem;
495 char *cp;
496
497 while (!ws->up) {
498 /*
499 * Can only read in up to COAP_MAX_FS at a time in case there is
500 * some frame info that needs to be subsequently processed
501 */
502 rem = ws->http_ofs > (sizeof(ws->http_hdr) - 1 - COAP_MAX_FS) ?
503 sizeof(ws->http_hdr) - ws->http_ofs -1 : COAP_MAX_FS;
504 bytes = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
505 &ws->http_hdr[ws->http_ofs],
506 rem);
507 if (bytes < 0)
508 return 0;
509 if (bytes == 0)
510 return 1;
511
512 ws->http_ofs += (uint32_t)bytes;
513 ws->http_hdr[ws->http_ofs] = '\000';
514 /* Force at least one check */
515 cp = (char *)ws->http_hdr;
516 while (cp) {
517 cp = strchr((char *)ws->http_hdr, '\n');
518 if (cp) {
519 /* Whole header record in */
520 *cp = '\000';
521 if (cp != (char *)ws->http_hdr) {
522 if (cp[-1] == '\r')
523 cp[-1] = '\000';
524 }
525
526 coap_log_debug("WS: HTTP: %s\n", ws->http_hdr);
527 if (ws->http_hdr[0] != '\000') {
528 if (ws->state == COAP_SESSION_TYPE_SERVER) {
529 if (!coap_ws_rd_http_header_server(session)) {
530 return 0;
531 }
532 } else {
533 if (!coap_ws_rd_http_header_client(session)) {
534 return 0;
535 }
536 }
537 }
538
539 rem = ws->http_ofs - ((uint8_t *)cp + 1 - ws->http_hdr);
540 if (ws->http_hdr[0] == '\000') {
541 /* Found trailing empty header line */
542 if (ws->state == COAP_SESSION_TYPE_SERVER) {
543 if (!(ws->seen_first && ws->seen_host && ws->seen_upg &&
544 ws->seen_conn && ws->seen_key && ws->seen_proto &&
545 ws->seen_ver)) {
546 coap_log_info("WS: Missing protocol header(s)\n");
547 return 0;
548 }
549 } else {
550 if (!(ws->seen_first && ws->seen_upg && ws->seen_conn &&
551 ws->seen_key && ws->seen_proto)) {
552 coap_log_info("WS: Missing protocol header(s)\n");
553 return 0;
554 }
555 }
556 ws->up = 1;
557 ws->hdr_ofs = (int)rem;
558 if (rem > 0)
559 memcpy(ws->rd_header, cp + 1, rem);
560 return 1;
561 }
562 ws->http_ofs = (uint32_t)rem;
563 memmove(ws->http_hdr, cp + 1, rem);
564 ws->http_hdr[ws->http_ofs] = '\000';
565 }
566 }
567 }
568 return 1;
569}
570
571/*
572 * return >=0 Number of bytes processed.
573 * -1 Error (error in errno).
574 */
575ssize_t
576coap_ws_read(coap_session_t *session, uint8_t *data, size_t datalen) {
577 ssize_t bytes_size = 0;
578 ssize_t extra_hdr_len = 0;
579 ssize_t ret;
580 uint8_t op_code;
581
582 if (!session->ws) {
583 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
584 if (!session->ws) {
586 return -1;
587 }
588 memset(session->ws, 0, sizeof(coap_ws_state_t));
589 }
590
591 if (!session->ws->up) {
592 char buf[250];
593
594 if (!coap_ws_rd_http_header(session)) {
595 snprintf(buf, sizeof(buf), "HTTP/1.1 400 Invalid request\r\n\r\n");
596 coap_log_debug("WS: Response (Fail)\n%s", buf);
597 if (coap_netif_available(session)) {
598 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
599 strlen(buf));
600 }
602 return -1;
603 }
604
605 if (!session->ws->up)
606 return 0;
607
608 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
609 char hash[30];
610
611 if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) {
612 return 0;
613 }
614 snprintf(buf, sizeof(buf), COAP_WS_RESPONSE, hash);
615 coap_log_debug("WS: Response\n%s", buf);
616 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
617 strlen(buf));
618
620 coap_log_debug("WS: established\n");
621 } else {
622 /* TODO Process the GET response - error on failure */
623
625 }
626 session->sock.lfunc[COAP_LAYER_WS].l_establish(session);
627 if (session->ws->hdr_ofs == 0)
628 return 0;
629 }
630
631 /* Get WebSockets frame if not already completely in */
632 if (!session->ws->all_hdr_in) {
633 ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
634 &session->ws->rd_header[session->ws->hdr_ofs],
635 sizeof(session->ws->rd_header) - session->ws->hdr_ofs);
636 if (ret < 0)
637 return ret;
638 session->ws->hdr_ofs += (int)ret;
639 /* Enough of the header in ? */
640 if (session->ws->hdr_ofs < 2)
641 return 0;
642
643 if (session->ws->state == COAP_SESSION_TYPE_SERVER &&
644 !(session->ws->rd_header[1] & WS_B1_MASK_BIT)) {
645 /* Client has failed to mask the data */
646 session->ws->close_reason = 1002;
647 coap_ws_close(session);
648 return 0;
649 }
650
651 bytes_size = session->ws->rd_header[1] & WS_B1_LEN_MASK;
652 if (bytes_size == 127) {
653 extra_hdr_len += 8;
654 } else if (bytes_size == 126) {
655 extra_hdr_len += 2;
656 }
657 if (session->ws->rd_header[1] & WS_B1_MASK_BIT) {
658 memcpy(session->ws->mask_key, &session->ws->rd_header[2 + extra_hdr_len], 4);
659 extra_hdr_len +=4;
660 }
661 if (session->ws->hdr_ofs < 2 + extra_hdr_len)
662 return 0;
663
664 /* Header frame is fully in */
665 coap_ws_log_header(session, session->ws->rd_header);
666
667 op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
668 if (op_code != WS_OP_BINARY && op_code != WS_OP_CLOSE) {
669 /* Remote has failed to use correct opcode */
670 session->ws->close_reason = 1003;
671 coap_ws_close(session);
672 return 0;
673 }
674 if (op_code == WS_OP_CLOSE) {
675 coap_log_debug("WS: Close received\n");
676 session->ws->recv_close = 1;
677 coap_ws_close(session);
678 return 0;
679 }
680
681 session->ws->all_hdr_in = 1;
682
683 /* Get WebSockets frame size */
684 if (bytes_size == 127) {
685 bytes_size = ((uint64_t)session->ws->rd_header[2] << 56) +
686 ((uint64_t)session->ws->rd_header[3] << 48) +
687 ((uint64_t)session->ws->rd_header[4] << 40) +
688 ((uint64_t)session->ws->rd_header[5] << 32) +
689 ((uint64_t)session->ws->rd_header[6] << 24) +
690 ((uint64_t)session->ws->rd_header[7] << 16) +
691 ((uint64_t)session->ws->rd_header[8] << 8) +
692 session->ws->rd_header[9];
693 } else if (bytes_size == 126) {
694 bytes_size = ((uint16_t)session->ws->rd_header[2] << 8) +
695 session->ws->rd_header[3];
696 }
697 session->ws->data_size = bytes_size;
698 if ((size_t)bytes_size > datalen) {
699 coap_log_err("coap_ws_read: packet size bigger than provided data space"
700 " (%" PRIuS " > %" PRIuS ")\n", bytes_size, datalen);
702 session->ws->close_reason = 1009;
703 session->ws->data_size = 0;
704 session->ws->data_ofs = 0;
705 coap_ws_close(session);
706 return 0;
707 }
708 coap_log_debug("* %s: Packet size %" PRIuS "\n", coap_session_str(session),
709 bytes_size);
710
711 /* Handle any data read in as a part of the header */
712 ret = session->ws->hdr_ofs - 2 - extra_hdr_len;
713 if (ret > 0) {
714 assert(2 + extra_hdr_len < (ssize_t)sizeof(session->ws->rd_header));
715 /* data in latter part of header */
716 if (ret <= bytes_size) {
717 /* copy across all the available data */
718 memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], ret);
719 session->ws->data_ofs = ret;
720 if (ret == bytes_size) {
721 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
722 /* Need to unmask the data */
723 coap_ws_mask_data(session, data, bytes_size);
724 }
725 session->ws->all_hdr_in = 0;
726 session->ws->hdr_ofs = 0;
727 op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
728 if (op_code == WS_OP_CLOSE) {
729 session->ws->close_reason = (data[0] << 8) + data[1];
730 coap_log_debug("* %s: WS: Close received (%u)\n",
731 coap_session_str(session),
732 session->ws->close_reason);
733 session->ws->recv_close = 1;
734 if (!session->ws->sent_close)
735 coap_ws_close(session);
736 return 0;
737 }
738 return bytes_size;
739 }
740 } else {
741 /* more information in header than given data size */
742 memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], bytes_size);
743 session->ws->data_ofs = bytes_size;
744 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
745 /* Need to unmask the data */
746 coap_ws_mask_data(session, data, bytes_size);
747 }
748 /* set up partial header for the next read */
749 memmove(session->ws->rd_header,
750 &session->ws->rd_header[2 + extra_hdr_len + bytes_size],
751 ret - bytes_size);
752 session->ws->all_hdr_in = 0;
753 session->ws->hdr_ofs = (int)(ret - bytes_size);
754 return bytes_size;
755 }
756 } else {
757 session->ws->data_ofs = 0;
758 }
759 }
760
761 /* Get in (remaining) data */
762 ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
763 &data[session->ws->data_ofs],
764 session->ws->data_size - session->ws->data_ofs);
765 if (ret <= 0)
766 return ret;
767 session->ws->data_ofs += ret;
768 if (session->ws->data_ofs == session->ws->data_size) {
769 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
770 /* Need to unmask the data */
771 coap_ws_mask_data(session, data, session->ws->data_size);
772 }
773 session->ws->all_hdr_in = 0;
774 session->ws->hdr_ofs = 0;
775 session->ws->data_ofs = 0;
776 coap_log_debug("* %s: ws: recv %4" PRIdS " bytes\n",
777 coap_session_str(session), session->ws->data_size);
778 return session->ws->data_size;
779 }
780 /* Need to get in all of the data */
781 coap_log_debug("* %s: Waiting Packet size %" PRIuS " (got %" PRIuS ")\n",
782 coap_session_str(session),
783 session->ws->data_size, session->ws->data_ofs);
784 return 0;
785}
786
787#define COAP_WS_REQUEST \
788 "GET /.well-known/coap HTTP/1.1\r\n" \
789 "Host: %s\r\n" \
790 "Upgrade: websocket\r\n" \
791 "Connection: Upgrade\r\n" \
792 "Sec-WebSocket-Key: %s\r\n" \
793 "Sec-WebSocket-Protocol: coap\r\n" \
794 "Sec-WebSocket-Version: 13\r\n" \
795 "\r\n"
796
797void
799 if (!session->ws) {
800 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
801 if (!session->ws) {
803 return;
804 }
805 memset(session->ws, 0, sizeof(coap_ws_state_t));
806 }
807 if (session->type == COAP_SESSION_TYPE_CLIENT) {
808 char buf[270];
809 char base64[28];
810 char host[80];
811 int port = 0;
812
813 session->ws->state = COAP_SESSION_TYPE_CLIENT;
814 if (!session->ws_host) {
815 coap_log_err("WS Host not defined\n");
817 return;
818 }
819 coap_prng_lkd(session->ws->key, sizeof(session->ws->key));
820 coap_ws_log_key(session);
821 if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
822 base64, sizeof(base64)))
823 return;
824 if (session->proto == COAP_PROTO_WS &&
825 coap_address_get_port(&session->addr_info.remote) != 80) {
826 port = coap_address_get_port(&session->addr_info.remote);
827 } else if (session->proto == COAP_PROTO_WSS &&
828 coap_address_get_port(&session->addr_info.remote) != 443) {
829 port = coap_address_get_port(&session->addr_info.remote);
830 }
831 if (strchr((const char *)session->ws_host->s, ':')) {
832 if (port) {
833 snprintf(host, sizeof(host), "[%s]:%d", session->ws_host->s, port);
834 } else {
835 snprintf(host, sizeof(host), "[%s]", session->ws_host->s);
836 }
837 } else {
838 if (port) {
839 snprintf(host, sizeof(host), "%s:%d", session->ws_host->s, port);
840 } else {
841 snprintf(host, sizeof(host), "%s", session->ws_host->s);
842 }
843 }
844 snprintf(buf, sizeof(buf), COAP_WS_REQUEST, host, base64);
845 coap_log_debug("WS Request\n%s", buf);
846 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
847 strlen(buf));
848 } else {
849 session->ws->state = COAP_SESSION_TYPE_SERVER;
850 }
851}
852
853void
855 if (!coap_netif_available(session) ||
856 session->state == COAP_SESSION_STATE_NONE) {
857 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
858 return;
859 }
860 if (session->ws && session->ws->up) {
861#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
862 int count;
863#endif /* ! WITH_LWIP && ! WITH_CONTIKI */
864
865 if (!session->ws->sent_close) {
866 size_t hdr_len = 2;
867 uint8_t ws_header[COAP_MAX_FS];
868 size_t ret;
869
870 ws_header[0] = WS_B0_FIN_BIT | WS_OP_CLOSE;
871 ws_header[1] = 2;
872 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
873 /* Need to set the Mask bit, and set the masking key */
874 ws_header[1] |= WS_B1_MASK_BIT;
875 coap_prng_lkd(&ws_header[hdr_len], 4);
876 memcpy(session->ws->mask_key, &ws_header[hdr_len], 4);
877 hdr_len += 4;
878 }
879 coap_ws_log_header(session, ws_header);
880 if (session->ws->close_reason == 0)
881 session->ws->close_reason = 1000;
882
883 ws_header[hdr_len] = session->ws->close_reason >> 8;
884 ws_header[hdr_len+1] = session->ws->close_reason & 0xff;
885 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
886 coap_ws_mask_data(session, &ws_header[hdr_len], 2);
887 }
888 session->ws->sent_close = 1;
889 coap_log_debug("* %s: WS: Close sent (%u)\n",
890 coap_session_str(session),
891 session->ws->close_reason);
892 ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, ws_header, hdr_len+2);
893 if (ret != hdr_len+2) {
894 return;
895 }
896 }
897#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
898 count = 5;
899 while (!session->ws->recv_close && count > 0 && coap_netif_available(session)) {
900 uint8_t buf[100];
901 fd_set readfds;
902 int result;
903 struct timeval tv;
904
905 FD_ZERO(&readfds);
906 FD_SET(session->sock.fd, &readfds);
907 tv.tv_sec = 0;
908 tv.tv_usec = 1000;
909 result = select((int)(session->sock.fd+1), &readfds, NULL, NULL, &tv);
910
911 if (result < 0) {
912 break;
913 } else if (result > 0) {
914 coap_ws_read(session, buf, sizeof(buf));
915 }
916 count --;
917 }
918#endif /* ! WITH_LWIP && ! WITH_CONTIKI */
920 }
921 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
922}
923
924int
926 if (!session | !ws_host)
927 return 0;
928
929 session->ws_host = coap_new_str_const(ws_host->s, ws_host->length);
930 if (!session->ws_host)
931 return 0;
932 return 1;
933}
934
935#else /* !COAP_WS_SUPPORT */
936
937int
939 return 0;
940}
941
942int
944 return 0;
945}
946
947int
949 (void)session;
950 (void)ws_host;
951 return 0;
952}
953
954#endif /* !COAP_WS_SUPPORT */
uint16_t coap_address_get_port(const coap_address_t *addr)
Returns the port from addr in host byte order.
#define PRIuS
#define PRIdS
@ COAP_NACK_WS_LAYER_FAILED
Definition coap_io.h:72
@ COAP_LAYER_WS
Library specific build wrapper for coap_internal.h.
@ COAP_STRING
Definition coap_mem.h:33
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().
#define NULL
Definition coap_option.h:30
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:195
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:5396
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:137
@ COAP_EVENT_WS_CLOSED
Triggered when the WebSockets layer is closed.
Definition coap_event.h:139
@ COAP_EVENT_WS_PACKET_SIZE
Triggered when there is an oversize WebSockets packet.
Definition coap_event.h:135
#define coap_log_debug(...)
Definition coap_debug.h:126
const char * coap_session_str(const coap_session_t *session)
Get session description.
#define coap_log_info(...)
Definition coap_debug.h:114
#define coap_log_err(...)
Definition coap_debug.h:102
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
@ COAP_PROTO_WS
Definition coap_pdu.h:240
@ COAP_PROTO_WSS
Definition coap_pdu.h:241
void coap_session_disconnected_lkd(coap_session_t *session, coap_nack_reason_t reason)
Notify session that it has failed.
@ COAP_SESSION_TYPE_SERVER
server-side
@ COAP_SESSION_TYPE_CLIENT
client-side
@ COAP_SESSION_STATE_NONE
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:130
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:55
int coap_tcp_is_supported(void)
Check whether TCP is available.
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:938
int coap_wss_is_supported(void)
Check whether Secure WebSockets is available.
Definition coap_ws.c:943
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:948
coap_address_t remote
remote address and port
Definition coap_io.h:58
CoAP binary data definition with const data.
Definition coap_str.h:65
size_t length
length of binary data
Definition coap_str.h:66
const uint8_t * s
read-only binary data
Definition coap_str.h:67
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:47
const uint8_t * s
read-only string data
Definition coap_str.h:49
size_t length
length of string
Definition coap_str.h:48
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.