libcoap 4.3.5rc1
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-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(&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 coap_log_debug("WS: Invalid Connection: header\n");
340 return 0;
341 }
342 ws->seen_conn = 1;
343 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Key:") == 0) {
344 size_t len;
345
346 if (ws->seen_key) {
347 coap_log_debug("WS: Duplicate Sec-WebSocket-Key: header\n");
348 return 0;
349 }
350 if (!coap_base64_decode_buffer(value, &len, ws->key,
351 sizeof(ws->key)) ||
352 len != sizeof(ws->key)) {
353 coap_log_info("WS: Invalid Sec-WebSocket-Key: %s\n", value);
354 return 0;
355 }
356 coap_ws_log_key(session);
357 ws->seen_key = 1;
358 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) {
359 if (ws->seen_proto) {
360 coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n");
361 return 0;
362 }
363 if (strcasecmp(value, "coap") != 0) {
364 coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n");
365 return 0;
366 }
367 ws->seen_proto = 1;
368 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Version:") == 0) {
369 if (ws->seen_ver) {
370 coap_log_debug("WS: Duplicate Sec-WebSocket-Version: header\n");
371 return 0;
372 }
373 if (strcasecmp(value, "13") != 0) {
374 coap_log_debug("WS: Invalid Sec-WebSocket-Version: header\n");
375 return 0;
376 }
377 ws->seen_ver = 1;
378 }
379 return 1;
380}
381
382#define COAP_WS_KEY_EXT "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
383
384static int
385coap_ws_build_key_hash(coap_session_t *session, char *hash, size_t max_hash_len) {
386 char buf[28 + sizeof(COAP_WS_KEY_EXT)];
387 coap_bin_const_t info;
388 coap_bin_const_t *hashed = NULL;
389
390 if (max_hash_len < 29)
391 return 0;
392 if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
393 buf, sizeof(buf)))
394 return 0;
395 if (strlen(buf) >= 28)
396 return 0;
397 strcat(buf, COAP_WS_KEY_EXT);
398 info.s = (uint8_t *)buf;
399 info.length = strlen(buf);
400 if (!coap_crypto_hash(COSE_ALGORITHM_SHA_1, &info, &hashed))
401 return 0;
402
403 if (!coap_base64_encode_buffer(hashed->s, hashed->length,
404 hash, max_hash_len)) {
405 coap_delete_bin_const(hashed);
406 return 0;
407 }
408 coap_delete_bin_const(hashed);
409 return 1;
410}
411
412static int
413coap_ws_rd_http_header_client(coap_session_t *session) {
414 coap_ws_state_t *ws = session->ws;
415 char *value;
416
417 if (!ws->seen_first) {
418 value = coap_ws_split_rd_header(session);
419
420 if (strcmp((char *)ws->http_hdr, "HTTP/1.1") != 0 ||
421 atoi(value) != 101) {
422 coap_log_info("WS: Invalid GET response %s\n", (char *)ws->http_hdr);
423 return 0;
424 }
425 ws->seen_first = 1;
426 return 1;
427 }
428 /* Process the individual header */
429 value = coap_ws_split_rd_header(session);
430 if (!value)
431 return 0;
432
433 if (strcasecmp((char *)ws->http_hdr, "Upgrade:") == 0) {
434 if (ws->seen_upg) {
435 coap_log_debug("WS: Duplicate Upgrade: header\n");
436 return 0;
437 }
438 if (strcasecmp(value, "websocket") != 0) {
439 coap_log_debug("WS: Invalid Upgrade: header\n");
440 return 0;
441 }
442 ws->seen_upg = 1;
443 } else if (strcasecmp((char *)ws->http_hdr, "Connection:") == 0) {
444 if (ws->seen_conn) {
445 coap_log_debug("WS: Duplicate Connection: header\n");
446 return 0;
447 }
448 if (strcasecmp(value, "Upgrade") != 0) {
449 coap_log_debug("WS: Invalid Connection: header\n");
450 return 0;
451 }
452 ws->seen_conn = 1;
453 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Accept:") == 0) {
454 char hash[30];
455
456 if (ws->seen_key) {
457 coap_log_debug("WS: Duplicate Sec-WebSocket-Accept: header\n");
458 return 0;
459 }
460 if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) {
461 return 0;
462 }
463 if (strcmp(hash, value) != 0) {
464 return 0;
465 }
466 ws->seen_key = 1;
467 } else if (strcasecmp((char *)ws->http_hdr, "Sec-WebSocket-Protocol:") == 0) {
468 if (ws->seen_proto) {
469 coap_log_debug("WS: Duplicate Sec-WebSocket-Protocol: header\n");
470 return 0;
471 }
472 if (strcasecmp(value, "coap") != 0) {
473 coap_log_debug("WS: Invalid Sec-WebSocket-Protocol: header\n");
474 return 0;
475 }
476 ws->seen_proto = 1;
477 }
478 return 1;
479}
480
481/*
482 * Read in and parse WebSockets setup HTTP headers
483 *
484 * return 0 failure
485 * 1 success
486 */
487static int
488coap_ws_rd_http_header(coap_session_t *session) {
489 coap_ws_state_t *ws = session->ws;
490 ssize_t bytes;
491 ssize_t rem;
492 char *cp;
493
494 while (!ws->up) {
495 /*
496 * Can only read in up to COAP_MAX_FS at a time in case there is
497 * some frame info that needs to be subsequently processed
498 */
499 rem = ws->http_ofs > (sizeof(ws->http_hdr) - 1 - COAP_MAX_FS) ?
500 sizeof(ws->http_hdr) - ws->http_ofs : COAP_MAX_FS;
501 bytes = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
502 &ws->http_hdr[ws->http_ofs],
503 rem);
504 if (bytes < 0)
505 return 0;
506 if (bytes == 0)
507 return 1;
508
509 ws->http_ofs += (uint32_t)bytes;
510 ws->http_hdr[ws->http_ofs] = '\000';
511 /* Force at least one check */
512 cp = (char *)ws->http_hdr;
513 while (cp) {
514 cp = strchr((char *)ws->http_hdr, '\n');
515 if (cp) {
516 /* Whole header record in */
517 *cp = '\000';
518 if (cp != (char *)ws->http_hdr) {
519 if (cp[-1] == '\r')
520 cp[-1] = '\000';
521 }
522
523 coap_log_debug("WS: HTTP: %s\n", ws->http_hdr);
524 if (ws->http_hdr[0] != '\000') {
525 if (ws->state == COAP_SESSION_TYPE_SERVER) {
526 if (!coap_ws_rd_http_header_server(session)) {
527 return 0;
528 }
529 } else {
530 if (!coap_ws_rd_http_header_client(session)) {
531 return 0;
532 }
533 }
534 }
535
536 rem = ws->http_ofs - ((uint8_t *)cp + 1 - ws->http_hdr);
537 if (ws->http_hdr[0] == '\000') {
538 /* Found trailing empty header line */
539 if (ws->state == COAP_SESSION_TYPE_SERVER) {
540 if (!(ws->seen_first && ws->seen_host && ws->seen_upg &&
541 ws->seen_conn && ws->seen_key && ws->seen_proto &&
542 ws->seen_ver)) {
543 coap_log_info("WS: Missing protocol header(s)\n");
544 return 0;
545 }
546 } else {
547 if (!(ws->seen_first && ws->seen_upg && ws->seen_conn &&
548 ws->seen_key && ws->seen_proto)) {
549 coap_log_info("WS: Missing protocol header(s)\n");
550 return 0;
551 }
552 }
553 ws->up = 1;
554 ws->hdr_ofs = (int)rem;
555 if (rem > 0)
556 memcpy(ws->rd_header, cp + 1, rem);
557 return 1;
558 }
559 ws->http_ofs = (uint32_t)rem;
560 memmove(ws->http_hdr, cp + 1, rem);
561 ws->http_hdr[ws->http_ofs] = '\000';
562 }
563 }
564 }
565 return 1;
566}
567
568/*
569 * return >=0 Number of bytes processed.
570 * -1 Error (error in errno).
571 */
572ssize_t
573coap_ws_read(coap_session_t *session, uint8_t *data, size_t datalen) {
574 ssize_t bytes_size = 0;
575 ssize_t extra_hdr_len = 0;
576 ssize_t ret;
577 uint8_t op_code;
578
579 if (!session->ws) {
580 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
581 if (!session->ws) {
583 return -1;
584 }
585 memset(session->ws, 0, sizeof(coap_ws_state_t));
586 }
587
588 if (!session->ws->up) {
589 char buf[250];
590
591 if (!coap_ws_rd_http_header(session)) {
592 snprintf(buf, sizeof(buf), "HTTP/1.1 400 Invalid request\r\n\r\n");
593 coap_log_debug("WS: Response (Fail)\n%s", buf);
594 if (coap_netif_available(session)) {
595 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
596 strlen(buf));
597 }
599 return -1;
600 }
601
602 if (!session->ws->up)
603 return 0;
604
605 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
606 char hash[30];
607
608 if (!coap_ws_build_key_hash(session, hash, sizeof(hash))) {
609 return 0;
610 }
611 snprintf(buf, sizeof(buf), COAP_WS_RESPONSE, hash);
612 coap_log_debug("WS: Response\n%s", buf);
613 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
614 strlen(buf));
615
617 coap_log_debug("WS: established\n");
618 } else {
619 /* TODO Process the GET response - error on failure */
620
622 }
623 session->sock.lfunc[COAP_LAYER_WS].l_establish(session);
624 if (session->ws->hdr_ofs == 0)
625 return 0;
626 }
627
628 /* Get WebSockets frame if not already completely in */
629 if (!session->ws->all_hdr_in) {
630 ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
631 &session->ws->rd_header[session->ws->hdr_ofs],
632 sizeof(session->ws->rd_header) - session->ws->hdr_ofs);
633 if (ret < 0)
634 return ret;
635 session->ws->hdr_ofs += (int)ret;
636 /* Enough of the header in ? */
637 if (session->ws->hdr_ofs < 2)
638 return 0;
639
640 if (session->ws->state == COAP_SESSION_TYPE_SERVER &&
641 !(session->ws->rd_header[1] & WS_B1_MASK_BIT)) {
642 /* Client has failed to mask the data */
643 session->ws->close_reason = 1002;
644 coap_ws_close(session);
645 return 0;
646 }
647
648 bytes_size = session->ws->rd_header[1] & WS_B1_LEN_MASK;
649 if (bytes_size == 127) {
650 extra_hdr_len += 8;
651 } else if (bytes_size == 126) {
652 extra_hdr_len += 2;
653 }
654 if (session->ws->rd_header[1] & WS_B1_MASK_BIT) {
655 memcpy(session->ws->mask_key, &session->ws->rd_header[2 + extra_hdr_len], 4);
656 extra_hdr_len +=4;
657 }
658 if (session->ws->hdr_ofs < 2 + extra_hdr_len)
659 return 0;
660
661 /* Header frame is fully in */
662 coap_ws_log_header(session, session->ws->rd_header);
663
664 op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
665 if (op_code != WS_OP_BINARY && op_code != WS_OP_CLOSE) {
666 /* Remote has failed to use correct opcode */
667 session->ws->close_reason = 1003;
668 coap_ws_close(session);
669 return 0;
670 }
671 if (op_code == WS_OP_CLOSE) {
672 coap_log_debug("WS: Close received\n");
673 session->ws->recv_close = 1;
674 coap_ws_close(session);
675 return 0;
676 }
677
678 session->ws->all_hdr_in = 1;
679
680 /* Get WebSockets frame size */
681 if (bytes_size == 127) {
682 bytes_size = ((uint64_t)session->ws->rd_header[2] << 56) +
683 ((uint64_t)session->ws->rd_header[3] << 48) +
684 ((uint64_t)session->ws->rd_header[4] << 40) +
685 ((uint64_t)session->ws->rd_header[5] << 32) +
686 ((uint64_t)session->ws->rd_header[6] << 24) +
687 ((uint64_t)session->ws->rd_header[7] << 16) +
688 ((uint64_t)session->ws->rd_header[8] << 8) +
689 session->ws->rd_header[9];
690 } else if (bytes_size == 126) {
691 bytes_size = ((uint16_t)session->ws->rd_header[2] << 8) +
692 session->ws->rd_header[3];
693 }
694 session->ws->data_size = bytes_size;
695 if ((size_t)bytes_size > datalen) {
696 coap_log_err("coap_ws_read: packet size bigger than provided data space"
697 " (%zu > %zu)\n", bytes_size, datalen);
699 session->ws->close_reason = 1009;
700 coap_ws_close(session);
701 return 0;
702 }
703 coap_log_debug("* %s: Packet size %zu\n", coap_session_str(session),
704 bytes_size);
705
706 /* Handle any data read in as a part of the header */
707 ret = session->ws->hdr_ofs - 2 - extra_hdr_len;
708 if (ret > 0) {
709 assert(2 + extra_hdr_len < (ssize_t)sizeof(session->ws->rd_header));
710 /* data in latter part of header */
711 if (ret <= bytes_size) {
712 /* copy across all the available data */
713 memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], ret);
714 session->ws->data_ofs = ret;
715 if (ret == bytes_size) {
716 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
717 /* Need to unmask the data */
718 coap_ws_mask_data(session, data, bytes_size);
719 }
720 session->ws->all_hdr_in = 0;
721 session->ws->hdr_ofs = 0;
722 op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
723 if (op_code == WS_OP_CLOSE) {
724 session->ws->close_reason = (data[0] << 8) + data[1];
725 coap_log_debug("* %s: WS: Close received (%u)\n",
726 coap_session_str(session),
727 session->ws->close_reason);
728 session->ws->recv_close = 1;
729 if (!session->ws->sent_close)
730 coap_ws_close(session);
731 return 0;
732 }
733 return bytes_size;
734 }
735 } else {
736 /* more information in header than given data size */
737 memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], bytes_size);
738 session->ws->data_ofs = bytes_size;
739 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
740 /* Need to unmask the data */
741 coap_ws_mask_data(session, data, bytes_size);
742 }
743 /* set up partial header for the next read */
744 memmove(session->ws->rd_header,
745 &session->ws->rd_header[2 + extra_hdr_len + bytes_size],
746 ret - bytes_size);
747 session->ws->all_hdr_in = 0;
748 session->ws->hdr_ofs = (int)(ret - bytes_size);
749 return bytes_size;
750 }
751 } else {
752 session->ws->data_ofs = 0;
753 }
754 }
755
756 /* Get in (remaining) data */
757 ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
758 &data[session->ws->data_ofs],
759 session->ws->data_size - session->ws->data_ofs);
760 if (ret <= 0)
761 return ret;
762 session->ws->data_ofs += ret;
763 if (session->ws->data_ofs == session->ws->data_size) {
764 if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
765 /* Need to unmask the data */
766 coap_ws_mask_data(session, data, session->ws->data_size);
767 }
768 session->ws->all_hdr_in = 0;
769 session->ws->hdr_ofs = 0;
770 session->ws->data_ofs = 0;
771 coap_log_debug("* %s: ws: recv %4zd bytes\n",
772 coap_session_str(session), session->ws->data_size);
773 return session->ws->data_size;
774 }
775 /* Need to get in all of the data */
776 coap_log_debug("* %s: Waiting Packet size %zu (got %zu)\n", coap_session_str(session),
777 session->ws->data_size, session->ws->data_ofs);
778 return 0;
779}
780
781#define COAP_WS_REQUEST \
782 "GET /.well-known/coap HTTP/1.1\r\n" \
783 "Host: %s\r\n" \
784 "Upgrade: websocket\r\n" \
785 "Connection: Upgrade\r\n" \
786 "Sec-WebSocket-Key: %s\r\n" \
787 "Sec-WebSocket-Protocol: coap\r\n" \
788 "Sec-WebSocket-Version: 13\r\n" \
789 "\r\n"
790
791void
793 if (!session->ws) {
794 session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
795 if (!session->ws) {
797 return;
798 }
799 memset(session->ws, 0, sizeof(coap_ws_state_t));
800 }
801 if (session->type == COAP_SESSION_TYPE_CLIENT) {
802 char buf[270];
803 char base64[28];
804 char host[80];
805 int port = 0;
806
807 session->ws->state = COAP_SESSION_TYPE_CLIENT;
808 if (!session->ws_host) {
809 coap_log_err("WS Host not defined\n");
811 return;
812 }
813 coap_prng(session->ws->key, sizeof(session->ws->key));
814 coap_ws_log_key(session);
815 if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
816 base64, sizeof(base64)))
817 return;
818 if (session->proto == COAP_PROTO_WS &&
819 coap_address_get_port(&session->addr_info.remote) != 80) {
820 port = coap_address_get_port(&session->addr_info.remote);
821 } else if (session->proto == COAP_PROTO_WSS &&
822 coap_address_get_port(&session->addr_info.remote) != 443) {
823 port = coap_address_get_port(&session->addr_info.remote);
824 }
825 if (strchr((const char *)session->ws_host->s, ':')) {
826 if (port) {
827 snprintf(host, sizeof(host), "[%s]:%d", session->ws_host->s, port);
828 } else {
829 snprintf(host, sizeof(host), "[%s]", session->ws_host->s);
830 }
831 } else {
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 }
838 snprintf(buf, sizeof(buf), COAP_WS_REQUEST, host, base64);
839 coap_log_debug("WS Request\n%s", buf);
840 session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
841 strlen(buf));
842 } else {
843 session->ws->state = COAP_SESSION_TYPE_SERVER;
844 }
845}
846
847void
849 if (!coap_netif_available(session) ||
850 session->state == COAP_SESSION_STATE_NONE) {
851 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
852 return;
853 }
854 if (session->ws && session->ws->up) {
855#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
856 int count;
857#endif /* ! WITH_LWIP && ! WITH_CONTIKI */
858
859 if (!session->ws->sent_close) {
860 size_t hdr_len = 2;
861 uint8_t ws_header[COAP_MAX_FS];
862 size_t ret;
863
864 ws_header[0] = WS_B0_FIN_BIT | WS_OP_CLOSE;
865 ws_header[1] = 2;
866 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
867 /* Need to set the Mask bit, and set the masking key */
868 ws_header[1] |= WS_B1_MASK_BIT;
869 coap_prng(&ws_header[hdr_len], 4);
870 memcpy(session->ws->mask_key, &ws_header[hdr_len], 4);
871 hdr_len += 4;
872 }
873 coap_ws_log_header(session, ws_header);
874 if (session->ws->close_reason == 0)
875 session->ws->close_reason = 1000;
876
877 ws_header[hdr_len] = session->ws->close_reason >> 8;
878 ws_header[hdr_len+1] = session->ws->close_reason & 0xff;
879 if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
880 coap_ws_mask_data(session, &ws_header[hdr_len], 2);
881 }
882 session->ws->sent_close = 1;
883 coap_log_debug("* %s: WS: Close sent (%u)\n",
884 coap_session_str(session),
885 session->ws->close_reason);
886 ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, ws_header, hdr_len+2);
887 if (ret != hdr_len+2) {
888 return;
889 }
890 }
891#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
892 count = 5;
893 while (!session->ws->recv_close && count > 0 && coap_netif_available(session)) {
894 uint8_t buf[100];
895 fd_set readfds;
896 int result;
897 struct timeval tv;
898
899 FD_ZERO(&readfds);
900 FD_SET(session->sock.fd, &readfds);
901 tv.tv_sec = 0;
902 tv.tv_usec = 1000;
903 result = select((int)(session->sock.fd+1), &readfds, NULL, NULL, &tv);
904
905 if (result < 0) {
906 break;
907 } else if (result > 0) {
908 coap_ws_read(session, buf, sizeof(buf));
909 }
910 count --;
911 }
912#endif /* ! WITH_LWIP && ! WITH_CONTIKI */
914 }
915 session->sock.lfunc[COAP_LAYER_WS].l_close(session);
916}
917
918int
920 if (!session | !ws_host)
921 return 0;
922
923 session->ws_host = coap_new_str_const(ws_host->s, ws_host->length);
924 if (!session->ws_host)
925 return 0;
926 return 1;
927}
928
929#else /* !COAP_WS_SUPPORT */
930
931int
933 return 0;
934}
935
936int
938 return 0;
939}
940
941int
943 (void)session;
944 (void)ws_host;
945 return 0;
946}
947
948#endif /* !COAP_WS_SUPPORT */
uint16_t coap_address_get_port(const coap_address_t *addr)
Returns the port from addr in host byte order.
@ COAP_NACK_WS_LAYER_FAILED
Definition coap_io.h:77
@ COAP_LAYER_WS
Library specific build wrapper for coap_internal.h.
@ COAP_STRING
Definition coap_mem.h:38
void * coap_malloc_type(coap_memory_tag_t type, size_t size)
Allocates a chunk of size bytes and returns a pointer to the newly allocated memory.
void coap_free_type(coap_memory_tag_t type, void *p)
Releases the memory that was allocated by coap_malloc_type().
int coap_tcp_is_supported(void)
Check whether TCP is available.
Definition coap_tcp.c:20
int coap_prng(void *buf, size_t len)
Fills buf with len random bytes using the default pseudo random number generator.
Definition coap_prng.c:140
int coap_handle_event_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:4253
int coap_crypto_hash(cose_alg_t alg, const coap_bin_const_t *data, coap_bin_const_t **hash)
Create a hash of the provided data.
int coap_tls_is_supported(void)
Check whether TLS is available.
Definition coap_notls.c:41
@ 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
@ 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.
@ 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:120
coap_str_const_t * coap_new_str_const(const uint8_t *data, size_t size)
Returns a new const string object with at least size+1 bytes storage allocated, and the provided data...
Definition coap_str.c:51
void coap_ws_establish(coap_session_t *session)
Layer function interface for layer below WebSockets accept/connect being established.
#define WS_B0_FIN_BIT
ssize_t coap_ws_write(coap_session_t *session, const uint8_t *data, size_t datalen)
Function interface for websockets data transmission.
#define WS_B1_MASK_BIT
#define WS_B1_LEN_MASK
void coap_ws_close(coap_session_t *session)
Layer function interface for WebSockets close for a session.
#define COAP_MAX_FS
#define WS_B0_OP_MASK
ssize_t coap_ws_read(coap_session_t *session, uint8_t *data, size_t datalen)
Function interface for websockets data receiving.
@ WS_OP_CLOSE
@ WS_OP_BINARY
int coap_ws_is_supported(void)
Check whether WebSockets is available.
Definition coap_ws.c:932
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:942
int coap_wss_is_supported(void)
Check whether Secure WebSockets is available.
Definition coap_ws.c:937
coap_address_t remote
remote address and port
Definition coap_io.h:56
CoAP binary data definition with const data.
Definition coap_str.h:64
size_t length
length of binary data
Definition coap_str.h:65
const uint8_t * s
read-only binary data
Definition coap_str.h:66
coap_layer_read_t l_read
coap_layer_write_t l_write
coap_layer_establish_t l_establish
coap_layer_close_t l_close
Abstraction of virtual session that can be attached to coap_context_t (client) or coap_endpoint_t (se...
coap_socket_t sock
socket object for the session, if any
coap_session_state_t state
current state of relationship with peer
coap_addr_tuple_t addr_info
remote/local address info
coap_proto_t proto
protocol used
coap_session_type_t type
client or server side socket
coap_context_t * context
session's context
coap_layer_func_t lfunc[COAP_LAYER_LAST]
Layer functions to use.
CoAP string data definition with const data.
Definition coap_str.h:46
const uint8_t * s
read-only string data
Definition coap_str.h:48
size_t length
length of string
Definition coap_str.h:47
WebSockets session state.
uint8_t http_hdr[80]
(Partial) HTTP header
uint8_t up
WebSockets established.
uint8_t key[16]
Random, but agreed key value.
uint8_t seen_host
Seen Host: HTTP header (server)
uint8_t seen_ver
Seen version: HTTP header (server)
uint8_t seen_key
Seen Key: HTTP header.
uint8_t seen_conn
Seen Connection: HTTP header.
uint8_t seen_upg
Seen Upgrade: HTTP header.
uint32_t http_ofs
Current offset into http_hdr.
int hdr_ofs
Current offset into rd_header.
coap_session_type_t state
Client or Server.
uint8_t rd_header[COAP_MAX_FS]
(Partial) frame
uint8_t seen_proto
Seen Protocol: HTTP header.
uint8_t mask_key[4]
Masking key.
uint8_t seen_first
Seen first request/response HTTP header.