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