Skip to content

Commit 6393fcd

Browse files
fix(websocket): Fix locking issues of esp_websocket_client_send_with_exact_opcode API
Extended examples to cover more cases Added new config CONFIG_ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER for testing
1 parent c9439bd commit 6393fcd

File tree

5 files changed

+142
-46
lines changed

5 files changed

+142
-46
lines changed

components/esp_websocket_client/esp_websocket_client.c

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -555,9 +555,29 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
555555
int wlen = 0, widx = 0;
556556
bool contained_fin = opcode & WS_TRANSPORT_OPCODES_FIN;
557557

558+
if (client == NULL || len < 0 || (data == NULL && len > 0)) {
559+
ESP_LOGE(TAG, "Invalid arguments");
560+
return -1;
561+
}
562+
563+
if (!esp_websocket_client_is_connected(client)) {
564+
ESP_LOGE(TAG, "Websocket client is not connected");
565+
return -1;
566+
}
567+
568+
if (client->transport == NULL) {
569+
ESP_LOGE(TAG, "Invalid transport");
570+
return -1;
571+
}
572+
573+
if (xSemaphoreTakeRecursive(client->lock, timeout) != pdPASS) {
574+
ESP_LOGE(TAG, "Could not lock ws-client within %" PRIu32 " timeout", timeout);
575+
return -1;
576+
}
577+
558578
if (esp_websocket_new_buf(client, true) != ESP_OK) {
559579
ESP_LOGE(TAG, "Failed to setup tx buffer");
560-
return -1;
580+
goto unlock_and_return;
561581
}
562582

563583
while (widx < len || opcode) { // allow for sending "current_opcode" only message with len==0
@@ -583,14 +603,18 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
583603
esp_websocket_client_error(client, "esp_transport_write() returned %d, errno=%d", ret, errno);
584604
}
585605
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
586-
return ret;
606+
goto unlock_and_return;
587607
}
588608
opcode = 0;
589609
widx += wlen;
590610
need_write = len - widx;
591611
}
592612
esp_websocket_free_buf(client, true);
593-
return widx;
613+
ret = widx;
614+
615+
unlock_and_return:
616+
xSemaphoreGiveRecursive(client->lock);
617+
return ret;
594618
}
595619

596620
esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config)
@@ -1211,35 +1235,7 @@ int esp_websocket_client_send_fin(esp_websocket_client_handle_t client, TickType
12111235

12121236
int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout)
12131237
{
1214-
int ret = -1;
1215-
if (client == NULL || len < 0 || (data == NULL && len > 0)) {
1216-
ESP_LOGE(TAG, "Invalid arguments");
1217-
return -1;
1218-
}
1219-
1220-
if (xSemaphoreTakeRecursive(client->lock, timeout) != pdPASS) {
1221-
ESP_LOGE(TAG, "Could not lock ws-client within %" PRIu32 " timeout", timeout);
1222-
return -1;
1223-
}
1224-
1225-
if (!esp_websocket_client_is_connected(client)) {
1226-
ESP_LOGE(TAG, "Websocket client is not connected");
1227-
goto unlock_and_return;
1228-
}
1229-
1230-
if (client->transport == NULL) {
1231-
ESP_LOGE(TAG, "Invalid transport");
1232-
goto unlock_and_return;
1233-
}
1234-
1235-
ret = esp_websocket_client_send_with_exact_opcode(client, opcode | WS_TRANSPORT_OPCODES_FIN, data, len, timeout);
1236-
if (ret < 0) {
1237-
ESP_LOGE(TAG, "Failed to send the buffer");
1238-
goto unlock_and_return;
1239-
}
1240-
unlock_and_return:
1241-
xSemaphoreGiveRecursive(client->lock);
1242-
return ret;
1238+
return esp_websocket_client_send_with_exact_opcode(client, opcode | WS_TRANSPORT_OPCODES_FIN, data, len, timeout);
12431239
}
12441240

12451241
bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client)

components/esp_websocket_client/examples/linux/main/main.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -96,14 +96,24 @@ static void websocket_app_start(void)
9696
vTaskDelay(1000 / portTICK_PERIOD_MS);
9797
}
9898

99-
ESP_LOGI(TAG, "Sending fragmented message");
100-
vTaskDelay(1000 / portTICK_PERIOD_MS);
99+
// Sending text data
100+
ESP_LOGI(TAG, "Sending fragmented text message");
101101
memset(data, 'a', sizeof(data));
102102
esp_websocket_client_send_text_partial(client, data, sizeof(data), portMAX_DELAY);
103103
memset(data, 'b', sizeof(data));
104104
esp_websocket_client_send_cont_msg(client, data, sizeof(data), portMAX_DELAY);
105105
esp_websocket_client_send_fin(client, portMAX_DELAY);
106106

