Coverage Report

Created: 2025-02-03 06:14

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