libcoap 4.3.5-develop-e2463f0
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 (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 -1 : 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 " (%" PRIuS " > %" PRIuS ")\n", bytes_size, datalen);
700 session->ws->close_reason = 1009;
701 session->ws->data_size = 0;
702 session->ws->data_ofs = 0;
703 coap_ws_close(session);
704 return 0;
705 }
706 coap_log_debug("* %s: Packet size %" PRIuS "\n", coap_session_str(session),
707 bytes_size);
708
709 /* Handle any data read in as a part of the header */
710 ret = session->ws->hdr_ofs - 2 - extra_hdr_len;
711 if (ret > 0) {
712 assert(2 + extra_hdr_len < (ssize_t)sizeof(session->ws->rd_header));
713 /* data in latter part of header */
714 if (ret <= bytes_size) {
715 /* copy across all the available data */
716 memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], ret);
717 session->ws->data_ofs = ret;
718 if (ret == bytes_size) {
719 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
720 /* Need to unmask the data */
721 coap_ws_mask_data(session, data, bytes_size);
722 }
723 session->ws->all_hdr_in = 0;
724 session->ws->hdr_ofs = 0;
725 op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
726 if (op_code == WS_OP_CLOSE) {
727 session->ws->close_reason = (data[0] << 8) + data[1];
728 coap_log_debug("* %s: WS: Close received (%u)\n",
729 coap_session_str(session),
730 session->ws->close_reason);
731 session->ws->recv_close = 1;
732 if (!session->ws->sent_close)
733 coap_ws_close(session);
734 return 0;
735 }
736 return bytes_size;
737 }
738 } else {
739 /* more information in header than given data size */
740 memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], bytes_size);
741 session->ws->data_ofs = bytes_size;
742 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
743 /* Need to unmask the data */
744 coap_ws_mask_data(session, data, bytes_size);
745 }
746 /* set up partial header for the next read */
747 memmove(session->ws->rd_header,
748 &session->ws->rd_header[2 + extra_hdr_len + bytes_size],
749 ret - bytes_size);
750 session->ws->all_hdr_in = 0;
751 session->ws->hdr_ofs = (int)(ret - bytes_size);
752 return bytes_size;
753 }
754 } else {
755 session->ws->data_ofs = 0;
756 }
757 }
758
759 /* Get in (remaining) data */
760 ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
761 &data[session->ws->data_ofs],
762 session->ws->data_size - session->ws->data_ofs);
763 if (ret <= 0)
764 return ret;
765 session->ws->data_ofs += ret;
766 if (session->ws->data_ofs == session->ws->data_size) {
767 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
768 /* Need to unmask the data */
769 coap_ws_mask_data(session, data, session->ws->data_size);
770 }
771 session->ws->all_hdr_in = 0;
772 session->ws->hdr_ofs = 0;
773 session->ws->data_ofs = 0;
774 coap_log_debug("* %s: ws: recv %4" PRIdS " bytes\n",
775 coap_session_str(session), session->ws->data_size);
776 return session->ws->data_size;
777 }
778 /* Need to get in all of the data */
779 coap_log_debug("* %s: Waiting Packet size %" PRIuS " (got %" PRIuS ")\n",
780 coap_session_str(session),
781 session->ws->data_size, session->ws->data_ofs);
782 return 0;
783}
784
785#define COAP_WS_REQUEST \
786 "GET /.well-known/coap HTTP/1.1\r\n" \
787 "Host: %s\r\n" \
788 "Upgrade: websocket\r\n" \
789 "Connection: Upgrade\r\n" \
790 "Sec-WebSocket-Key: %s\r\n" \
791 "Sec-WebSocket-Protocol: coap\r\n" \
792 "Sec-WebSocket-Version: 13\r\n" \
793 "\r\n"
794
795void
797 if (!session->ws) {
798 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
799 if (!session->ws) {
801 return;
802 }
803 memset(session->ws, 0, sizeof(coap_ws_state_t));
804 }
805 if (session->type == COAP_SESSION_TYPE_CLIENT) {
806 char buf[270];
807 char base64[28];
808 char host[80];
809 int port = 0;
810
811 session->ws->state = COAP_SESSION_TYPE_CLIENT;
812 if (!session->ws_host) {
813 coap_log_err("WS Host not defined\n");
815 return;
816 }
817 coap_prng_lkd(session->ws->key, sizeof(session->ws->key));
818 coap_ws_log_key(session);
819 if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
820 base64, sizeof(base64)))
821 return;
822 if (session->proto == COAP_PROTO_WS &&
823 coap_address_get_port(&session->addr_info.remote) != 80) {
824 port = coap_address_get_port(&session->addr_info.remote);
825 } else if (session->proto == COAP_PROTO_WSS &&
826 coap_address_get_port(&session->addr_info.remote) != 443) {
827 port = coap_address_get_port(&session->addr_info.remote);
828 }
829 if (strchr((const char *)session->ws_host->s, ':')) {
830 if (port) {
831 snprintf(host, sizeof(host), "[%s]:%d", session->ws_host->s, port);
832 } else {
833 snprintf(host, sizeof(host), "[%s]", session->ws_host->s);
834 }
835 } else {
836 if (port) {
837 snprintf(host, sizeof(host), "%s:%d", session->ws_host->s, port);
838 } else {
839 snprintf(host, sizeof(host), "%s", session->ws_host->s);
840 }
841 }
842 snprintf(buf, sizeof(buf), COAP_WS_REQUEST, host, base64);
843 coap_log_debug("WS Request\n%s", buf);
844 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
845 strlen(buf));
846 } else {
847 session->ws->state = COAP_SESSION_TYPE_SERVER;
848 }
849}
850
851void
853 if (!coap_netif_available(session) ||
854 session->state == COAP_SESSION_STATE_NONE) {
855 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
856 return;
857 }
858 if (session->ws && session->ws->up) {
859#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
860 int count;
861#endif /* ! WITH_LWIP && ! WITH_CONTIKI */
862
863 if (!session->ws->sent_close) {
864 size_t hdr_len = 2;
865 uint8_t ws_header[COAP_MAX_FS];
866 size_t ret;
867
868 ws_header[0] = WS_B0_FIN_BIT | WS_OP_CLOSE;
869 ws_header[1] = 2;
870 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
871 /* Need to set the Mask bit, and set the masking key */
872 ws_header[1] |= WS_B1_MASK_BIT;
873 coap_prng_lkd(&ws_header[hdr_len], 4);
874 memcpy(session->ws->mask_key, &ws_header[hdr_len], 4);
875 hdr_len += 4;
876 }
877 coap_ws_log_header(session, ws_header);
878 if (session->ws->close_reason == 0)
879 session->ws->close_reason = 1000;
880
881 ws_header[hdr_len] = session->ws->close_reason >> 8;
882 ws_header[hdr_len+1] = session->ws->close_reason & 0xff;
883 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
884 coap_ws_mask_data(session, &ws_header[hdr_len], 2);
885 }
886 session->ws->sent_close = 1;
887 coap_log_debug("* %s: WS: Close sent (%u)\n",
888 coap_session_str(session),
889 session->ws->close_reason);
890 ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, ws_header, hdr_len+2);
891 if (ret != hdr_len+2) {
892 return;
893 }
894 }
895#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
896 count = 5;
897 while (!session->ws->recv_close && count > 0 && coap_netif_available(session)) {
898 uint8_t buf[100];
899 fd_set readfds;
900 int result;
901 struct timeval tv;
902
903 FD_ZERO(&readfds);
904 FD_SET(session->sock.fd, &readfds);
905 tv.tv_sec = 0;
906 tv.tv_usec = 1000;
907 result = select((int)(session->sock.fd+1), &readfds, NULL, NULL, &tv);
908
909 if (result < 0) {
910 break;
911 } else if (result > 0) {
912 coap_ws_read(session, buf, sizeof(buf));
913 }
914 count --;
915 }
916#endif /* ! WITH_LWIP && ! WITH_CONTIKI */
918 }
919 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
920}
921
922int
924 if (!session | !ws_host)
925 return 0;
926
927 session->ws_host = coap_new_str_const(ws_host->s, ws_host->length);
928 if (!session->ws_host)
929 return 0;
930 return 1;
931}
932
933#else /* !COAP_WS_SUPPORT */
934
935int
937 return 0;
938}
939
940int
942 return 0;
943}
944
945int
947 (void)session;
948 (void)ws_host;
949 return 0;
950}
951
952#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:190
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:5279
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:936
int coap_wss_is_supported(void)
Check whether Secure WebSockets is available.
Definition coap_ws.c:941
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:946
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.