http_server: adds WebSocket support

This commit adds the WebSocket support for esp_http_server
library. It mainly does:

- Handling WebSocket handshake
  - Parsing HTTP upgrade request
  - Reply the upgrade request
- Receive WebSocket packets
  - Parse header, decode to a struct
  - Unmask payload (if required)
- Send WebSocket frames
- Receive WebSocket frame
- Automatic control frame handling

Merges https://github.com/espressif/esp-idf/pull/4306
Closes https://github.com/espressif/esp-idf/issues/4819
This commit is contained in:
Jackson Ming Hu 2019-11-04 20:51:59 +11:00 committed by bot
parent 5172724092
commit e983042af2
8 changed files with 597 additions and 8 deletions

View file

@ -3,8 +3,9 @@ idf_component_register(SRCS "src/httpd_main.c"
"src/httpd_sess.c"
"src/httpd_txrx.c"
"src/httpd_uri.c"
"src/httpd_ws.c"
"src/util/ctrl_sock.c"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src/port/esp32" "src/util"
REQUIRES nghttp # for http_parser.h
PRIV_REQUIRES lwip esp_timer)
PRIV_REQUIRES lwip mbedtls esp_timer)

View file

@ -39,4 +39,10 @@ menu "HTTP Server"
Enabling this will log discarded binary HTTP request data at Debug level.
For large content data this may not be desirable as it will clutter the log.
config HTTPD_WS_SUPPORT
bool "WebSocket server support"
default y
help
This sets the WebSocket server support.
endmenu

View file

@ -399,6 +399,14 @@ typedef struct httpd_uri {
* Pointer to user context data which will be available to handler
*/
void *user_ctx;
#ifdef CONFIG_HTTPD_WS_SUPPORT
/**
* Flag for indicating a WebSocket endpoint.
* If this flag is true, then method must be HTTP_GET. Otherwise the handshake will not be handled.
*/
bool is_websocket;
#endif
} httpd_uri_t;
/**
@ -1452,6 +1460,65 @@ esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *ar
* @}
*/
/* ************** Group: WebSocket ************** */
/** @name WebSocket
* Functions and structs for WebSocket server
* @{
*/
#ifdef CONFIG_HTTPD_WS_SUPPORT
/**
* @brief Enum for WebSocket packet types (Opcode in the header)
* @note Please refer to RFC6455 Section 5.4 for more details
*/
typedef enum {
HTTPD_WS_TYPE_CONTINUE = 0x0,
HTTPD_WS_TYPE_TEXT = 0x1,
HTTPD_WS_TYPE_BINARY = 0x2,
HTTPD_WS_TYPE_CLOSE = 0x8,
HTTPD_WS_TYPE_PING = 0x9,
HTTPD_WS_TYPE_PONG = 0xA
} httpd_ws_type_t;
/**
* @brief WebSocket frame format
*/
typedef struct httpd_ws_frame {
bool final; /*!< Final frame */
httpd_ws_type_t type; /*!< WebSocket frame type */
uint8_t *payload; /*!< Pre-allocated data buffer */
size_t len; /*!< Length of the WebSocket data */
} httpd_ws_frame_t;
/**
* @brief Receive and parse a WebSocket frame
* @param[in] req Current request
* @param[out] pkt WebSocket packet
* @param[in] max_len Maximum length for receive
* @return
* - ESP_OK : On successful
* - ESP_FAIL : Socket errors occurs
* - ESP_ERR_INVALID_STATE : Handshake was already done beforehand
* - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket)
*/
esp_err_t httpd_ws_recv_frame(httpd_req_t *req, httpd_ws_frame_t *pkt, size_t max_len);
/**
* @brief Construct and send a WebSocket frame
* @param[in] req Current request
* @param[in] pkt WebSocket frame
* @return
* - ESP_OK : On successful
* - ESP_FAIL : When socket errors occurs
* - ESP_ERR_INVALID_STATE : Handshake was already done beforehand
* - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket)
*/
esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *pkt);
#endif /* CONFIG_HTTPD_WS_SUPPORT */
/** End of WebSocket related stuff
* @}
*/
#ifdef __cplusplus
}
#endif

