• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
13 /**
14  * @file coap_ws.c
15  * @brief CoAP WebSocket handling functions
16  */
17 
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 
37 int
coap_ws_is_supported(void)38 coap_ws_is_supported(void) {
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 
47 int
coap_wss_is_supported(void)48 coap_wss_is_supported(void) {
49   return coap_tls_is_supported();
50 }
51 
52 static const char
53 basis_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
54 
55 static int
coap_base64_encode_buffer(const uint8_t * string,size_t len,char * encoded,const size_t max_encoded_len)56 coap_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 
92 static int
coap_base64_decode_buffer(const char * bufcoded,size_t * len,uint8_t * bufplain,const size_t max_decoded_len)93 coap_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 
156 static void
coap_ws_log_header(const coap_session_t * session,const uint8_t * header)157 coap_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 
183 static void
coap_ws_log_key(const coap_session_t * session)184 coap_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 
194 static void
coap_ws_mask_data(coap_session_t * session,uint8_t * data,size_t data_len)195 coap_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 
204 ssize_t
coap_ws_write(coap_session_t * session,const uint8_t * data,size_t datalen)205 coap_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) {
214       coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
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);
272     coap_free_type(COAP_STRING, wdata);
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 
288 static char *
coap_ws_split_rd_header(coap_session_t * session)289 coap_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 
305 static int
coap_ws_rd_http_header_server(coap_session_t * session)306 coap_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 
391 static int
coap_ws_build_key_hash(coap_session_t * session,char * hash,size_t max_hash_len)392 coap_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 
419 static int
coap_ws_rd_http_header_client(coap_session_t * session)420 coap_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  */
494 static int
coap_ws_rd_http_header(coap_session_t * session)495 coap_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  */
579 ssize_t
coap_ws_read(coap_session_t * session,uint8_t * data,size_t datalen)580 coap_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) {
589       coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
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       }
605       coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
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 
623       coap_handle_event(session->context, COAP_EVENT_WS_CONNECTED, session);
624       coap_log_debug("WS: established\n");
625     } else {
626       /* TODO Process the GET response - error on failure */
627 
628       coap_handle_event(session->context, COAP_EVENT_WS_CONNECTED, session);
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     /* Header frame is fully in */
669     coap_ws_log_header(session, session->ws->rd_header);
670 
671     op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
672     if (op_code != WS_OP_BINARY && op_code != WS_OP_CLOSE) {
673       /* Remote has failed to use correct opcode */
674       session->ws->close_reason = 1003;
675       coap_ws_close(session);
676       return 0;
677     }
678     if (op_code == WS_OP_CLOSE) {
679       coap_log_debug("WS: Close received\n");
680       session->ws->recv_close = 1;
681       coap_ws_close(session);
682       return 0;
683     }
684 
685     session->ws->all_hdr_in = 1;
686 
687     /* Get WebSockets frame size */
688     if (bytes_size == 127) {
689       bytes_size = ((uint64_t)session->ws->rd_header[2] << 56) +
690                    ((uint64_t)session->ws->rd_header[3] << 48) +
691                    ((uint64_t)session->ws->rd_header[4] << 40) +
692                    ((uint64_t)session->ws->rd_header[5] << 32) +
693                    ((uint64_t)session->ws->rd_header[6] << 24) +
694                    ((uint64_t)session->ws->rd_header[7] << 16) +
695                    ((uint64_t)session->ws->rd_header[8] <<  8) +
696                    session->ws->rd_header[9];
697     } else if (bytes_size == 126) {
698       bytes_size = ((uint16_t)session->ws->rd_header[2] << 8) +
699                    session->ws->rd_header[3];
700     }
701     session->ws->data_size = bytes_size;
702     if ((size_t)bytes_size > datalen) {
703       coap_log_err("coap_ws_read: packet size bigger than provided data space"
704                    " (%zu > %zu)\n", bytes_size, datalen);
705       coap_handle_event(session->context, COAP_EVENT_WS_PACKET_SIZE, session);
706       session->ws->close_reason = 1009;
707       coap_ws_close(session);
708       return 0;
709     }
710     coap_log_debug("*  %s: Packet size %zu\n", coap_session_str(session),
711                    bytes_size);
712 
713     /* Handle any data read in as a part of the header */
714     ret = session->ws->hdr_ofs - 2 - extra_hdr_len;
715     if (ret > 0) {
716       assert(2 + extra_hdr_len < (ssize_t)sizeof(session->ws->rd_header));
717       /* data in latter part of header */
718       if (ret <= bytes_size) {
719         /* copy across all the available data */
720         memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], ret);
721         session->ws->data_ofs = ret;
722         if (ret == bytes_size) {
723           if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
724             /* Need to unmask the data */
725             coap_ws_mask_data(session, data, bytes_size);
726           }
727           session->ws->all_hdr_in = 0;
728           session->ws->hdr_ofs = 0;
729           op_code = session->ws->rd_header[0] & WS_B0_OP_MASK;
730           if (op_code == WS_OP_CLOSE) {
731             session->ws->close_reason = (data[0] << 8) + data[1];
732             coap_log_debug("*  %s: WS: Close received (%u)\n",
733                            coap_session_str(session),
734                            session->ws->close_reason);
735             session->ws->recv_close = 1;
736             if (!session->ws->sent_close)
737               coap_ws_close(session);
738             return 0;
739           }
740           return bytes_size;
741         }
742       } else {
743         /* more information in header than given data size */
744         memcpy(data, &session->ws->rd_header[2 + extra_hdr_len], bytes_size);
745         session->ws->data_ofs = bytes_size;
746         if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
747           /* Need to unmask the data */
748           coap_ws_mask_data(session, data, bytes_size);
749         }
750         /* set up partial header for the next read */
751         memmove(session->ws->rd_header,
752                 &session->ws->rd_header[2 + extra_hdr_len + bytes_size],
753                 ret - bytes_size);
754         session->ws->all_hdr_in = 0;
755         session->ws->hdr_ofs = (int)(ret - bytes_size);
756         return bytes_size;
757       }
758     } else {
759       session->ws->data_ofs = 0;
760     }
761   }
762 
763   /* Get in (remaining) data */
764   ret = session->sock.lfunc[COAP_LAYER_WS].l_read(session,
765                                                   &data[session->ws->data_ofs],
766                                                   session->ws->data_size - session->ws->data_ofs);
767   if (ret <= 0)
768     return ret;
769   session->ws->data_ofs += ret;
770   if (session->ws->data_ofs == session->ws->data_size) {
771     if (session->ws->state == COAP_SESSION_TYPE_SERVER) {
772       /* Need to unmask the data */
773       coap_ws_mask_data(session, data, session->ws->data_size);
774     }
775     session->ws->all_hdr_in = 0;
776     session->ws->hdr_ofs = 0;
777     session->ws->data_ofs = 0;
778     coap_log_debug("*  %s: ws:    recv %4zd bytes\n",
779                    coap_session_str(session), session->ws->data_size);
780     return session->ws->data_size;
781   }
782   /* Need to get in all of the data */
783   coap_log_debug("*  %s: Waiting Packet size %zu (got %zu)\n", coap_session_str(session),
784                  session->ws->data_size, session->ws->data_ofs);
785   return 0;
786 }
787 
788 #define COAP_WS_REQUEST \
789   "GET /.well-known/coap HTTP/1.1\r\n" \
790   "Host: %s\r\n" \
791   "Upgrade: websocket\r\n" \
792   "Connection: Upgrade\r\n" \
793   "Sec-WebSocket-Key: %s\r\n" \
794   "Sec-WebSocket-Protocol: coap\r\n" \
795   "Sec-WebSocket-Version: 13\r\n" \
796   "\r\n"
797 
798 void
coap_ws_establish(coap_session_t * session)799 coap_ws_establish(coap_session_t *session) {
800   if (!session->ws) {
801     session->ws = coap_malloc_type(COAP_STRING, sizeof(coap_ws_state_t));
802     if (!session->ws) {
803       coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
804       return;
805     }
806     memset(session->ws, 0, sizeof(coap_ws_state_t));
807   }
808   if (session->type == COAP_SESSION_TYPE_CLIENT) {
809     char buf[270];
810     char base64[28];
811     char host[80];
812     int port = 0;
813 
814     session->ws->state = COAP_SESSION_TYPE_CLIENT;
815     if (!session->ws_host) {
816       coap_log_err("WS Host not defined\n");
817       coap_session_disconnected(session, COAP_NACK_WS_LAYER_FAILED);
818       return;
819     }
820     coap_prng(session->ws->key, sizeof(session->ws->key));
821     coap_ws_log_key(session);
822     if (!coap_base64_encode_buffer(session->ws->key, sizeof(session->ws->key),
823                                    base64, sizeof(base64)))
824       return;
825     if (session->proto == COAP_PROTO_WS &&
826         coap_address_get_port(&session->addr_info.remote) != 80) {
827       port = coap_address_get_port(&session->addr_info.remote);
828     } else if (session->proto == COAP_PROTO_WSS &&
829                coap_address_get_port(&session->addr_info.remote) != 443) {
830       port = coap_address_get_port(&session->addr_info.remote);
831     }
832     if (strchr((const char *)session->ws_host->s, ':')) {
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     } else {
839       if (port) {
840         snprintf(host, sizeof(host), "%s:%d", session->ws_host->s, port);
841       } else {
842         snprintf(host, sizeof(host), "%s", session->ws_host->s);
843       }
844     }
845     snprintf(buf, sizeof(buf), COAP_WS_REQUEST, host, base64);
846     coap_log_debug("WS Request\n%s", buf);
847     session->sock.lfunc[COAP_LAYER_WS].l_write(session, (uint8_t *)buf,
848                                                strlen(buf));
849   } else {
850     session->ws->state = COAP_SESSION_TYPE_SERVER;
851   }
852 }
853 
854 void
coap_ws_close(coap_session_t * session)855 coap_ws_close(coap_session_t *session) {
856   if (!coap_netif_available(session) ||
857       session->state == COAP_SESSION_STATE_NONE) {
858     session->sock.lfunc[COAP_LAYER_WS].l_close(session);
859     return;
860   }
861   if (session->ws && session->ws->up) {
862     int count;
863 
864     if (!session->ws->sent_close) {
865       size_t hdr_len = 2;
866       uint8_t ws_header[COAP_MAX_FS];
867       size_t ret;
868 
869       ws_header[0] = WS_B0_FIN_BIT | WS_OP_CLOSE;
870       ws_header[1] = 2;
871       if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
872         /* Need to set the Mask bit, and set the masking key */
873         ws_header[1] |= WS_B1_MASK_BIT;
874         coap_prng(&ws_header[hdr_len], 4);
875         memcpy(session->ws->mask_key, &ws_header[hdr_len], 4);
876         hdr_len += 4;
877       }
878       coap_ws_log_header(session, ws_header);
879       if (session->ws->close_reason == 0)
880         session->ws->close_reason = 1000;
881 
882       ws_header[hdr_len] =  session->ws->close_reason >> 8;
883       ws_header[hdr_len+1] =  session->ws->close_reason & 0xff;
884       if (session->ws->state == COAP_SESSION_TYPE_CLIENT) {
885         coap_ws_mask_data(session, &ws_header[hdr_len], 2);
886       }
887       session->ws->sent_close = 1;
888       coap_log_debug("*  %s: WS: Close sent (%u)\n",
889                      coap_session_str(session),
890                      session->ws->close_reason);
891       ret = session->sock.lfunc[COAP_LAYER_WS].l_write(session, ws_header, hdr_len+2);
892       if (ret != hdr_len+2) {
893         return;
894       }
895     }
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     coap_handle_event(session->context, COAP_EVENT_WS_CLOSED, session);
917   }
918   session->sock.lfunc[COAP_LAYER_WS].l_close(session);
919 }
920 
921 int
coap_ws_set_host_request(coap_session_t * session,coap_str_const_t * ws_host)922 coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host) {
923   if (!session | !ws_host)
924     return 0;
925 
926   session->ws_host = coap_new_str_const(ws_host->s, ws_host->length);
927   if (!session->ws_host)
928     return 0;
929   return 1;
930 }
931 
932 #else /* !COAP_WS_SUPPORT */
933 
934 int
coap_ws_is_supported(void)935 coap_ws_is_supported(void) {
936   return 0;
937 }
938 
939 int
coap_wss_is_supported(void)940 coap_wss_is_supported(void) {
941   return 0;
942 }
943 
944 int
coap_ws_set_host_request(coap_session_t * session,coap_str_const_t * ws_host)945 coap_ws_set_host_request(coap_session_t *session, coap_str_const_t *ws_host) {
946   (void)session;
947   (void)ws_host;
948   return 0;
949 }
950 
951 #endif /* !COAP_WS_SUPPORT */
952