diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c index 483ca29472..65b73f3395 100644 --- a/components/esp-tls/esp_tls.c +++ b/components/esp-tls/esp_tls.c @@ -126,7 +126,7 @@ static ssize_t tcp_read(esp_tls_t *tls, char *data, size_t datalen) static ssize_t tcp_write(esp_tls_t *tls, const char *data, size_t datalen) { - return send(tls->sockfd, data, datalen, 0); + return send(tls->sockfd, data, datalen, MSG_MORE); } ssize_t esp_tls_conn_read(esp_tls_t *tls, void *data, size_t datalen) diff --git a/components/esp_http_server/src/httpd_txrx.c b/components/esp_http_server/src/httpd_txrx.c index 85c40b1b33..b1d3779870 100644 --- a/components/esp_http_server/src/httpd_txrx.c +++ b/components/esp_http_server/src/httpd_txrx.c @@ -56,7 +56,7 @@ int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len) } struct httpd_req_aux *ra = r->aux; - int ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0); + int ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, MSG_MORE); if (ret < 0) { ESP_LOGD(TAG, LOG_FMT("error in send_fn")); return ret; @@ -70,7 +70,7 @@ static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) int ret; while (buf_len > 0) { - ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0); + ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, MSG_MORE); if (ret < 0) { ESP_LOGD(TAG, LOG_FMT("error in send_fn")); return ESP_FAIL; diff --git a/components/esp_http_server/src/httpd_ws.c b/components/esp_http_server/src/httpd_ws.c index 01e16b62b6..cfea442935 100644 --- a/components/esp_http_server/src/httpd_ws.c +++ b/components/esp_http_server/src/httpd_ws.c @@ -416,14 +416,14 @@ esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t } /* Send off header */ - if (sess->send_fn(hd, fd, (const char *)header_buf, tx_len, 0) < 0) { + if (sess->send_fn(hd, fd, (const char *)header_buf, tx_len, MSG_MORE) < 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 (sess->send_fn(hd, fd, (const char *)frame->payload, frame->len, 0) < 0) { + if (sess->send_fn(hd, fd, (const char *)frame->payload, frame->len, MSG_MORE) < 0) { ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload")); return ESP_FAIL; } diff --git a/components/tcp_transport/include/esp_transport_ws.h b/components/tcp_transport/include/esp_transport_ws.h index fcf2230732..6c969562f3 100644 --- a/components/tcp_transport/include/esp_transport_ws.h +++ b/components/tcp_transport/include/esp_transport_ws.h @@ -14,6 +14,11 @@ extern "C" { #endif +// this define will also be used for optimized websocket sends with the +// optimized version to prepend the required space for the websocket +// header +#define MAX_WEBSOCKET_HEADER_SIZE 16 + typedef enum ws_transport_opcodes { WS_TRANSPORT_OPCODES_CONT = 0x00, WS_TRANSPORT_OPCODES_TEXT = 0x01, @@ -139,6 +144,37 @@ esp_err_t esp_transport_ws_set_config(esp_transport_handle_t t, const esp_transp */ int esp_transport_ws_send_raw(esp_transport_handle_t t, ws_transport_opcodes_t opcode, const char *b, int len, int timeout_ms); +/** + * @brief Sends websocket raw message with custom opcode and payload in a optimized way + * + * This method is similar to esp_transport_ws_send_raw(), but + * it assumes, that the first MAX_WEBSOCKET_HEADER_SIZE bytes + * of the buffer should not be sent and should rather be used + * for the required websocket header itself. This is done to + * have a single TCP packet for header and payload and to avoid + * copying and allocating additional resources. The first + * MAX_WEBSOCKET_HEADER_SIZE bytes should not be initialized in + * any specific way, and the return value (length) will also + * include the MAX_WEBSOCKET_HEADER_SIZE byte extra buffer. + * + * Note that generic esp_transport_write for ws handle sends + * binary massages by default if size is > 0 and + * ping message if message size is set to 0. + * This API is provided to support explicit messages with arbitrary opcode, + * should it be PING, PONG or TEXT message with arbitrary data. + * + * @param[in] t Websocket transport handle + * @param[in] opcode ws operation code + * @param[in] buffer The buffer + * @param[in] len The length + * @param[in] timeout_ms The timeout milliseconds (-1 indicates block forever) + * + * @return + * - Number of bytes was written + * - (-1) if there are any errors, should check errno + */ +int esp_transport_ws_send_raw_optimized(esp_transport_handle_t t, ws_transport_opcodes_t opcode, const char *b, int len, int timeout_ms); + /** * @brief Returns websocket fin flag for last received data * diff --git a/components/tcp_transport/transport_ssl.c b/components/tcp_transport/transport_ssl.c index 7f0a4d78f3..d3b0816d64 100644 --- a/components/tcp_transport/transport_ssl.c +++ b/components/tcp_transport/transport_ssl.c @@ -238,7 +238,7 @@ static int tcp_write(esp_transport_handle_t t, const char *buffer, int len, int ESP_LOGW(TAG, "Poll timeout or error, errno=%s, fd=%d, timeout_ms=%d", strerror(errno), ssl->sockfd, timeout_ms); return poll; } - int ret = send(ssl->sockfd, (const unsigned char *) buffer, len, 0); + int ret = send(ssl->sockfd, (const unsigned char *) buffer, len, MSG_MORE); if (ret < 0) { ESP_LOGE(TAG, "tcp_write error, errno=%s", strerror(errno)); esp_transport_capture_errno(t, errno); diff --git a/components/tcp_transport/transport_ws.c b/components/tcp_transport/transport_ws.c index 75cea86104..7d0c024de9 100644 --- a/components/tcp_transport/transport_ws.c +++ b/components/tcp_transport/transport_ws.c @@ -36,7 +36,6 @@ static const char *TAG = "transport_ws"; #define WS_MASK 0x80 #define WS_SIZE16 126 #define WS_SIZE64 127 -#define MAX_WEBSOCKET_HEADER_SIZE 16 #define WS_RESPONSE_OK 101 #define WS_TRANSPORT_MAX_CONTROL_FRAME_BUFFER_LEN 125 @@ -411,6 +410,92 @@ static int _ws_write(esp_transport_handle_t t, int opcode, int mask_flag, const return ret; } +// This method is similar to _ws_write() but it assumes, that the first 16 bytes of the buffer should not be sent and +// should rather be used for the required websocket header itself. This is done to have a single TCP packet for header +// and payload and to avoid copying and allocating additional resources. The first 16 bytes should not be initialized +// in any specific way, and the return value (length) will also include the 16 byte extra buffer. +static int _ws_write_optimized(esp_transport_handle_t t, int opcode, int mask_flag, const char *b, int len, int timeout_ms) +{ + assert(len >= MAX_WEBSOCKET_HEADER_SIZE); + + transport_ws_t *ws = esp_transport_get_context_data(t); + char *buffer = (char *)b; + char *ws_header; +// char *mask; + int header_len = 0; //, i; + + int poll_write; + if ((poll_write = esp_transport_poll_write(ws->parent, timeout_ms)) <= 0) { + ESP_LOGE(TAG, "Error transport_poll_write"); + return poll_write; + } + + int len2 = len - MAX_WEBSOCKET_HEADER_SIZE; + if (len2 <= 125) { + ws_header = buffer+MAX_WEBSOCKET_HEADER_SIZE-2-4; + ws_header[header_len++] = opcode; + ws_header[header_len++] = (uint8_t)(len2 | mask_flag); + } else if (len2 < 65536) { + ws_header = buffer+MAX_WEBSOCKET_HEADER_SIZE-4-4; + ws_header[header_len++] = opcode; + ws_header[header_len++] = WS_SIZE16 | mask_flag; + ws_header[header_len++] = (uint8_t)(len2 >> 8); + ws_header[header_len++] = (uint8_t)(len2 & 0xFF); + } else { + ws_header = buffer+MAX_WEBSOCKET_HEADER_SIZE-10-4; + ws_header[header_len++] = opcode; + ws_header[header_len++] = WS_SIZE64 | mask_flag; + /* Support maximum 4 bytes length */ + ws_header[header_len++] = 0; //(uint8_t)((len >> 56) & 0xFF); + ws_header[header_len++] = 0; //(uint8_t)((len >> 48) & 0xFF); + ws_header[header_len++] = 0; //(uint8_t)((len >> 40) & 0xFF); + ws_header[header_len++] = 0; //(uint8_t)((len >> 32) & 0xFF); + ws_header[header_len++] = (uint8_t)((len2 >> 24) & 0xFF); + ws_header[header_len++] = (uint8_t)((len2 >> 16) & 0xFF); + ws_header[header_len++] = (uint8_t)((len2 >> 8) & 0xFF); + ws_header[header_len++] = (uint8_t)((len2 >> 0) & 0xFF); + } + + if (mask_flag) { + ws_header[header_len++] = 0; + ws_header[header_len++] = 0; + ws_header[header_len++] = 0; + ws_header[header_len++] = 0; +// mask = &ws_header[header_len]; +// mask = 0; +// ssize_t rc; +// if ((rc = getrandom(ws_header + header_len, 4, 0)) < 0) { +// ESP_LOGD(TAG, "getrandom() returned %zd", rc); +// return -1; +// } +// header_len += 4; + +// for (i = MAX_WEBSOCKET_HEADER_SIZE; i < len; ++i) { +// buffer[i] = (buffer[i] ^ mask[i % 4]); +// } + } + +// if (esp_transport_write(ws->parent, ws_header, len - MAX_WEBSOCKET_HEADER_SIZE + header_len, timeout_ms) != header_len) { +// ESP_LOGE(TAG, "Error write header"); +// return -1; +// } +// if (len == 0) { +// return 0; +// } + + int ret = esp_transport_write(ws->parent, ws_header, len - MAX_WEBSOCKET_HEADER_SIZE + header_len, timeout_ms); + ESP_LOGI(TAG, "len=%d header_len=%d total_size=%d sent=%d", len, header_len, len - MAX_WEBSOCKET_HEADER_SIZE + header_len, ret); + // in case of masked transport we have to revert back to the original data, as ws layer + // does not create its own copy of data to be sent + if (mask_flag) { +// mask = &ws_header[header_len - 4]; +// for (i = 0; i < len; ++i) { +// buffer[i] = (buffer[i] ^ mask[i % 4]); +// } + } + return ret + (ws_header - buffer); +} + int esp_transport_ws_send_raw(esp_transport_handle_t t, ws_transport_opcodes_t opcode, const char *b, int len, int timeout_ms) { uint8_t op_code = ws_get_bin_opcode(opcode); @@ -422,6 +507,17 @@ int esp_transport_ws_send_raw(esp_transport_handle_t t, ws_transport_opcodes_t o return _ws_write(t, op_code, WS_MASK, b, len, timeout_ms); } +int esp_transport_ws_send_raw_optimized(esp_transport_handle_t t, ws_transport_opcodes_t opcode, const char *b, int len, int timeout_ms) +{ + uint8_t op_code = ws_get_bin_opcode(opcode); + if (t == NULL) { + ESP_LOGE(TAG, "Transport must be a valid ws handle"); + return ESP_ERR_INVALID_ARG; + } + ESP_LOGD(TAG, "Sending raw ws message with opcode %d", op_code); + return _ws_write_optimized(t, op_code, WS_MASK, b, len, timeout_ms); +} + static int ws_write(esp_transport_handle_t t, const char *b, int len, int timeout_ms) { if (len == 0) {