View file

@ -71,6 +71,11 @@ struct sock_db {
uint64_t lru_counter; /*!< LRU Counter indicating when the socket was last used */
char pending_data[PARSER_BLOCK_SIZE]; /*!< Buffer for pending data to be received */
size_t pending_len; /*!< Length of pending data to be received */
#ifdef CONFIG_HTTPD_WS_SUPPORT
bool ws_handshake_done; /*!< True if it has done WebSocket handshake (if this socket is a valid WS) */
bool ws_close; /*!< Set to true to close the socket later (when WS Close frame received) */
esp_err_t (*ws_handler)(httpd_req_t *r); /*!< WebSocket handler, leave to null if it's not WebSocket */
#endif
};
/**
@ -91,6 +96,11 @@ struct httpd_req_aux {
const char *value;
} *resp_hdrs; /*!< Additional headers in response packet */
struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */
#ifdef CONFIG_HTTPD_WS_SUPPORT
bool ws_handshake_detect; /*!< WebSocket handshake detection flag */
httpd_ws_type_t ws_type; /*!< WebSocket frame type */
bool ws_final; /*!< WebSocket FIN bit (final frame or not) */
#endif
};
/**
@ -471,6 +481,44 @@ int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len,
* @}
*/
/* ************** Group: WebSocket ************** */
/** @name WebSocket
* Functions for WebSocket header parsing
* @{
*/
/**
* @brief This function is for responding a WebSocket handshake
*
* @param[in] req Pointer to handshake request that will be handled
* @return
* - ESP_OK : When handshake is sucessful
* - ESP_ERR_NOT_FOUND : When some headers (Sec-WebSocket-*) are not found
* - ESP_ERR_INVALID_VERSION : The WebSocket version is not "13"
* - ESP_ERR_INVALID_STATE : Handshake was done beforehand
* - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket)
* - ESP_FAIL : Socket failures
*/
esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req);
/**
* @brief This function is for getting a frame type
* and responding a WebSocket control frame automatically
*
* @param[in] req Pointer to handshake request that will be handled
* @return
* - ESP_OK : When handshake is sucessful
* - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket)
* - ESP_ERR_INVALID_STATE : Received only some parts of a control frame
* - ESP_FAIL : Socket failures
*/
esp_err_t httpd_ws_get_frame_type(httpd_req_t *req);
/** End of WebSocket related functions
* @}
*/
#ifdef __cplusplus
}
#endif

View file

