/ components / esp_http_server / src / httpd_ws.c
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 */