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