@ -374,13 +374,36 @@ static esp_err_t cb_headers_complete(http_parser *parser)
ESP_LOGD(TAG, LOG_FMT("bytes read = %d"), parser->nread);
ESP_LOGD(TAG, LOG_FMT("content length = %zu"), r->content_len);
/* Handle upgrade requests - only WebSocket is supported for now */
if (parser->upgrade) {
ESP_LOGW(TAG, LOG_FMT("upgrade from HTTP not supported"));
/* There is no specific HTTP error code to notify the client that
* upgrade is not supported, thus sending 400 Bad Request */
#ifdef CONFIG_HTTPD_WS_SUPPORT
ESP_LOGD(TAG, LOG_FMT("Got an upgrade request"));
/* If there's no "Upgrade" header field, then it's not WebSocket. */
char ws_upgrade_hdr_val[] = "websocket";
if (httpd_req_get_hdr_value_str(r, "Upgrade", ws_upgrade_hdr_val, sizeof(ws_upgrade_hdr_val)) != ESP_OK) {
ESP_LOGW(TAG, LOG_FMT("Upgrade header does not match the length of \"websocket\""));
parser_data->error = HTTPD_400_BAD_REQUEST;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* If "Upgrade" field's key is not "websocket", then we should also forget about it. */
if (strcasecmp("websocket", ws_upgrade_hdr_val) != 0) {
ESP_LOGW(TAG, LOG_FMT("Upgrade header found but it's %s"), ws_upgrade_hdr_val);
parser_data->error = HTTPD_400_BAD_REQUEST;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* Now set handshake flag to true */
ra->ws_handshake_detect = true;
#else
ESP_LOGD(TAG, LOG_FMT("WS functions has been disabled, Upgrade request is not supported."));
parser_data->error = HTTPD_400_BAD_REQUEST;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
#endif
}
parser_data->status = PARSING_BODY;
@ -667,6 +690,7 @@ static void init_req_aux(struct httpd_req_aux *ra, httpd_config_t *config)
ra->first_chunk_sent = 0;
ra->req_hdrs_count = 0;
ra->resp_hdrs_count = 0;
ra->ws_handshake_detect = false;
memset(ra->resp_hdrs, 0, config->max_resp_headers * sizeof(struct resp_hdr));
}
@ -678,6 +702,13 @@ static void httpd_req_cleanup(httpd_req_t *r)
if ((r->ignore_sess_ctx_changes == false) && (ra->sd->ctx != r->sess_ctx)) {
httpd_sess_free_ctx(ra->sd->ctx, ra->sd->free_ctx);
}
/* Close the socket when a WebSocket Close request is received */
if (ra->sd->ws_close) {
ESP_LOGD(TAG, LOG_FMT("Try closing WS connection at FD: %d"), ra->sd->fd);
httpd_sess_trigger_close(r->handle, ra->sd->fd);
}
/* Retrieve session info from the request into the socket database. */
ra->sd->ctx = r->sess_ctx;
ra->sd->free_ctx = r->free_ctx;
@ -699,23 +730,62 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
init_req_aux(&hd->hd_req_aux, &hd->config);
r->handle = hd;
r->aux = &hd->hd_req_aux;
/* Associate the request to the socket */
struct httpd_req_aux *ra = r->aux;
ra->sd = sd;
/* Set defaults */
ra->status = (char *)HTTPD_200;
ra->content_type = (char *)HTTPD_TYPE_TEXT;
ra->first_chunk_sent = false;
/* Copy session info to the request */
r->sess_ctx = sd->ctx;
r->free_ctx = sd->free_ctx;
r->ignore_sess_ctx_changes = sd->ignore_sess_ctx_changes;
esp_err_t ret;
#ifdef CONFIG_HTTPD_WS_SUPPORT
/* Handle WebSocket */
ESP_LOGD(TAG, LOG_FMT("New request, has WS? %s, sd->ws_handler valid? %s, sd->ws_close? %s"),
sd->ws_handshake_done ? "Yes" : "No",
sd->ws_handler != NULL ? "Yes" : "No",
sd->ws_close ? "Yes" : "No");
if (sd->ws_handshake_done && sd->ws_handler != NULL) {
ESP_LOGD(TAG, LOG_FMT("New WS request from existing socket"));
ret = httpd_ws_get_frame_type(r);
/* Stop and return here immediately if it's a CLOSE frame */
if (ra->ws_type == HTTPD_WS_TYPE_CLOSE) {
sd->ws_close = true;
return ret;
}
/* Ignore PONG frame, as this is a server */
if (ra->ws_type == HTTPD_WS_TYPE_PONG) {
return ret;
}
/* Call handler if it's a non-control frame */
if (ret == ESP_OK && ra->ws_type < HTTPD_WS_TYPE_CLOSE) {
ret = sd->ws_handler(r);
}
if (ret != ESP_OK) {
httpd_req_cleanup(r);
}
return ret;
}
#endif
/* Parse request */
esp_err_t err = httpd_parse_req(hd);
if (err != ESP_OK) {
ret = httpd_parse_req(hd);
if (ret != ESP_OK) {
httpd_req_cleanup(r);
}
return err;
return ret;
}
/* Function that resets the http request data

View file

@ -281,7 +281,9 @@ bool httpd_sess_pending(struct httpd_data *hd, int fd)
if (sd->pending_fn) {
// test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
// this should check e.g. for the SSL data buffer
if (sd->pending_fn(hd, fd) > 0) return true;
if (sd->pending_fn(hd, fd) > 0) {
return true;
}
}
return (sd->pending_len != 0);

View file

@ -172,6 +172,9 @@ esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
hd->hd_calls[i]->method = uri_handler->method;
hd->hd_calls[i]->handler = uri_handler->handler;
hd->hd_calls[i]->user_ctx = uri_handler->user_ctx;
#ifdef CONFIG_HTTPD_WS_SUPPORT
hd->hd_calls[i]->is_websocket = uri_handler->is_websocket;
#endif
ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri);
return ESP_OK;
}
@ -276,6 +279,7 @@ esp_err_t httpd_uri(struct httpd_data *hd)
{
httpd_uri_t *uri = NULL;
httpd_req_t *req = &hd->hd_req;
struct httpd_req_aux *aux = req->aux;
struct http_parser_url *res = &hd->hd_req_aux.url_parse_res;
/* For conveying URI not found/method not allowed */
@ -307,6 +311,23 @@ esp_err_t httpd_uri(struct httpd_data *hd)
/* Attach user context data (passed during URI registration) into request */
req->user_ctx = uri->user_ctx;
/* Final step for a WebSocket handshake verification */
#ifdef CONFIG_HTTPD_WS_SUPPORT
if (uri->is_websocket && aux->ws_handshake_detect && uri->method == HTTP_GET) {
ESP_LOGD(TAG, LOG_FMT("Responding WS handshake to sock %d"), aux->sd->fd);
esp_err_t ret = httpd_ws_respond_server_handshake(&hd->hd_req);
if (ret != ESP_OK) {
return ret;
}
aux->sd->ws_handshake_done = true;
aux->sd->ws_handler = uri->handler;
/* Return immediately after handshake, no need to call handler here */
return ESP_OK;
}
#endif
/* Invoke handler */
if (uri->handler(req) != ESP_OK) {
/* Handler returns error, this socket should be closed */

View file

@ -0,0 +1,374 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <sys/random.h>
#include <esp_log.h>
#include <esp_err.h>
#include <mbedtls/sha1.h>
#include <mbedtls/base64.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.h"
#ifdef CONFIG_HTTPD_WS_SUPPORT
#define TAG "httpd_ws"
/*
* Bit masks for WebSocket frames.
* Please refer to RFC6455 Section 5.2 for more details.
*/
#define HTTPD_WS_FIN_BIT 0x80U
#define HTTPD_WS_OPCODE_BITS 0x0fU
#define HTTPD_WS_MASK_BIT 0x80U
#define HTTPD_WS_LENGTH_BITS 0x7fU
/*
* The magic GUID string used for handshake
* Please refer to RFC6455 Section 1.3 for more details.
*/
static const char ws_magic_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req)
{
/* Probe if input parameters are valid or not */
if (!req || !req->aux) {
ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
return ESP_ERR_INVALID_ARG;
}
/* Detect handshake - reject if handshake was ALREADY performed */
struct httpd_req_aux *req_aux = req->aux;
if (req_aux->sd->ws_handshake_done) {
ESP_LOGW(TAG, LOG_FMT("State is invalid - Handshake has been performed"));
return ESP_ERR_INVALID_STATE;
}
/* Detect WS version existence */
char version_val[3] = { '\0' };
if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Version", version_val, sizeof(version_val)) != ESP_OK) {
ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not found"));
return ESP_ERR_NOT_FOUND;
}
/* Detect if WS version is "13" or not.
* WS version must be 13 for now. Please refer to RFC6455 Section 4.1, Page 18 for more details. */
if (strcasecmp(version_val, "13") != 0) {
ESP_LOGW(TAG, LOG_FMT("\"Sec-WebSocket-Version\" is not \"13\", it is: %s"), version_val);
return ESP_ERR_INVALID_VERSION;
}
/* Grab Sec-WebSocket-Key (client key) from the header */
/* Size of base64 coded string is equal '((input_size * 4) / 3) + (input_size / 96) + 6' including Z-term */
char sec_key_encoded[28] = { '\0' };
if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Key", sec_key_encoded, sizeof(sec_key_encoded)) != ESP_OK) {
ESP_LOGW(TAG, LOG_FMT("Cannot find client key"));
return ESP_ERR_NOT_FOUND;
}
/* Prepare server key (Sec-WebSocket-Accept), concat the string */
char server_key_encoded[33] = { '\0' };
uint8_t server_key_hash[20] = { 0 };
char server_raw_text[sizeof(sec_key_encoded) + sizeof(ws_magic_uuid) + 1] = { '\0' };
strcpy(server_raw_text, sec_key_encoded);
strcat(server_raw_text, ws_magic_uuid);
ESP_LOGD(TAG, LOG_FMT("Server key before encoding: %s"), server_raw_text);
/* Generate SHA-1 first and then encode to Base64 */
size_t key_len = strlen(server_raw_text);
mbedtls_sha1_ret((uint8_t *)server_raw_text, key_len, server_key_hash);
size_t encoded_len = 0;
mbedtls_base64_encode((uint8_t *)server_key_encoded, sizeof(server_key_encoded), &encoded_len,
server_key_hash, sizeof(server_key_hash));
ESP_LOGD(TAG, LOG_FMT("Generated server key: %s"), server_key_encoded);
/* Prepare the Switching Protocol response */
char tx_buf[192] = { '\0' };
int fmt_len = snprintf(tx_buf, sizeof(tx_buf),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n\r\n", server_key_encoded);
if (fmt_len < 0 || fmt_len > sizeof(tx_buf)) {
ESP_LOGW(TAG, LOG_FMT("Failed to prepare Tx buffer"));
return ESP_FAIL;
}
/* Send off the response */
if (httpd_send(req, tx_buf, fmt_len) < 0) {
ESP_LOGW(TAG, LOG_FMT("Failed to send the response"));
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t httpd_ws_check_req(httpd_req_t *req)
{
/* Probe if input parameters are valid or not */
if (!req || !req->aux) {
ESP_LOGW(TAG, LOG_FMT("Argument is null"));
return ESP_ERR_INVALID_ARG;
}
/* Detect handshake - reject if handshake was NOT YET performed */
struct httpd_req_aux *req_aux = req->aux;
if (!req_aux->sd->ws_handshake_done) {
ESP_LOGW(TAG, LOG_FMT("State is invalid - No handshake performed"));
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
static esp_err_t httpd_ws_unmask_payload(uint8_t *payload, size_t len, const uint8_t *mask_key)
{
if (len < 1 || !payload) {
ESP_LOGW(TAG, LOG_FMT("Invalid payload provided"));
return ESP_ERR_INVALID_ARG;
}
for (size_t idx = 0; idx < len; idx++) {
payload[idx] = (payload[idx] ^ mask_key[idx % 4]);
}
return ESP_OK;
}
esp_err_t httpd_ws_recv_frame(httpd_req_t *req, httpd_ws_frame_t *frame, size_t max_len)
{
esp_err_t ret = httpd_ws_check_req(req);
if (ret != ESP_OK) {
return ret;
}
struct httpd_req_aux *aux = req->aux;
if (aux == NULL) {
ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer"));
return ESP_ERR_INVALID_ARG;
}
if (!frame) {
ESP_LOGW(TAG, LOG_FMT("Frame pointer is invalid"));
return ESP_ERR_INVALID_ARG;
}
/* Assign the frame info from the previous reading */
frame->type = aux->ws_type;
frame->final = aux->ws_final;
/* Grab the second byte */
uint8_t second_byte = 0;
if (httpd_recv_with_opt(req, (char *)&second_byte, sizeof(second_byte), false) <= 0) {
ESP_LOGW(TAG, LOG_FMT("Failed to receive the second byte"));
return ESP_FAIL;
}
/* Parse the second byte */
/* Please refer to RFC6455 Section 5.2 for more details */
bool masked = (second_byte & HTTPD_WS_MASK_BIT) != 0;
/* Interpret length */
uint8_t init_len = second_byte & HTTPD_WS_LENGTH_BITS;
if (init_len < 126) {
/* Case 1: If length is 0-125, then this length bit is 7 bits */
frame->len = init_len;
} else if (init_len == 126) {
/* Case 2: If length byte is 126, then this frame's length bit is 16 bits */
uint8_t length_bytes[2] = { 0 };
if (httpd_recv_with_opt(req, (char *)length_bytes, sizeof(length_bytes), false) <= 0) {
ESP_LOGW(TAG, LOG_FMT("Failed to receive 2 bytes length"));
return ESP_FAIL;
}
frame->len = ((uint32_t)(length_bytes[0] << 8U) | (length_bytes[1]));
} else if (init_len == 127) {
/* Case 3: If length is byte 127, then this frame's length bit is 64 bits */
uint8_t length_bytes[8] = { 0 };
if (httpd_recv_with_opt(req, (char *)length_bytes, sizeof(length_bytes), false) <= 0) {
ESP_LOGW(TAG, LOG_FMT("Failed to receive 2 bytes length"));
return ESP_FAIL;
}
frame->len = (((uint64_t)length_bytes[0] << 56U) |
((uint64_t)length_bytes[1] << 48U) |
((uint64_t)length_bytes[2] << 40U) |
((uint64_t)length_bytes[3] << 32U) |
((uint64_t)length_bytes[4] << 24U) |
((uint64_t)length_bytes[5] << 16U) |
((uint64_t)length_bytes[6] << 8U) |
((uint64_t)length_bytes[7]));
}
/* We only accept the incoming packet length that is smaller than the max_len (or it will overflow the buffer!) */
if (frame->len > max_len) {
ESP_LOGW(TAG, LOG_FMT("WS Message too long"));
return ESP_ERR_INVALID_SIZE;
}
/* If this frame is masked, dump the mask as well */
uint8_t mask_key[4] = { 0 };
if (masked) {
if (httpd_recv_with_opt(req, (char *)mask_key, sizeof(mask_key), false) <= 0) {
ESP_LOGW(TAG, LOG_FMT("Failed to receive mask key"));
return ESP_FAIL;
}
} else {
/* If the WS frame from client to server is not masked, it should be rejected.
* Please refer to RFC6455 Section 5.2 for more details. */
ESP_LOGW(TAG, LOG_FMT("WS frame is not properly masked."));
return ESP_ERR_INVALID_STATE;
}
/* Receive buffer */
/* If there's nothing to receive, return and stop here. */
if (frame->len == 0) {
return ESP_OK;
}
if (frame->payload == NULL) {
ESP_LOGW(TAG, LOG_FMT("Payload buffer is null"));
return ESP_FAIL;
}
if (httpd_recv_with_opt(req, (char *)frame->payload, frame->len, false) <= 0) {
ESP_LOGW(TAG, LOG_FMT("Failed to receive payload"));
return ESP_FAIL;
}
/* Unmask payload */
httpd_ws_unmask_payload(frame->payload, frame->len, mask_key);
return ESP_OK;
}
esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *frame)
{
esp_err_t ret = httpd_ws_check_req(req);
if (ret != ESP_OK) {
return ret;
}
if (!frame) {
ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
return ESP_ERR_INVALID_ARG;
}
/* Prepare Tx buffer - maximum length is 14, which includes 2 bytes header, 8 bytes length, 4 bytes mask key */
uint8_t tx_len = 0;
uint8_t header_buf[10] = {0 };
header_buf[0] |= frame->final ? HTTPD_WS_FIN_BIT : 0; /* Final (FIN) bit */
header_buf[0] |= frame->type; /* Type (opcode): 4 bits */
if (frame->len <= 125) {
header_buf[1] = frame->len & 0x7fU; /* Length for 7 bits */
tx_len = 2;
} else if (frame->len > 125 && frame->len < UINT16_MAX) {
header_buf[1] = 126; /* Length for 16 bits */
header_buf[2] = (frame->len >> 8U) & 0xffU;
header_buf[3] = frame->len & 0xffU;
tx_len = 4;
} else {
header_buf[1] = 127; /* Length for 64 bits */
uint8_t shift_idx = sizeof(uint64_t) - 1; /* Shift index starts at 7 */
for (int8_t idx = 2; idx > 9; idx--) {
/* Now do shifting (be careful of endianess, i.e. when buffer index is 2, frame length shift index is 7) */
header_buf[idx] = (frame->len >> (uint8_t)(shift_idx * 8)) & 0xffU;
shift_idx--;
}
tx_len = 10;
}
/* WebSocket server does not required to mask response payload, so leave the MASK bit as 0. */
header_buf[1] &= (~HTTPD_WS_MASK_BIT);
/* Send off header */
if (httpd_send(req, (const char *)header_buf, tx_len) < 0) {
ESP_LOGW(TAG, LOG_FMT("Failed to send WS header"));
return ESP_FAIL;
}
/* Send off payload */
if(frame->len > 0 && frame->payload != NULL) {
if (httpd_send(req, (const char *)frame->payload, frame->len) < 0) {
ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload"));
return ESP_FAIL;
}
}
return ESP_OK;
}
esp_err_t httpd_ws_get_frame_type(httpd_req_t *req)
{
esp_err_t ret = httpd_ws_check_req(req);
if (ret != ESP_OK) {
return ret;
}
struct httpd_req_aux *aux = req->aux;
if (aux == NULL) {
ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer"));
return ESP_ERR_INVALID_ARG;
}
/* Read the first byte from the frame to get the FIN flag and Opcode */
/* Please refer to RFC6455 Section 5.2 for more details */
uint8_t first_byte = 0;
if (httpd_recv_with_opt(req, (char *)&first_byte, sizeof(first_byte), false) <= 0) {
/* If the recv() return code is <= 0, then this socket FD is invalid (i.e. a broken connection) */
/* Here we mark it as a Close message and close it later. */
ESP_LOGW(TAG, LOG_FMT("Failed to read header byte (socket FD invalid), closing socket now"));
aux->ws_final = true;
aux->ws_type = HTTPD_WS_TYPE_CLOSE;
return ESP_OK;
}
ESP_LOGD(TAG, LOG_FMT("First byte received: 0x%02X"), first_byte);
/* Decode the FIN flag and Opcode from the byte */
aux->ws_final = (first_byte & HTTPD_WS_FIN_BIT) != 0;
aux->ws_type = (first_byte & HTTPD_WS_OPCODE_BITS);
/* Reply to PING. For PONG and CLOSE, it will be handled elsewhere. */
if(aux->ws_type == HTTPD_WS_TYPE_PING) {
ESP_LOGD(TAG, LOG_FMT("Got a WS PING frame, Replying PONG..."));
/* Read the rest of the PING frame, for PONG to reply back. */
/* Please refer to RFC6455 Section 5.5.2 for more details */
httpd_ws_frame_t frame;
uint8_t frame_buf[128] = { 0 };
memset(&frame, 0, sizeof(httpd_ws_frame_t));
frame.payload = frame_buf;
if(httpd_ws_recv_frame(req, &frame, 126) != ESP_OK) {
ESP_LOGD(TAG, LOG_FMT("Cannot receive the full PING frame"));
return ESP_ERR_INVALID_STATE;
}
/* Now turn the frame to PONG */
frame.type = HTTPD_WS_TYPE_PONG;
return httpd_ws_send_frame(req, &frame);
}
return ESP_OK;
}
#endif /* CONFIG_HTTPD_WS_SUPPORT */