107+
vTaskDelay(1000 / portTICK_PERIOD_MS);
108+
// Sending binary data
109+
ESP_LOGI(TAG, "Sending fragmented binary message");
110+
char binary_data[128];
111+
memset(binary_data, 0, sizeof(binary_data));
112+
esp_websocket_client_send_bin_partial(client, binary_data, sizeof(binary_data), portMAX_DELAY);
113+
memset(binary_data, 1, sizeof(binary_data));
114+
esp_websocket_client_send_cont_msg(client, binary_data, sizeof(binary_data), portMAX_DELAY);
115+
esp_websocket_client_send_fin(client, portMAX_DELAY);
116+
107117
esp_websocket_client_destroy(client);
108118
}
109119

components/esp_websocket_client/examples/target/main/websocket_example.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
8888
case WEBSOCKET_EVENT_DATA:
8989
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
9090
ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
91-
if (data->op_code == 0x08 && data->data_len == 2) {
91+
if (data->op_code == 0x2) { // Opcode 0x2 indicates binary data
92+
ESP_LOG_BUFFER_HEX("Received binary data", data->data_ptr, data->data_len);
93+
} else if (data->op_code == 0x08 && data->data_len == 2) {
9294
ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]);
9395
} else {
9496
ESP_LOGW(TAG, "Received=%.*s\n\n", data->data_len, (char *)data->data_ptr);
@@ -183,13 +185,32 @@ static void websocket_app_start(void)
183185
vTaskDelay(1000 / portTICK_PERIOD_MS);
184186
}
185187

186-
ESP_LOGI(TAG, "Sending fragmented message");
187188
vTaskDelay(1000 / portTICK_PERIOD_MS);
189+
// Sending text data
190+
ESP_LOGI(TAG, "Sending fragmented text message");
188191
memset(data, 'a', sizeof(data));
189192
esp_websocket_client_send_text_partial(client, data, sizeof(data), portMAX_DELAY);
190193
memset(data, 'b', sizeof(data));
191194
esp_websocket_client_send_cont_msg(client, data, sizeof(data), portMAX_DELAY);
192195
esp_websocket_client_send_fin(client, portMAX_DELAY);
196+
vTaskDelay(1000 / portTICK_PERIOD_MS);
197+
198+
// Sending binary data
199+
ESP_LOGI(TAG, "Sending fragmented binary message");
200+
char binary_data[5];
201+
memset(binary_data, 0, sizeof(binary_data));
202+
esp_websocket_client_send_bin_partial(client, binary_data, sizeof(binary_data), portMAX_DELAY);
203+
memset(binary_data, 1, sizeof(binary_data));
204+
esp_websocket_client_send_cont_msg(client, binary_data, sizeof(binary_data), portMAX_DELAY);
205+
esp_websocket_client_send_fin(client, portMAX_DELAY);
206+
vTaskDelay(1000 / portTICK_PERIOD_MS);
207+
208+
// Sending text data longer than ws buffer (default 1024)
209+
ESP_LOGI(TAG, "Sending text longer than ws buffer (default 1024)");
210+
const int size = 2000;
211+
char *long_data = malloc(size);
212+
memset(long_data, 'a', size);
213+
esp_websocket_client_send_text(client, long_data, size, portMAX_DELAY);
193214

194215
xSemaphoreTake(shutdown_sema, portMAX_DELAY);
195216
esp_websocket_client_close(client, portMAX_DELAY);

components/esp_websocket_client/examples/target/pytest_websocket.py

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ def get_my_ip():
2727

2828

2929
class WebsocketTestEcho(WebSocket):
30-
3130
def handleMessage(self):
32-
self.sendMessage(self.data)
33-
print('\n Server sent: {}\n'.format(self.data))
31+
if isinstance(self.data, bytes):
32+
print(f'\n Server received binary data: {self.data.hex()}\n')
33+
self.sendMessage(self.data, binary=True)
34+
else:
35+
print(f'\n Server received: {self.data}\n')
36+
self.sendMessage(self.data)
3437

3538
def handleConnected(self):
3639
print('Connection from: {}'.format(self.address))
@@ -86,19 +89,26 @@ def test_examples_protocol_websocket(dut):
8689
3. send and receive data
8790
"""
8891

92+
# Test for echo functionality:
93+
# Sends a series of simple "hello" messages to the WebSocket server and verifies that each one is echoed back correctly.
94+
# This tests the basic responsiveness and correctness of the WebSocket connection.
8995
def test_echo(dut):
9096
dut.expect('WEBSOCKET_EVENT_CONNECTED')
9197
for i in range(0, 5):
9298
dut.expect(re.compile(b'Received=hello (\\d)'))
9399
print('All echos received')
94100
sys.stdout.flush()
95101

102+
# Test for clean closure of the WebSocket connection:
103+
# Ensures that the WebSocket can correctly receive a close frame and terminate the connection without issues.
96104
def test_close(dut):
97105
code = dut.expect(
98106
re.compile(
99107
b'websocket: Received closed message with code=(\\d*)'))[0]
100108
print('Received close frame with code {}'.format(code))
101109

110+
# Test for JSON message handling:
111+
# Sends a JSON formatted string and verifies that the received message matches the expected JSON structure.
102112
def test_json(dut, websocket):
103113
json_string = """
104114
[
@@ -118,14 +128,16 @@ def test_json(dut, websocket):
118128
match = dut.expect(
119129
re.compile(b'Json=({[a-zA-Z0-9]*).*}')).group(0).decode()[5:]
120130
if match == str(data[0]):
121-
print('Sent message and received message are equal \n')
131+
print('\n Sent message and received message are equal \n')
122132
sys.stdout.flush()
123133
else:
124134
raise ValueError(
125135
'DUT received string do not match sent string, \nexpected: {}\nwith length {}\
126136
\nreceived: {}\nwith length {}'.format(
127137
data[0], len(data[0]), match, len(match)))
128138

