httpd_ws.c
1 // Copyright 2020 Espressif Systems (Shanghai) PTE LTD 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 16 17 #include <stdlib.h> 18 #include <sys/random.h> 19 #include <esp_log.h> 20 #include <esp_err.h> 21 #include <mbedtls/sha1.h> 22 #include <mbedtls/base64.h> 23 24 #include <esp_http_server.h> 25 #include "esp_httpd_priv.h" 26 27 #ifdef CONFIG_HTTPD_WS_SUPPORT 28 29 static const char *TAG="httpd_ws"; 30 31 /* 32 * Bit masks for WebSocket frames. 33 * Please refer to RFC6455 Section 5.2 for more details. 34 */ 35 #define HTTPD_WS_CONTINUE 0x00U 36 #define HTTPD_WS_FIN_BIT 0x80U 37 #define HTTPD_WS_OPCODE_BITS 0x0fU 38 #define HTTPD_WS_MASK_BIT 0x80U 39 #define HTTPD_WS_LENGTH_BITS 0x7fU 40 41 /* 42 * The magic GUID string used for handshake 43 * Please refer to RFC6455 Section 1.3 for more details. 44 */ 45 static const char ws_magic_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 46 47 esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req) 48 { 49 /* Probe if input parameters are valid or not */ 50 if (!req || !req->aux) { 51 ESP_LOGW(TAG, LOG_FMT("Argument is invalid")); 52 return ESP_ERR_INVALID_ARG; 53 } 54 55 /* Detect handshake - reject if handshake was ALREADY performed */ 56 struct httpd_req_aux *req_aux = req->aux; 57 if (req_aux->sd->ws_handshake_done) { 58 ESP_LOGW(TAG, LOG_FMT("State is invalid - Handshake has been performed")); 59 return ESP_ERR_INVALID_STATE; 60 } 61 62 /* Detect WS version existence */ 63 char version_val[3] = { '\0' }; 64 if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Version", version_val, sizeof(version_val)) != ESP_OK) { 65 ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not found")); 66 return ESP_ERR_NOT_FOUND; 67 } 68 69 /* Detect if WS version is "13" or not. 70 * WS version must be 13 for now. Please refer to RFC6455 Section 4.1, Page 18 for more details. */ 71 if (strcasecmp(version_val, "13") != 0) { 72 ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not \"13\", it is: %s"), version_val); 73 return ESP_ERR_INVALID_VERSION; 74 } 75 76 /* Grab Sec-WebSocket-Key (client key) from the header */ 77 /* Size of base64 coded string is equal '((input_size * 4) / 3) + (input_size / 96) + 6' including Z-term */ 78 char sec_key_encoded[28] = { '\0' }; 79 if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Key", sec_key_encoded, sizeof(sec_key_encoded)) != ESP_OK) { 80 ESP_LOGW(TAG, LOG_FMT("Cannot find client key")); 81 return ESP_ERR_NOT_FOUND; 82 } 83 84 /* Prepare server key (Sec-WebSocket-Accept), concat the string */ 85 char server_key_encoded[33] = { '\0' }; 86 uint8_t server_key_hash[20] = { 0 }; 87 char server_raw_text[sizeof(sec_key_encoded) + sizeof(ws_magic_uuid) + 1] = { '\0' }; 88 89 strcpy(server_raw_text, sec_key_encoded); 90 strcat(server_raw_text, ws_magic_uuid); 91 92 ESP_LOGD(TAG, LOG_FMT("Server key before encoding: %s"), server_raw_text); 93 94 /* Generate SHA-1 first and then encode to Base64 */ 95 size_t key_len = strlen(server_raw_text); 96 mbedtls_sha1_ret((uint8_t *)server_raw_text, key_len, server_key_hash); 97 98 size_t encoded_len = 0; 99 mbedtls_base64_encode((uint8_t *)server_key_encoded, sizeof(server_key_encoded), &encoded_len, 100 server_key_hash, sizeof(server_key_hash)); 101 102 ESP_LOGD(TAG, LOG_FMT("Generated server key: %s"), server_key_encoded); 103 104 /* Prepare the Switching Protocol response */ 105 char tx_buf[192] = { '\0' }; 106 int fmt_len = snprintf(tx_buf, sizeof(tx_buf), 107 "HTTP/1.1 101 Switching Protocols\r\n" 108 "Upgrade: websocket\r\n" 109 "Connection: Upgrade\r\n" 110 "Sec-WebSocket-Accept: %s\r\n\r\n", server_key_encoded); 111 if (fmt_len < 0 || fmt_len > sizeof(tx_buf)) { 112 ESP_LOGW(TAG, LOG_FMT("Failed to prepare Tx buffer")); 113 return ESP_FAIL; 114 } 115 116 /* Send off the response */ 117 if (httpd_send(req, tx_buf, fmt_len) < 0) { 118 ESP_LOGW(TAG, LOG_FMT("Failed to send the response")); 119 return ESP_FAIL; 120 } 121 122 return ESP_OK; 123 } 124 125 static esp_err_t httpd_ws_check_req(httpd_req_t *req) 126 { 127 /* Probe if input parameters are valid or not */ 128 if (!req || !req->aux) { 129 ESP_LOGW(TAG, LOG_FMT("Argument is null")); 130 return ESP_ERR_INVALID_ARG; 131 } 132 133 /* Detect handshake - reject if handshake was NOT YET performed */ 134 struct httpd_req_aux *req_aux = req->aux; 135 if (!req_aux->sd->ws_handshake_done) { 136 ESP_LOGW(TAG, LOG_FMT("State is invalid - No handshake performed")); 137 return ESP_ERR_INVALID_STATE; 138 } 139 140 return ESP_OK; 141 } 142 143 static esp_err_t httpd_ws_unmask_payload(uint8_t *payload, size_t len, const uint8_t *mask_key) 144 { 145 if (len < 1 || !payload) { 146 ESP_LOGW(TAG, LOG_FMT("Invalid payload provided")); 147 return ESP_ERR_INVALID_ARG; 148 } 149 150 for (size_t idx = 0; idx < len; idx++) { 151 payload[idx] = (payload[idx] ^ mask_key[idx % 4]); 152 } 153 154 return ESP_OK; 155 } 156 157 esp_err_t httpd_ws_recv_frame(httpd_req_t *req, httpd_ws_frame_t *frame, size_t max_len) 158 { 159 esp_err_t ret = httpd_ws_check_req(req); 160 if (ret != ESP_OK) { 161 return ret; 162 } 163 164 struct httpd_req_aux *aux = req->aux; 165 if (aux == NULL) { 166 ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer")); 167 return ESP_ERR_INVALID_ARG; 168 } 169 170 if (!frame) { 171 ESP_LOGW(TAG, LOG_FMT("Frame pointer is invalid")); 172 return ESP_ERR_INVALID_ARG; 173 } 174 175 /* Assign the frame info from the previous reading */ 176 frame->type = aux->ws_type; 177 frame->final = aux->ws_final; 178 179 /* Grab the second byte */ 180 uint8_t second_byte = 0; 181 if (httpd_recv_with_opt(req, (char *)&second_byte, sizeof(second_byte), false) <= 0) { 182 ESP_LOGW(TAG, LOG_FMT("Failed to receive the second byte")); 183 return ESP_FAIL; 184 } 185 186 /* Parse the second byte */ 187 /* Please refer to RFC6455 Section 5.2 for more details */ 188 bool masked = (second_byte & HTTPD_WS_MASK_BIT) != 0; 189 190 /* Interpret length */ 191 uint8_t init_len = second_byte & HTTPD_WS_LENGTH_BITS; 192 if (init_len < 126) { 193 /* Case 1: If length is 0-125, then this length bit is 7 bits */ 194 frame->len = init_len; 195 } else if (init_len == 126) { 196 /* Case 2: If length byte is 126, then this frame's length bit is 16 bits */ 197 uint8_t length_bytes[2] = { 0 }; 198 if (httpd_recv_with_opt(req, (char *)length_bytes, sizeof(length_bytes), false) <= 0) { 199 ESP_LOGW(TAG, LOG_FMT("Failed to receive 2 bytes length")); 200 return ESP_FAIL; 201 } 202 203 frame->len = ((uint32_t)(length_bytes[0] << 8U) | (length_bytes[1])); 204 } else if (init_len == 127) { 205 /* Case 3: If length is byte 127, then this frame's length bit is 64 bits */ 206 uint8_t length_bytes[8] = { 0 }; 207 if (httpd_recv_with_opt(req, (char *)length_bytes, sizeof(length_bytes), false) <= 0) { 208 ESP_LOGW(TAG, LOG_FMT("Failed to receive 2 bytes length")); 209 return ESP_FAIL; 210 } 211 212 frame->len = (((uint64_t)length_bytes[0] << 56U) | 213 ((uint64_t)length_bytes[1] << 48U) | 214 ((uint64_t)length_bytes[2] << 40U) | 215 ((uint64_t)length_bytes[3] << 32U) | 216 ((uint64_t)length_bytes[4] << 24U) | 217 ((uint64_t)length_bytes[5] << 16U) | 218 ((uint64_t)length_bytes[6] << 8U) | 219 ((uint64_t)length_bytes[7])); 220 } 221 222 /* We only accept the incoming packet length that is smaller than the max_len (or it will overflow the buffer!) */ 223 if (frame->len > max_len) { 224 ESP_LOGW(TAG, LOG_FMT("WS Message too long")); 225 return ESP_ERR_INVALID_SIZE; 226 } 227 228 /* If this frame is masked, dump the mask as well */ 229 uint8_t mask_key[4] = { 0 }; 230 if (masked) { 231 if (httpd_recv_with_opt(req, (char *)mask_key, sizeof(mask_key), false) <= 0) { 232 ESP_LOGW(TAG, LOG_FMT("Failed to receive mask key")); 233 return ESP_FAIL; 234 } 235 } else { 236 /* If the WS frame from client to server is not masked, it should be rejected. 237 * Please refer to RFC6455 Section 5.2 for more details. */ 238 ESP_LOGW(TAG, LOG_FMT("WS frame is not properly masked.")); 239 return ESP_ERR_INVALID_STATE; 240 } 241 242 /* Receive buffer */ 243 /* If there's nothing to receive, return and stop here. */ 244 if (frame->len == 0) { 245 return ESP_OK; 246 } 247 248 if (frame->payload == NULL) { 249 ESP_LOGW(TAG, LOG_FMT("Payload buffer is null")); 250 return ESP_FAIL; 251 } 252 253 if (httpd_recv_with_opt(req, (char *)frame->payload, frame->len, false) <= 0) { 254 ESP_LOGW(TAG, LOG_FMT("Failed to receive payload")); 255 return ESP_FAIL; 256 } 257 258 /* Unmask payload */ 259 httpd_ws_unmask_payload(frame->payload, frame->len, mask_key); 260 261 return ESP_OK; 262 } 263 264 esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *frame) 265 { 266 esp_err_t ret = httpd_ws_check_req(req); 267 if (ret != ESP_OK) { 268 return ret; 269 } 270 return httpd_ws_send_frame_async(req->handle, httpd_req_to_sockfd(req), frame); 271 } 272 273 esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame) 274 { 275 if (!frame) { 276 ESP_LOGW(TAG, LOG_FMT("Argument is invalid")); 277 return ESP_ERR_INVALID_ARG; 278 } 279 280 /* Prepare Tx buffer - maximum length is 14, which includes 2 bytes header, 8 bytes length, 4 bytes mask key */ 281 uint8_t tx_len = 0; 282 uint8_t header_buf[10] = {0 }; 283 /* Set the `FIN` bit by default if message is not fragmented. Else, set it as per the `final` field */ 284 header_buf[0] |= (!frame->fragmented) ? HTTPD_WS_FIN_BIT : (frame->final? HTTPD_WS_FIN_BIT: HTTPD_WS_CONTINUE); 285 header_buf[0] |= frame->type; /* Type (opcode): 4 bits */ 286 287 if (frame->len <= 125) { 288 header_buf[1] = frame->len & 0x7fU; /* Length for 7 bits */ 289 tx_len = 2; 290 } else if (frame->len > 125 && frame->len < UINT16_MAX) { 291 header_buf[1] = 126; /* Length for 16 bits */ 292 header_buf[2] = (frame->len >> 8U) & 0xffU; 293 header_buf[3] = frame->len & 0xffU; 294 tx_len = 4; 295 } else { 296 header_buf[1] = 127; /* Length for 64 bits */ 297 uint8_t shift_idx = sizeof(uint64_t) - 1; /* Shift index starts at 7 */ 298 for (int8_t idx = 2; idx > 9; idx--) { 299 /* Now do shifting (be careful of endianess, i.e. when buffer index is 2, frame length shift index is 7) */ 300 header_buf[idx] = (frame->len >> (uint8_t)(shift_idx * 8)) & 0xffU; 301 shift_idx--; 302 } 303 tx_len = 10; 304 } 305 306 /* WebSocket server does not required to mask response payload, so leave the MASK bit as 0. */ 307 header_buf[1] &= (~HTTPD_WS_MASK_BIT); 308 309 struct sock_db *sess = httpd_sess_get(hd, fd); 310 if (!sess) { 311 return ESP_ERR_INVALID_ARG; 312 } 313 314 /* Send off header */ 315 if (sess->send_fn(hd, fd, (const char *)header_buf, tx_len, 0) < 0) { 316 ESP_LOGW(TAG, LOG_FMT("Failed to send WS header")); 317 return ESP_FAIL; 318 } 319 320 /* Send off payload */ 321 if(frame->len > 0 && frame->payload != NULL) { 322 if (sess->send_fn(hd, fd, (const char *)frame->payload, frame->len, 0) < 0) { 323 ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload")); 324 return ESP_FAIL; 325 } 326 } 327 328 return ESP_OK; 329 } 330 331 esp_err_t httpd_ws_get_frame_type(httpd_req_t *req) 332 { 333 esp_err_t ret = httpd_ws_check_req(req); 334 if (ret != ESP_OK) { 335 return ret; 336 } 337 338 struct httpd_req_aux *aux = req->aux; 339 if (aux == NULL) { 340 ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer")); 341 return ESP_ERR_INVALID_ARG; 342 } 343 344 /* Read the first byte from the frame to get the FIN flag and Opcode */ 345 /* Please refer to RFC6455 Section 5.2 for more details */ 346 uint8_t first_byte = 0; 347 if (httpd_recv_with_opt(req, (char *)&first_byte, sizeof(first_byte), false) <= 0) { 348 /* If the recv() return code is <= 0, then this socket FD is invalid (i.e. a broken connection) */ 349 /* Here we mark it as a Close message and close it later. */ 350 ESP_LOGW(TAG, LOG_FMT("Failed to read header byte (socket FD invalid), closing socket now")); 351 aux->ws_final = true; 352 aux->ws_type = HTTPD_WS_TYPE_CLOSE; 353 return ESP_OK; 354 } 355 356 ESP_LOGD(TAG, LOG_FMT("First byte received: 0x%02X"), first_byte); 357 358 /* Decode the FIN flag and Opcode from the byte */ 359 aux->ws_final = (first_byte & HTTPD_WS_FIN_BIT) != 0; 360 aux->ws_type = (first_byte & HTTPD_WS_OPCODE_BITS); 361 362 /* Reply to PING. For PONG and CLOSE, it will be handled elsewhere. */ 363 if(aux->ws_type == HTTPD_WS_TYPE_PING) { 364 ESP_LOGD(TAG, LOG_FMT("Got a WS PING frame, Replying PONG...")); 365 366 /* Read the rest of the PING frame, for PONG to reply back. */ 367 /* Please refer to RFC6455 Section 5.5.2 for more details */ 368 httpd_ws_frame_t frame; 369 uint8_t frame_buf[128] = { 0 }; 370 memset(&frame, 0, sizeof(httpd_ws_frame_t)); 371 frame.payload = frame_buf; 372 373 if(httpd_ws_recv_frame(req, &frame, 126) != ESP_OK) { 374 ESP_LOGD(TAG, LOG_FMT("Cannot receive the full PING frame")); 375 return ESP_ERR_INVALID_STATE; 376 } 377 378 /* Now turn the frame to PONG */ 379 frame.type = HTTPD_WS_TYPE_PONG; 380 return httpd_ws_send_frame(req, &frame); 381 } 382 383 return ESP_OK; 384 } 385 386 httpd_ws_client_info_t httpd_ws_get_fd_info(httpd_handle_t hd, int fd) 387 { 388 struct sock_db *sess = httpd_sess_get(hd, fd); 389 390 if (sess == NULL) { 391 return HTTPD_WS_CLIENT_INVALID; 392 } 393 bool is_active_ws = sess->ws_handshake_done && (!sess->ws_close); 394 return is_active_ws ? HTTPD_WS_CLIENT_WEBSOCKET : HTTPD_WS_CLIENT_HTTP; 395 } 396 397 #endif /* CONFIG_HTTPD_WS_SUPPORT */