From 86b688d43fe3f6fda96c641a1d24903c89d626b6 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Tue, 21 Jan 2020 17:03:03 +0200 Subject: [PATCH] tests: net: websocket: Add RX unit tests for websocket API Add simple tests for testing receiving websocket data. Signed-off-by: Jukka Rissanen --- subsys/net/lib/websocket/websocket.c | 36 +++ tests/net/socket/websocket/CMakeLists.txt | 10 + tests/net/socket/websocket/prj.conf | 37 +++ tests/net/socket/websocket/src/main.c | 263 ++++++++++++++++++++++ tests/net/socket/websocket/testcase.yaml | 9 + 5 files changed, 355 insertions(+) create mode 100644 tests/net/socket/websocket/CMakeLists.txt create mode 100644 tests/net/socket/websocket/prj.conf create mode 100644 tests/net/socket/websocket/src/main.c create mode 100644 tests/net/socket/websocket/testcase.yaml diff --git a/subsys/net/lib/websocket/websocket.c b/subsys/net/lib/websocket/websocket.c index 84369a0bea6..c6ebc86d894 100644 --- a/subsys/net/lib/websocket/websocket.c +++ b/subsys/net/lib/websocket/websocket.c @@ -642,6 +642,20 @@ int websocket_recv_msg(int ws_sock, u8_t *buf, size_t buf_len, size_t can_copy, left; int ret; +#if defined(CONFIG_NET_TEST) + /* Websocket unit test does not use socket layer but feeds + * the data directly here when testing this function. + */ + struct test_data { + u8_t *input_buf; + size_t input_len; + struct websocket_context *ctx; + }; + + struct test_data *test_data = INT_TO_POINTER(ws_sock); + + ctx = test_data->ctx; +#else ctx = z_get_fd_obj(ws_sock, NULL, 0); if (ctx == NULL) { return -EBADF; @@ -650,12 +664,24 @@ int websocket_recv_msg(int ws_sock, u8_t *buf, size_t buf_len, if (!PART_OF_ARRAY(contexts, ctx)) { return -ENOENT; } +#endif /* CONFIG_NET_TEST */ /* If we have not received the websocket header yet, read it first */ if (!ctx->header_received) { +#if defined(CONFIG_NET_TEST) + size_t input_len = MIN(ctx->tmp_buf_len - ctx->tmp_buf_pos, + test_data->input_len); + + memcpy(&ctx->tmp_buf[ctx->tmp_buf_pos], test_data->input_buf, + input_len); + test_data->input_buf += input_len; + ret = input_len; +#else ret = recv(ctx->real_sock, &ctx->tmp_buf[ctx->tmp_buf_pos], ctx->tmp_buf_len - ctx->tmp_buf_pos, timeout == K_NO_WAIT ? MSG_DONTWAIT : 0); +#endif /* CONFIG_NET_TEST */ + if (ret < 0) { return -errno; } @@ -730,8 +756,18 @@ int websocket_recv_msg(int ws_sock, u8_t *buf, size_t buf_len, if (ctx->tmp_buf_pos == 0) { /* Read more data into temp buffer */ +#if defined(CONFIG_NET_TEST) + size_t input_len = MIN(ctx->tmp_buf_len, test_data->input_len); + + memcpy(ctx->tmp_buf, test_data->input_buf, input_len); + test_data->input_buf += input_len; + + ret = input_len; +#else ret = recv(ctx->real_sock, ctx->tmp_buf, ctx->tmp_buf_len, timeout == K_NO_WAIT ? MSG_DONTWAIT : 0); +#endif /* CONFIG_NET_TEST */ + if (ret < 0) { return -errno; } diff --git a/tests/net/socket/websocket/CMakeLists.txt b/tests/net/socket/websocket/CMakeLists.txt new file mode 100644 index 00000000000..13eaebdebea --- /dev/null +++ b/tests/net/socket/websocket/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(websocket) + +target_include_directories(app PRIVATE + $ENV{ZEPHYR_BASE}/subsys/net/lib/websocket) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/socket/websocket/prj.conf b/tests/net/socket/websocket/prj.conf new file mode 100644 index 00000000000..c949bb0a271 --- /dev/null +++ b/tests/net/socket/websocket/prj.conf @@ -0,0 +1,37 @@ +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_TEST=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SHELL=y +CONFIG_NET_STATISTICS=y +CONFIG_NET_LOOPBACK=y + +# Sockets +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y + +# Network driver config +CONFIG_TEST_RANDOM_GENERATOR=y + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=n + +# HTTP & Websocket +CONFIG_HTTP_CLIENT=y +CONFIG_WEBSOCKET_CLIENT=y + +# Network debug config +CONFIG_LOG=y +#CONFIG_LOG_IMMEDIATE=y +CONFIG_NET_LOG=y +#CONFIG_NET_WEBSOCKET_LOG_LEVEL_DBG=y + +# Generic options +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_HEAP_MEM_POOL_SIZE=1500 + +# Test options +CONFIG_ZTEST=y +CONFIG_ZTEST_STACKSIZE=2048 diff --git a/tests/net/socket/websocket/src/main.c b/tests/net/socket/websocket/src/main.c new file mode 100644 index 00000000000..9abc5b92c45 --- /dev/null +++ b/tests/net/socket/websocket/src/main.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2020 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_WEBSOCKET_LOG_LEVEL); + +#include + +#include +#include +#include + +#include "websocket_internal.h" + +#define MAX_RECV_BUF_LEN 256 +static u8_t recv_buf[MAX_RECV_BUF_LEN]; + +/* We need to allocate bigger buffer for the websocket data we receive so that + * the websocket header fits into it. + */ +#define EXTRA_BUF_SPACE 30 + +static u8_t temp_recv_buf[MAX_RECV_BUF_LEN + EXTRA_BUF_SPACE]; +static u8_t feed_buf[MAX_RECV_BUF_LEN + EXTRA_BUF_SPACE]; + +struct test_data { + u8_t *input_buf; + size_t input_len; + struct websocket_context *ctx; +}; + +static int test_recv_buf(u8_t *feed_buf, size_t feed_len, + struct websocket_context *ctx, + u32_t *msg_type, u64_t *remaining, + u8_t *recv_buf, size_t recv_len) +{ + static struct test_data test_data; + int ctx_ptr; + + test_data.ctx = ctx; + test_data.input_buf = feed_buf; + test_data.input_len = feed_len; + + ctx_ptr = POINTER_TO_INT(&test_data); + + return websocket_recv_msg(ctx_ptr, recv_buf, recv_len, + msg_type, remaining, K_NO_WAIT); +} + +/* Websocket frame, header is 6 bytes, FIN bit is set, opcode is text (1), + * payload length is 12, masking key is e17e8eb9, + * unmasked data is "test message" + */ +static const unsigned char frame1[] = { + 0x81, 0x8c, 0xe1, 0x7e, 0x8e, 0xb9, 0x95, 0x1b, + 0xfd, 0xcd, 0xc1, 0x13, 0xeb, 0xca, 0x92, 0x1f, + 0xe9, 0xdc +}; + +static const unsigned char frame1_msg[] = { + /* Null added for printing purposes */ + 't', 'e', 's', 't', ' ', 'm', 'e', 's', 's', 'a', 'g', 'e', '\0' +}; + +/* The frame2 has frame1 + frame1. The idea is to test a case where we + * read full frame1 and then part of second frame + */ +static const unsigned char frame2[] = { + 0x81, 0x8c, 0xe1, 0x7e, 0x8e, 0xb9, 0x95, 0x1b, + 0xfd, 0xcd, 0xc1, 0x13, 0xeb, 0xca, 0x92, 0x1f, + 0xe9, 0xdc, + 0x81, 0x8c, 0xe1, 0x7e, 0x8e, 0xb9, 0x95, 0x1b, + 0xfd, 0xcd, 0xc1, 0x13, 0xeb, 0xca, 0x92, 0x1f, + 0xe9, 0xdc +}; + +#define FRAME1_HDR_SIZE (sizeof(frame1) - (sizeof(frame1_msg) - 1)) + +static void test_recv(int count) +{ + struct websocket_context ctx; + u32_t msg_type = -1; + u64_t remaining = -1; + int total_read = 0; + int ret, i, left; + + memset(&ctx, 0, sizeof(ctx)); + + ctx.tmp_buf = temp_recv_buf; + ctx.tmp_buf_len = sizeof(temp_recv_buf); + ctx.tmp_buf_pos = 0; + + memcpy(feed_buf, &frame1, sizeof(frame1)); + + NET_DBG("Reading %d bytes at a time, frame %zd hdr %zd", count, + sizeof(frame1), FRAME1_HDR_SIZE); + + /* We feed the frame N byte(s) at a time */ + for (i = 0; i < sizeof(frame1) / count; i++) { + ret = test_recv_buf(&feed_buf[i * count], count, + &ctx, &msg_type, &remaining, + recv_buf + total_read, + sizeof(recv_buf) - total_read); + if (count < 7 && (i * count) < FRAME1_HDR_SIZE) { + zassert_equal(ret, -EAGAIN, + "[%d] Header parse failed (ret %d)", + i * count, ret); + } else { + total_read += ret; + } + } + + /* Read any remaining data */ + left = sizeof(frame1) % count; + if (left > 0) { + /* Some leftover bytes are still there */ + ret = test_recv_buf(&feed_buf[sizeof(frame1) - left], left, + &ctx, &msg_type, &remaining, + recv_buf + total_read, + sizeof(recv_buf) - total_read); + total_read += ret; + zassert_equal(total_read, sizeof(frame1) - FRAME1_HDR_SIZE, + "Invalid amount of data read (%d)", ret); + + } else if (total_read < (sizeof(frame1) - FRAME1_HDR_SIZE)) { + /* We read the whole message earlier, but we have parsed + * only part of the message. Parse the reset of the message + * here. + */ + ret = test_recv_buf(&feed_buf[FRAME1_HDR_SIZE + total_read], + sizeof(frame1) - FRAME1_HDR_SIZE - total_read, + &ctx, &msg_type, &remaining, + recv_buf + total_read, + sizeof(recv_buf) - total_read); + total_read += ret; + zassert_equal(total_read, sizeof(frame1) - FRAME1_HDR_SIZE, + "Invalid amount of data read (%d)", ret); + } + + zassert_mem_equal(recv_buf, frame1_msg, sizeof(frame1_msg) - 1, + "Invalid message, should be '%s' was '%s'", + frame1_msg, recv_buf); + + zassert_equal(remaining, 0, "Msg not empty"); +} + +static void test_recv_1_byte(void) +{ + test_recv(1); +} + +static void test_recv_2_byte(void) +{ + test_recv(2); +} + +static void test_recv_3_byte(void) +{ + test_recv(3); +} + +static void test_recv_6_byte(void) +{ + test_recv(6); +} + +static void test_recv_7_byte(void) +{ + test_recv(7); +} + +static void test_recv_8_byte(void) +{ + test_recv(8); +} + +static void test_recv_9_byte(void) +{ + test_recv(9); +} + +static void test_recv_10_byte(void) +{ + test_recv(10); +} + +static void test_recv_12_byte(void) +{ + test_recv(12); +} + +static void test_recv_whole_msg(void) +{ + test_recv(sizeof(frame1)); +} + +static void test_recv_2(int count) +{ + struct websocket_context ctx; + u32_t msg_type = -1; + u64_t remaining = -1; + int total_read = 0; + int ret; + + memset(&ctx, 0, sizeof(ctx)); + + ctx.tmp_buf = temp_recv_buf; + ctx.tmp_buf_len = sizeof(temp_recv_buf); + + memcpy(feed_buf, &frame2, sizeof(frame2)); + + NET_DBG("Reading %d bytes at a time, frame %zd hdr %zd", count, + sizeof(frame2), FRAME1_HDR_SIZE); + + total_read = test_recv_buf(&feed_buf[0], count, &ctx, &msg_type, + &remaining, recv_buf, sizeof(recv_buf)); + + zassert_mem_equal(recv_buf, frame1_msg, sizeof(frame1_msg) - 1, + "Invalid message, should be '%s' was '%s'", + frame1_msg, recv_buf); + + zassert_equal(remaining, 0, "Msg not empty"); + + /* Then read again, now we should get EAGAIN as the second message + * header is partially read. + */ + ret = test_recv_buf(&feed_buf[sizeof(frame1)], count, &ctx, &msg_type, + &remaining, recv_buf, sizeof(recv_buf)); + + zassert_equal(ret, sizeof(frame1_msg) - 1, + "2nd header parse failed (ret %d)", ret); + + zassert_equal(remaining, 0, "Msg not empty"); +} + +static void test_recv_two_msg(void) +{ + test_recv_2(sizeof(frame1) + FRAME1_HDR_SIZE / 2); +} + +void test_main(void) +{ + k_thread_system_pool_assign(k_current_get()); + + ztest_test_suite(websocket, + ztest_unit_test(test_recv_1_byte), + ztest_unit_test(test_recv_2_byte), + ztest_unit_test(test_recv_3_byte), + ztest_unit_test(test_recv_6_byte), + ztest_unit_test(test_recv_7_byte), + ztest_unit_test(test_recv_8_byte), + ztest_unit_test(test_recv_9_byte), + ztest_unit_test(test_recv_10_byte), + ztest_unit_test(test_recv_12_byte), + ztest_unit_test(test_recv_whole_msg), + ztest_unit_test(test_recv_two_msg) + ); + + ztest_run_test_suite(websocket); +} diff --git a/tests/net/socket/websocket/testcase.yaml b/tests/net/socket/websocket/testcase.yaml new file mode 100644 index 00000000000..5e09cc4254b --- /dev/null +++ b/tests/net/socket/websocket/testcase.yaml @@ -0,0 +1,9 @@ +common: + depends_on: netif +tests: + net.socket.websocket: + min_ram: 21 + tags: net websocket + # Temporarily disable test for native_posix_64 because of sanity issues + # not seen locally but only in shippable. + platform_exclude: native_posix_64