139+
# Test for receiving long messages:
140+
# This sends a message with a specified length (2000 characters) to ensure the WebSocket can handle large data payloads. Repeated 3 times for reliability.
129141
def test_recv_long_msg(dut, websocket, msg_len, repeats):
130142

131143
send_msg = ''.join(
@@ -142,17 +154,58 @@ def test_recv_long_msg(dut, websocket, msg_len, repeats):
142154
recv_msg += match
143155

144156
if recv_msg == send_msg:
145-
print('Sent message and received message are equal \n')
157+
print('\n Sent message and received message are equal \n')
146158
sys.stdout.flush()
147159
else:
148160
raise ValueError(
149161
'DUT received string do not match sent string, \nexpected: {}\nwith length {}\
150162
\nreceived: {}\nwith length {}'.format(
151163
send_msg, len(send_msg), recv_msg, len(recv_msg)))
152164

153-
def test_fragmented_msg(dut):
165+
# Test for receiving the first fragment of a large message:
166+
# Verifies the WebSocket's ability to correctly process the initial segment of a fragmented message.
167+
def test_recv_fragmented_msg1(dut):
168+
dut.expect('websocket: Total payload length=2000, data_len=1024, current payload offset=0')
169+
170+
# Test for receiving the second fragment of a large message:
171+
# Confirms that the WebSocket can correctly handle and process the subsequent segment of a fragmented message.
172+
def test_recv_fragmented_msg2(dut):
173+
dut.expect('websocket: Total payload length=2000, data_len=976, current payload offset=1024')
174+
175+
# Test for receiving fragmented text messages:
176+
# Checks if the WebSocket can accurately reconstruct a message sent in several smaller parts.
177+
def test_fragmented_txt_msg(dut):
154178
dut.expect('Received=' + 32 * 'a' + 32 * 'b')
155-
print('Fragmented data received')
179+
print('\nFragmented data received\n')
180+
181+
# Extract the hexdump portion of the log line
182+
def parse_hexdump(line):
183+
match = re.search(r'\(.*\) Received binary data: ([0-9A-Fa-f ]+)', line)
184+
if match:
185+
hexdump = match.group(1).strip().replace(' ', '')
186+
# Convert the hexdump string to a bytearray
187+
return bytearray.fromhex(hexdump)
188+
return bytearray()
189+
190+
# Capture the binary log output from the DUT
191+
def test_fragmented_binary_msg(dut):
192+
match = dut.expect(r'\(.*\) Received binary data: .*')
193+
if match:
194+
line = match.group(0).strip()
195+
if isinstance(line, bytes):
196+
line = line.decode('utf-8')
197+
198+
# Parse the hexdump from the log line
199+
received_data = parse_hexdump(line)
200+
201+
# Create the expected bytearray with the specified pattern
202+
expected_data = bytearray([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
203+
204+
# Validate the received data
205+
assert received_data == expected_data, f'Received data does not match expected data. Received: {received_data}, Expected: {expected_data}'
206+
print('\nFragmented data received\n')
207+
else:
208+
assert False, 'Log line with binary data not found'
156209

157210
# Starting of the test
158211
try:
@@ -184,10 +237,12 @@ def test_fragmented_msg(dut):
184237
dut.expect('Please enter uri of websocket endpoint', timeout=30)
185238
dut.write(uri)
186239
test_echo(dut)
187-
# Message length should exceed DUT's buffer size to test fragmentation, default is 1024 byte
188240
test_recv_long_msg(dut, ws, 2000, 3)
189241
test_json(dut, ws)
190-
test_fragmented_msg(dut)
242+
test_fragmented_txt_msg(dut)
243+
test_fragmented_binary_msg(dut)
244+
test_recv_fragmented_msg1(dut)
245+
test_recv_fragmented_msg2(dut)
191246
test_close(dut)
192247
else:
193248
print('DUT connecting to {}'.format(uri))
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CONFIG_IDF_TARGET="esp32"
2+
CONFIG_IDF_TARGET_LINUX=n
3+
CONFIG_WEBSOCKET_URI_FROM_STDIN=n
4+
CONFIG_WEBSOCKET_URI_FROM_STRING=y
5+
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
6+
CONFIG_EXAMPLE_CONNECT_WIFI=n
7+
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
8+
CONFIG_EXAMPLE_ETH_PHY_IP101=y
9+
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
10+
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
11+
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
12+
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
13+
CONFIG_EXAMPLE_CONNECT_IPV6=y
14+
CONFIG_ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER=y

0 commit comments

Comments
 (0)