1 /*
2  * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 
8 
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/random.h>
12 #include <esp_log.h>
13 #include <esp_err.h>
14 #include <mbedtls/sha1.h>
15 #include <mbedtls/base64.h>
16 
17 #include <esp_http_server.h>
18 #include "esp_httpd_priv.h"
19 #include "freertos/event_groups.h"
20 
21 #ifdef CONFIG_HTTPD_WS_SUPPORT
22 
23 #define WS_SEND_OK      (1 << 0)
24 #define WS_SEND_FAILED  (1 << 1)
25 
26 typedef struct {
27     httpd_ws_frame_t frame;
28     httpd_handle_t handle;
29     int socket;
30     transfer_complete_cb callback;
31     void *arg;
32     bool blocking;
33     EventGroupHandle_t transfer_done;
34 } async_transfer_t;
35 
36 static const char *TAG="httpd_ws";
37 
38 /*
39  * Bit masks for WebSocket frames.
40  * Please refer to RFC6455 Section 5.2 for more details.
41  */
42 #define HTTPD_WS_CONTINUE       0x00U
43 #define HTTPD_WS_FIN_BIT        0x80U
44 #define HTTPD_WS_OPCODE_BITS    0x0fU
45 #define HTTPD_WS_MASK_BIT       0x80U
46 #define HTTPD_WS_LENGTH_BITS    0x7fU
47 
48 /*
49  * The magic GUID string used for handshake
50  * Please refer to RFC6455 Section 1.3 for more details.
51  */
52 static const char ws_magic_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
53 
54 /* Checks if any subprotocols from the comma seperated list matches the supported one
55  *
56  * Returns true if the response should contain a protocol field
57 */
58 
59 /**
60  * @brief Checks if any subprotocols from the comma seperated list matches the supported one
61  *
62  * @param supported_subprotocol[in] The subprotocol supported by the URI
63  * @param subprotocol[in],  [in]: A comma seperate list of subprotocols requested
64  * @param buf_len Length of the buffer
65  * @return true: found a matching subprotocol
66  * @return false
67  */
httpd_ws_get_response_subprotocol(const char * supported_subprotocol,char * subprotocol,size_t buf_len)68 static bool httpd_ws_get_response_subprotocol(const char *supported_subprotocol, char *subprotocol, size_t buf_len)
69 {
70     /* Request didnt contain any subprotocols */
71     if (strnlen(subprotocol, buf_len) == 0) {
72         return false;
73     }
74 
75     if (supported_subprotocol == NULL) {
76         ESP_LOGW(TAG, "Sec-WebSocket-Protocol %s not supported, URI do not support any subprotocols", subprotocol);
77         return false;
78     }
79 
80     /* Get first subprotocol from comma seperated list */
81     char *rest = NULL;
82     char *s = strtok_r(subprotocol, ", ", &rest);
83     do {
84         if (strncmp(s, supported_subprotocol, sizeof(subprotocol)) == 0) {
85             ESP_LOGD(TAG, "Requested subprotocol supported: %s", s);
86             return true;
87         }
88     } while ((s = strtok_r(NULL, ", ", &rest)) != NULL);
89 
90     ESP_LOGW(TAG, "Sec-WebSocket-Protocol %s not supported, supported subprotocol is %s", subprotocol, supported_subprotocol);
91 
92     /* No matches */
93     return false;
94 
95 }
96 
httpd_ws_respond_server_handshake(httpd_req_t * req,const char * supported_subprotocol)97 esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req, const char *supported_subprotocol)
98 {
99     /* Probe if input parameters are valid or not */
100     if (!req || !req->aux) {
101         ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
102         return ESP_ERR_INVALID_ARG;
103     }
104 
105     /* Detect handshake - reject if handshake was ALREADY performed */
106     struct httpd_req_aux *req_aux = req->aux;
107     if (req_aux->sd->ws_handshake_done) {
108         ESP_LOGW(TAG, LOG_FMT("State is invalid - Handshake has been performed"));
109         return ESP_ERR_INVALID_STATE;
110     }
111 
112     /* Detect WS version existence */
113     char version_val[3] = { '\0' };
114     if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Version", version_val, sizeof(version_val)) != ESP_OK) {
115         ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not found"));
116         return ESP_ERR_NOT_FOUND;
117     }
118 
119     /* Detect if WS version is "13" or not.
120      * WS version must be 13 for now. Please refer to RFC6455 Section 4.1, Page 18 for more details. */
121     if (strcasecmp(version_val, "13") != 0) {
122         ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not \"13\", it is: %s"), version_val);
123         return ESP_ERR_INVALID_VERSION;
124     }
125 
126     /* Grab Sec-WebSocket-Key (client key) from the header */
127     /* Size of base64 coded string is equal '((input_size * 4) / 3) + (input_size / 96) + 6' including Z-term */
128     char sec_key_encoded[28] = { '\0' };
129     if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Key", sec_key_encoded, sizeof(sec_key_encoded)) != ESP_OK) {
130         ESP_LOGW(TAG, LOG_FMT("Cannot find client key"));
131         return ESP_ERR_NOT_FOUND;
132     }
133 
134     /* Prepare server key (Sec-WebSocket-Accept), concat the string */
135     char server_key_encoded[33] = { '\0' };
136     uint8_t server_key_hash[20] = { 0 };
137     char server_raw_text[sizeof(sec_key_encoded) + sizeof(ws_magic_uuid) + 1] = { '\0' };
138 
139     strcpy(server_raw_text, sec_key_encoded);
140     strcat(server_raw_text, ws_magic_uuid);
141 
142     ESP_LOGD(TAG, LOG_FMT("Server key before encoding: %s"), server_raw_text);
143 
144     /* Generate SHA-1 first and then encode to Base64 */
145     size_t key_len = strlen(server_raw_text);
146     mbedtls_sha1_ret((uint8_t *)server_raw_text, key_len, server_key_hash);
147 
148     size_t encoded_len = 0;
149     mbedtls_base64_encode((uint8_t *)server_key_encoded, sizeof(server_key_encoded), &encoded_len,
150                           server_key_hash, sizeof(server_key_hash));
151 
152     ESP_LOGD(TAG, LOG_FMT("Generated server key: %s"), server_key_encoded);
153 
154     char subprotocol[50] = { '\0' };
155     if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Protocol", subprotocol, sizeof(subprotocol) - 1) == ESP_ERR_HTTPD_RESULT_TRUNC) {
156         ESP_LOGW(TAG, "Sec-WebSocket-Protocol length exceeded buffer size of %d, was trunctated", sizeof(subprotocol));
157     }
158 
159 
160     /* Prepare the Switching Protocol response */
161     char tx_buf[192] = { '\0' };
162     int fmt_len = snprintf(tx_buf, sizeof(tx_buf),
163                            "HTTP/1.1 101 Switching Protocols\r\n"
164                            "Upgrade: websocket\r\n"
165                            "Connection: Upgrade\r\n"
166                            "Sec-WebSocket-Accept: %s\r\n", server_key_encoded);
167 
168     if (fmt_len < 0 || fmt_len > sizeof(tx_buf)) {
169         ESP_LOGW(TAG, LOG_FMT("Failed to prepare Tx buffer"));
170         return ESP_FAIL;
171     }
172 
173     if ( httpd_ws_get_response_subprotocol(supported_subprotocol, subprotocol, sizeof(subprotocol))) {
174         ESP_LOGD(TAG, "subprotocol: %s", subprotocol);
175         int r = snprintf(tx_buf + fmt_len, sizeof(tx_buf) - fmt_len, "Sec-WebSocket-Protocol: %s\r\n", supported_subprotocol);
176         if (r <= 0) {
177             ESP_LOGE(TAG, "Error in response generation"
178                           "(snprintf of subprotocol returned %d, buffer size: %d", r, sizeof(tx_buf));
179             return ESP_FAIL;
180         }
181 
182         fmt_len += r;
183 
184         if (fmt_len >= sizeof(tx_buf)) {
185             ESP_LOGE(TAG, "Error in response generation"
186                           "(snprintf of subprotocol returned %d, desired response len: %d, buffer size: %d", r, fmt_len, sizeof(tx_buf));
187             return ESP_FAIL;
188         }
189     }
190 
191     int r = snprintf(tx_buf + fmt_len, sizeof(tx_buf) - fmt_len, "\r\n");
192     if (r <= 0) {
193         ESP_LOGE(TAG, "Error in response generation"
194                         "(snprintf of subprotocol returned %d, buffer size: %d", r, sizeof(tx_buf));
195         return ESP_FAIL;
196     }
197     fmt_len += r;
198     if (fmt_len >= sizeof(tx_buf)) {
199         ESP_LOGE(TAG, "Error in response generation"
200                        "(snprintf of header terminal returned %d, desired response len: %d, buffer size: %d", r, fmt_len, sizeof(tx_buf));
201         return ESP_FAIL;
202     }
203 
204     /* Send off the response */
205     if (httpd_send(req, tx_buf, fmt_len) < 0) {
206         ESP_LOGW(TAG, LOG_FMT("Failed to send the response"));
207         return ESP_FAIL;
208     }
209 
210     return ESP_OK;
211 }
212 
httpd_ws_check_req(httpd_req_t * req)213 static esp_err_t httpd_ws_check_req(httpd_req_t *req)
214 {
215     /* Probe if input parameters are valid or not */
216     if (!req || !req->aux) {
217         ESP_LOGW(TAG, LOG_FMT("Argument is null"));
218         return ESP_ERR_INVALID_ARG;
219     }
220 
221     /* Detect handshake - reject if handshake was NOT YET performed */
222     struct httpd_req_aux *req_aux = req->aux;
223     if (!req_aux->sd->ws_handshake_done) {
224         ESP_LOGW(TAG, LOG_FMT("State is invalid - No handshake performed"));
225         return ESP_ERR_INVALID_STATE;
226     }
227 
228     return ESP_OK;
229 }
230 
httpd_ws_unmask_payload(uint8_t * payload,size_t len,const uint8_t * mask_key)231 static esp_err_t httpd_ws_unmask_payload(uint8_t *payload, size_t len, const uint8_t *mask_key)
232 {
233     if (len < 1 || !payload) {
234         ESP_LOGW(TAG, LOG_FMT("Invalid payload provided"));
235         return ESP_ERR_INVALID_ARG;
236     }
237 
238     for (size_t idx = 0; idx < len; idx++) {
239         payload[idx] = (payload[idx] ^ mask_key[idx % 4]);
240     }
241 
242     return ESP_OK;
243 }
244 
httpd_ws_recv_frame(httpd_req_t * req,httpd_ws_frame_t * frame,size_t max_len)245 esp_err_t httpd_ws_recv_frame(httpd_req_t *req, httpd_ws_frame_t *frame, size_t max_len)
246 {
247     esp_err_t ret = httpd_ws_check_req(req);
248     if (ret != ESP_OK) {
249         return ret;
250     }
251 
252     struct httpd_req_aux *aux = req->aux;
253     if (aux == NULL) {
254         ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer"));
255         return ESP_ERR_INVALID_ARG;
256     }
257 
258     if (!frame) {
259         ESP_LOGW(TAG, LOG_FMT("Frame pointer is invalid"));
260         return ESP_ERR_INVALID_ARG;
261     }
262     /* If frame len is 0, will get frame len from req. Otherwise regard frame len already achieved by calling httpd_ws_recv_frame before */
263     if (frame->len == 0) {
264         /* Assign the frame info from the previous reading */
265         frame->type = aux->ws_type;
266         frame->final = aux->ws_final;
267 
268         /* Grab the second byte */
269         uint8_t second_byte = 0;
270         if (httpd_recv_with_opt(req, (char *)&second_byte, sizeof(second_byte), false) <= 0) {
271             ESP_LOGW(TAG, LOG_FMT("Failed to receive the second byte"));
272             return ESP_FAIL;
273         }
274 
275         /* Parse the second byte */
276         /* Please refer to RFC6455 Section 5.2 for more details */
277         bool masked = (second_byte & HTTPD_WS_MASK_BIT) != 0;
278 
279         /* Interpret length */
280         uint8_t init_len = second_byte & HTTPD_WS_LENGTH_BITS;
281         if (init_len < 126) {
282             /* Case 1: If length is 0-125, then this length bit is 7 bits */
283             frame->len = init_len;
284         } else if (init_len == 126) {
285             /* Case 2: If length byte is 126, then this frame's length bit is 16 bits */
286             uint8_t length_bytes[2] = { 0 };
287             if (httpd_recv_with_opt(req, (char *)length_bytes, sizeof(length_bytes), false) <= 0) {
288                 ESP_LOGW(TAG, LOG_FMT("Failed to receive 2 bytes length"));
289                 return ESP_FAIL;
290             }
291 
292             frame->len = ((uint32_t)(length_bytes[0] << 8U) | (length_bytes[1]));
293         } else if (init_len == 127) {
294             /* Case 3: If length is byte 127, then this frame's length bit is 64 bits */
295             uint8_t length_bytes[8] = { 0 };
296             if (httpd_recv_with_opt(req, (char *)length_bytes, sizeof(length_bytes), false) <= 0) {
297                 ESP_LOGW(TAG, LOG_FMT("Failed to receive 2 bytes length"));
298                 return ESP_FAIL;
299             }
300 
301             frame->len = (((uint64_t)length_bytes[0] << 56U) |
302                     ((uint64_t)length_bytes[1] << 48U) |
303                     ((uint64_t)length_bytes[2] << 40U) |
304                     ((uint64_t)length_bytes[3] << 32U) |
305                     ((uint64_t)length_bytes[4] << 24U) |
306                     ((uint64_t)length_bytes[5] << 16U) |
307                     ((uint64_t)length_bytes[6] <<  8U) |
308                     ((uint64_t)length_bytes[7]));
309         }
310         /* If this frame is masked, dump the mask as well */
311         if (masked) {
312             if (httpd_recv_with_opt(req, (char *)aux->mask_key, sizeof(aux->mask_key), false) <= 0) {
313                 ESP_LOGW(TAG, LOG_FMT("Failed to receive mask key"));
314                 return ESP_FAIL;
315             }
316         } else {
317             /* If the WS frame from client to server is not masked, it should be rejected.
318              * Please refer to RFC6455 Section 5.2 for more details. */
319             ESP_LOGW(TAG, LOG_FMT("WS frame is not properly masked."));
320             return ESP_ERR_INVALID_STATE;
321         }
322     }
323     /* We only accept the incoming packet length that is smaller than the max_len (or it will overflow the buffer!) */
324     /* If max_len is 0, regard it OK for userspace to get frame len */
325     if (frame->len > max_len) {
326         if (max_len == 0) {
327             ESP_LOGD(TAG, "regard max_len == 0 is OK for user to get frame len");
328             return ESP_OK;
329         }
330         ESP_LOGW(TAG, LOG_FMT("WS Message too long"));
331         return ESP_ERR_INVALID_SIZE;
332     }
333 
334     /* Receive buffer */
335     /* If there's nothing to receive, return and stop here. */
336     if (frame->len == 0) {
337         return ESP_OK;
338     }
339 
340     if (frame->payload == NULL) {
341         ESP_LOGW(TAG, LOG_FMT("Payload buffer is null"));
342         return ESP_FAIL;
343     }
344 
345     size_t left_len = frame->len;
346     size_t offset = 0;
347 
348     while (left_len > 0) {
349         int read_len = httpd_recv_with_opt(req, (char *)frame->payload + offset, left_len, false);
350         if (read_len <= 0) {
351             ESP_LOGW(TAG, LOG_FMT("Failed to receive payload"));
352             return ESP_FAIL;
353         }
354         offset += read_len;
355         left_len -= read_len;
356 
357         ESP_LOGD(TAG, "Frame length: %d, Bytes Read: %d", frame->len, offset);
358     }
359 
360     /* Unmask payload */
361     httpd_ws_unmask_payload(frame->payload, frame->len, aux->mask_key);
362 
363     return ESP_OK;
364 }
365 
httpd_ws_send_frame(httpd_req_t * req,httpd_ws_frame_t * frame)366 esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *frame)
367 {
368     esp_err_t ret = httpd_ws_check_req(req);
369     if (ret != ESP_OK) {
370         return ret;
371     }
372     return httpd_ws_send_frame_async(req->handle, httpd_req_to_sockfd(req), frame);
373 }
374 
httpd_ws_send_frame_async(httpd_handle_t hd,int fd,httpd_ws_frame_t * frame)375 esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame)
376 {
377     if (!frame) {
378         ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
379         return ESP_ERR_INVALID_ARG;
380     }
381 
382     /* Prepare Tx buffer - maximum length is 14, which includes 2 bytes header, 8 bytes length, 4 bytes mask key */
383     uint8_t tx_len = 0;
384     uint8_t header_buf[10] = {0 };
385     /* Set the `FIN` bit by default if message is not fragmented. Else, set it as per the `final` field */
386     header_buf[0] |= (!frame->fragmented) ? HTTPD_WS_FIN_BIT : (frame->final? HTTPD_WS_FIN_BIT: HTTPD_WS_CONTINUE);
387     header_buf[0] |= frame->type; /* Type (opcode): 4 bits */
388 
389     if (frame->len <= 125) {
390         header_buf[1] = frame->len & 0x7fU; /* Length for 7 bits */
391         tx_len = 2;
392     } else if (frame->len > 125 && frame->len < UINT16_MAX) {
393         header_buf[1] = 126;                /* Length for 16 bits */
394         header_buf[2] = (frame->len >> 8U) & 0xffU;
395         header_buf[3] = frame->len & 0xffU;
396         tx_len = 4;
397     } else {
398         header_buf[1] = 127;                /* Length for 64 bits */
399         uint8_t shift_idx = sizeof(uint64_t) - 1; /* Shift index starts at 7 */
400         uint64_t len64 = frame->len; /* Raise variable size to make sure we won't shift by more bits
401                                       * than the length has (to avoid undefined behaviour) */
402         for (int8_t idx = 2; idx <= 9; idx++) {
403             /* Now do shifting (be careful of endianness, i.e. when buffer index is 2, frame length shift index is 7) */
404             header_buf[idx] = (len64 >> (shift_idx * 8)) & 0xffU;
405             shift_idx--;
406         }
407         tx_len = 10;
408     }
409 
410     /* WebSocket server does not required to mask response payload, so leave the MASK bit as 0. */
411     header_buf[1] &= (~HTTPD_WS_MASK_BIT);
412 
413     struct sock_db *sess = httpd_sess_get(hd, fd);
414     if (!sess) {
415         return ESP_ERR_INVALID_ARG;
416     }
417 
418     /* Send off header */
419     if (sess->send_fn(hd, fd, (const char *)header_buf, tx_len, 0) < 0) {
420         ESP_LOGW(TAG, LOG_FMT("Failed to send WS header"));
421         return ESP_FAIL;
422     }
423 
424     /* Send off payload */
425     if(frame->len > 0 && frame->payload != NULL) {
426         if (sess->send_fn(hd, fd, (const char *)frame->payload, frame->len, 0) < 0) {
427             ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload"));
428             return ESP_FAIL;
429         }
430     }
431 
432     return ESP_OK;
433 }
434 
httpd_ws_get_frame_type(httpd_req_t * req)435 esp_err_t httpd_ws_get_frame_type(httpd_req_t *req)
436 {
437     esp_err_t ret = httpd_ws_check_req(req);
438     if (ret != ESP_OK) {
439         return ret;
440     }
441 
442     struct httpd_req_aux *aux = req->aux;
443     if (aux == NULL) {
444         ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer"));
445         return ESP_ERR_INVALID_ARG;
446     }
447 
448     /* Read the first byte from the frame to get the FIN flag and Opcode */
449     /* Please refer to RFC6455 Section 5.2 for more details */
450     uint8_t first_byte = 0;
451     if (httpd_recv_with_opt(req, (char *)&first_byte, sizeof(first_byte), false) <= 0) {
452         /* If the recv() return code is <= 0, then this socket FD is invalid (i.e. a broken connection) */
453         /* Here we mark it as a Close message and close it later. */
454         ESP_LOGW(TAG, LOG_FMT("Failed to read header byte (socket FD invalid), closing socket now"));
455         aux->ws_final = true;
456         aux->ws_type = HTTPD_WS_TYPE_CLOSE;
457         return ESP_OK;
458     }
459 
460     ESP_LOGD(TAG, LOG_FMT("First byte received: 0x%02X"), first_byte);
461 
462     /* Decode the FIN flag and Opcode from the byte */
463     aux->ws_final = (first_byte & HTTPD_WS_FIN_BIT) != 0;
464     aux->ws_type = (first_byte & HTTPD_WS_OPCODE_BITS);
465 
466     /* Reply to PING. For PONG and CLOSE, it will be handled elsewhere. */
467     if(aux->ws_type == HTTPD_WS_TYPE_PING) {
468         ESP_LOGD(TAG, LOG_FMT("Got a WS PING frame, Replying PONG..."));
469 
470         /* Read the rest of the PING frame, for PONG to reply back. */
471         /* Please refer to RFC6455 Section 5.5.2 for more details */
472         httpd_ws_frame_t frame;
473         uint8_t frame_buf[128] = { 0 };
474         memset(&frame, 0, sizeof(httpd_ws_frame_t));
475         frame.payload = frame_buf;
476 
477         if(httpd_ws_recv_frame(req, &frame, 126) != ESP_OK) {
478             ESP_LOGD(TAG, LOG_FMT("Cannot receive the full PING frame"));
479             return ESP_ERR_INVALID_STATE;
480         }
481 
482         /* Now turn the frame to PONG */
483         frame.type = HTTPD_WS_TYPE_PONG;
484         return httpd_ws_send_frame(req, &frame);
485     }
486 
487     return ESP_OK;
488 }
489 
httpd_ws_get_fd_info(httpd_handle_t hd,int fd)490 httpd_ws_client_info_t httpd_ws_get_fd_info(httpd_handle_t hd, int fd)
491 {
492     struct sock_db *sess = httpd_sess_get(hd, fd);
493 
494     if (sess == NULL) {
495         return HTTPD_WS_CLIENT_INVALID;
496     }
497     bool is_active_ws = sess->ws_handshake_done && (!sess->ws_close);
498     return is_active_ws ? HTTPD_WS_CLIENT_WEBSOCKET : HTTPD_WS_CLIENT_HTTP;
499 }
500 
httpd_ws_send_cb(void * arg)501 static void httpd_ws_send_cb(void *arg)
502 {
503     async_transfer_t *trans = arg;
504 
505     esp_err_t err = httpd_ws_send_frame_async(trans->handle, trans->socket, &trans->frame);
506 
507     if (trans->blocking) {
508         xEventGroupSetBits(trans->transfer_done, err ? WS_SEND_FAILED : WS_SEND_OK);
509     } else if (trans->callback) {
510         trans->callback(err, trans->socket, trans->arg);
511     }
512 
513     free(trans);
514 }
515 
httpd_ws_send_data(httpd_handle_t handle,int socket,httpd_ws_frame_t * frame)516 esp_err_t httpd_ws_send_data(httpd_handle_t handle, int socket, httpd_ws_frame_t *frame)
517 {
518     async_transfer_t *transfer = calloc(1, sizeof(async_transfer_t));
519     if (transfer == NULL) {
520         return ESP_ERR_NO_MEM;
521     }
522 
523     EventGroupHandle_t transfer_done = xEventGroupCreate();
524     if (!transfer_done) {
525         free(transfer);
526         return ESP_ERR_NO_MEM;
527     }
528 
529     transfer->blocking = true;
530     transfer->handle = handle;
531     transfer->socket = socket;
532     transfer->transfer_done = transfer_done;
533     memcpy(&transfer->frame, frame, sizeof(httpd_ws_frame_t));
534 
535     esp_err_t err = httpd_queue_work(handle, httpd_ws_send_cb, transfer);
536     if (err != ESP_OK) {
537         vEventGroupDelete(transfer_done);
538         free(transfer);
539         return err;
540     }
541 
542     EventBits_t status = xEventGroupWaitBits(transfer_done, WS_SEND_OK | WS_SEND_FAILED,
543                                              pdTRUE, pdFALSE, portMAX_DELAY);
544 
545     vEventGroupDelete(transfer_done);
546 
547     return (status & WS_SEND_OK) ? ESP_OK : ESP_FAIL;
548 }
549 
httpd_ws_send_data_async(httpd_handle_t handle,int socket,httpd_ws_frame_t * frame,transfer_complete_cb callback,void * arg)550 esp_err_t httpd_ws_send_data_async(httpd_handle_t handle, int socket, httpd_ws_frame_t *frame,
551                                    transfer_complete_cb callback, void *arg)
552 {
553     async_transfer_t *transfer = calloc(1, sizeof(async_transfer_t));
554     if (transfer == NULL) {
555         return ESP_ERR_NO_MEM;
556     }
557 
558     transfer->arg = arg;
559     transfer->callback = callback;
560     transfer->handle = handle;
561     transfer->socket = socket;
562     memcpy(&transfer->frame, frame, sizeof(httpd_ws_frame_t));
563 
564     esp_err_t err = httpd_queue_work(handle, httpd_ws_send_cb, transfer);
565 
566     if (err) {
567         free(transfer);
568         return err;
569     }
570 
571     return ESP_OK;
572 }
573 
574 #endif /* CONFIG_HTTPD_WS_SUPPORT */
575