xref: /third_party/libcoap/src/coap_ws.c (revision c87c5fba)
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
37int
38coap_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
47int
48coap_wss_is_supported(void) {
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) {
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
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) {
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
798void
799coap_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
854void
855coap_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
921int
922coap_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
934int
935coap_ws_is_supported(void) {
936  return 0;
937}
938
939int
940coap_wss_is_supported(void) {
941  return 0;
942}
943
944int
945coap_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