tests: net: lib: http_server: core: Add tests for dynamic POST/GET

Add tests cases covering interaction with dynamic resources with
POST/GET requests.

Simplify HTTP2 request generation to facilitate adding more tests.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
This commit is contained in:
Robert Lubos 2024-07-17 15:47:19 +02:00 committed by Anas Nashif
commit d1ebddb110
2 changed files with 330 additions and 44 deletions

View file

@ -28,6 +28,7 @@ CONFIG_NET_BUF_TX_COUNT=32
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16
CONFIG_NET_CONTEXT_RCVTIMEO=y
CONFIG_NET_TCP_TIME_WAIT_DELAY=0
# Reduce the retry count, so the close always finishes within a second
CONFIG_NET_TCP_RETRY_COUNT=3

View file

@ -23,52 +23,49 @@
#define TEST_STREAM_ID_1 3
#define TEST_STREAM_ID_2 5
/* Magic, SETTINGS[0], HEADERS[1]: GET /, HEADERS[3]: GET /index.html, SETTINGS[0], GOAWAY[0]*/
static const uint8_t request_get_2_streams[] = {
/* Magic */
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a,
/* SETTINGS[0] */
0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x64, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff,
/* HEADERS[1]: GET / */
0x00, 0x00, 0x21, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1,
0x82, 0x84, 0x86, 0x41, 0x8a, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xdc,
0x78, 0x0f, 0x03, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x90, 0x7a, 0x8a, 0xaa,
0x69, 0xd2, 0x9a, 0xc4, 0xc0, 0x57, 0x68, 0x0b, 0x83,
/* HEADERS[3]: GET /index.html */
0x00, 0x00, 0x21, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_2,
0x82, 0x85, 0x86, 0x41, 0x8a, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xdc,
0x78, 0x0f, 0x03, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x90, 0x7a, 0x8a, 0xaa,
0x69, 0xd2, 0x9a, 0xc4, 0xc0, 0x57, 0x68, 0x0b, 0x83,
/* SETTINGS[0] */
0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00,
/* GOAWAY[0] */
0x00, 0x00, 0x08, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t request_get_static_simple[] = {
/* Magic */
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a,
/* SETTINGS[0] */
0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x64, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff,
/* HEADERS[1]: GET / */
0x00, 0x00, 0x21, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1,
0x82, 0x84, 0x86, 0x41, 0x8a, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xdc,
0x78, 0x0f, 0x03, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x90, 0x7a, 0x8a, 0xaa,
0x69, 0xd2, 0x9a, 0xc4, 0xc0, 0x57, 0x68, 0x0b, 0x83,
/* SETTINGS[0] */
0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00,
/* GOAWAY[0] */
0x00, 0x00, 0x08, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
#define TEST_DYNAMIC_POST_PAYLOAD "Test dynamic POST"
#define TEST_DYNAMIC_GET_PAYLOAD "Test dynamic GET"
#define TEST_STATIC_PAYLOAD "Hello, World!"
/* Individual HTTP2 frames, used to compose requests. */
#define TEST_HTTP2_MAGIC \
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, \
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
#define TEST_HTTP2_SETTINGS \
0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x03, 0x00, 0x00, 0x00, 0x64, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff
#define TEST_HTTP2_SETTINGS_ACK \
0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00
#define TEST_HTTP2_GOAWAY \
0x00, 0x00, 0x08, 0x07, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
#define TEST_HTTP2_HEADERS_GET_ROOT_STREAM_1 \
0x00, 0x00, 0x21, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x82, 0x84, 0x86, 0x41, 0x8a, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xdc, \
0x78, 0x0f, 0x03, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x90, 0x7a, 0x8a, 0xaa, \
0x69, 0xd2, 0x9a, 0xc4, 0xc0, 0x57, 0x68, 0x0b, 0x83
#define TEST_HTTP2_HEADERS_GET_INDEX_STREAM_2 \
0x00, 0x00, 0x21, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_2, \
0x82, 0x85, 0x86, 0x41, 0x8a, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xdc, \
0x78, 0x0f, 0x03, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x90, 0x7a, 0x8a, 0xaa, \
0x69, 0xd2, 0x9a, 0xc4, 0xc0, 0x57, 0x68, 0x0b, 0x83
#define TEST_HTTP2_HEADERS_GET_DYNAMIC_STREAM_1 \
0x00, 0x00, 0x2b, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x82, 0x86, 0x41, 0x87, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xff, 0x04, \
0x86, 0x62, 0x4f, 0x55, 0x0e, 0x93, 0x13, 0x7a, 0x88, 0x25, 0xb6, 0x50, \
0xc3, 0xcb, 0xbc, 0xb8, 0x3f, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x5f, 0x87, \
0x49, 0x7c, 0xa5, 0x8a, 0xe8, 0x19, 0xaa
#define TEST_HTTP2_HEADERS_POST_DYNAMIC_STREAM_1 \
0x00, 0x00, 0x30, 0x01, 0x04, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x83, 0x86, 0x41, 0x87, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xff, 0x04, \
0x86, 0x62, 0x4f, 0x55, 0x0e, 0x93, 0x13, 0x7a, 0x88, 0x25, 0xb6, 0x50, \
0xc3, 0xcb, 0xbc, 0xb8, 0x3f, 0x53, 0x03, 0x2a, 0x2f, 0x2a, 0x5f, 0x87, \
0x49, 0x7c, 0xa5, 0x8a, 0xe8, 0x19, 0xaa, 0x0f, 0x0d, 0x02, 0x31, 0x37
#define TEST_HTTP2_DATA_POST_DYNAMIC_STREAM_1 \
0x00, 0x00, 0x11, 0x00, 0x01, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, \
0x20, 0x50, 0x4f, 0x53, 0x54
static uint16_t test_http_service_port = SERVER_PORT;
HTTP_SERVICE_DEFINE(test_http_service, SERVER_IPV4_ADDR,
&test_http_service_port, 1, 10, NULL);
@ -86,6 +83,78 @@ struct http_resource_detail_static static_resource_detail = {
HTTP_RESOURCE_DEFINE(static_resource, test_http_service, "/",
&static_resource_detail);
static uint8_t dynamic_payload[32];
static size_t dynamic_payload_len = sizeof(dynamic_payload);
static uint8_t dynamic_buffer[32];
static int dynamic_cb(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, void *user_data)
{
static size_t offset;
size_t copy_len;
int ret = 0;
if (status == HTTP_SERVER_DATA_ABORTED) {
offset = 0;
return 0;
}
switch (client->method) {
case HTTP_GET:
copy_len = MIN(sizeof(dynamic_buffer),
dynamic_payload_len - offset);
ret = copy_len;
if (copy_len > 0) {
memcpy(buffer, dynamic_payload + offset, copy_len);
offset += copy_len;
} else {
/* All resource returned, reset progress. */
offset = 0;
}
break;
case HTTP_POST:
if (len + offset > sizeof(dynamic_payload)) {
return -ENOMEM;
}
if (len > 0) {
memcpy(dynamic_payload + offset, buffer, len);
offset += len;
}
if (status == HTTP_SERVER_DATA_FINAL) {
/* All data received, reset progress. */
dynamic_payload_len = offset;
offset = 0;
}
break;
default:
return -ENOTSUP;
}
return ret;
}
struct http_resource_detail_dynamic dynamic_detail = {
.common = {
.type = HTTP_RESOURCE_TYPE_DYNAMIC,
.bitmask_of_supported_http_methods =
BIT(HTTP_GET) | BIT(HTTP_POST),
.content_type = "text/plain",
},
.cb = dynamic_cb,
.data_buffer = dynamic_buffer,
.data_buffer_len = sizeof(dynamic_buffer),
.user_data = NULL,
};
HTTP_RESOURCE_DEFINE(dynamic_resource, test_http_service, "/dynamic",
&dynamic_detail);
static int client_fd = -1;
static uint8_t buf[BUFFER_SIZE];
@ -207,6 +276,14 @@ static void expect_http2_data_frame(size_t *offset, int stream_id,
ZTEST(server_function_tests, test_http2_get_concurrent_streams)
{
static const uint8_t request_get_2_streams[] = {
TEST_HTTP2_MAGIC,
TEST_HTTP2_SETTINGS,
TEST_HTTP2_SETTINGS_ACK,
TEST_HTTP2_HEADERS_GET_ROOT_STREAM_1,
TEST_HTTP2_HEADERS_GET_INDEX_STREAM_2,
TEST_HTTP2_GOAWAY,
};
size_t offset = 0;
int ret;
@ -232,6 +309,13 @@ ZTEST(server_function_tests, test_http2_get_concurrent_streams)
ZTEST(server_function_tests, test_http2_static_get)
{
static const uint8_t request_get_static_simple[] = {
TEST_HTTP2_MAGIC,
TEST_HTTP2_SETTINGS,
TEST_HTTP2_SETTINGS_ACK,
TEST_HTTP2_HEADERS_GET_ROOT_STREAM_1,
TEST_HTTP2_GOAWAY,
};
size_t offset = 0;
int ret;
@ -309,6 +393,204 @@ ZTEST(server_function_tests, test_http1_static_get)
"Received data doesn't match expected response");
}
ZTEST(server_function_tests, test_http2_dynamic_post)
{
static const uint8_t request_post_dynamic[] = {
TEST_HTTP2_MAGIC,
TEST_HTTP2_SETTINGS,
TEST_HTTP2_SETTINGS_ACK,
TEST_HTTP2_HEADERS_POST_DYNAMIC_STREAM_1,
TEST_HTTP2_DATA_POST_DYNAMIC_STREAM_1,
TEST_HTTP2_GOAWAY,
};
size_t offset = 0;
int ret;
ret = zsock_send(client_fd, request_post_dynamic,
sizeof(request_post_dynamic), 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
memset(buf, 0, sizeof(buf));
expect_http2_settings_frame(&offset, false);
expect_http2_settings_frame(&offset, true);
expect_http2_headers_frame(&offset, TEST_STREAM_ID_1,
HTTP2_FLAG_END_HEADERS | HTTP2_FLAG_END_STREAM);
zassert_equal(dynamic_payload_len, strlen(TEST_DYNAMIC_POST_PAYLOAD),
"Wrong dynamic resource length");
zassert_mem_equal(dynamic_payload, TEST_DYNAMIC_POST_PAYLOAD,
dynamic_payload_len, "Wrong dynamic resource data");
}
ZTEST(server_function_tests, test_http1_dynamic_upgrade_post)
{
static const char http1_request[] =
"POST /dynamic HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: curl/7.68.0\r\n"
"Accept: */*\r\n"
"Content-Length: 17\r\n"
"Connection: Upgrade, HTTP2-Settings\r\n"
"Upgrade: h2c\r\n"
"HTTP2-Settings: AAMAAABkAAQAoAAAAAIAAAAA\r\n"
"\r\n"
TEST_DYNAMIC_POST_PAYLOAD;
size_t offset = 0;
int ret;
ret = zsock_send(client_fd, http1_request, strlen(http1_request), 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
memset(buf, 0, sizeof(buf));
/* Verify HTTP1 switching protocols response. */
expect_http1_switching_protocols(&offset);
/* Verify HTTP2 frames. */
expect_http2_settings_frame(&offset, false);
expect_http2_headers_frame(&offset, UPGRADE_STREAM_ID,
HTTP2_FLAG_END_HEADERS | HTTP2_FLAG_END_STREAM);
zassert_equal(dynamic_payload_len, strlen(TEST_DYNAMIC_POST_PAYLOAD),
"Wrong dynamic resource length");
zassert_mem_equal(dynamic_payload, TEST_DYNAMIC_POST_PAYLOAD,
dynamic_payload_len, "Wrong dynamic resource data");
}
ZTEST(server_function_tests, test_http1_dynamic_post)
{
static const char http1_request[] =
"POST /dynamic HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: curl/7.68.0\r\n"
"Accept: */*\r\n"
"Content-Length: 17\r\n"
"\r\n"
TEST_DYNAMIC_POST_PAYLOAD;
static const char expected_response[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"0\r\n\r\n";
size_t offset = 0;
int ret;
ret = zsock_send(client_fd, http1_request, strlen(http1_request), 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
memset(buf, 0, sizeof(buf));
test_read_data(&offset, sizeof(expected_response) - 1);
zassert_mem_equal(buf, expected_response, sizeof(expected_response) - 1,
"Received data doesn't match expected response");
zassert_equal(dynamic_payload_len, strlen(TEST_DYNAMIC_POST_PAYLOAD),
"Wrong dynamic resource length");
zassert_mem_equal(dynamic_payload, TEST_DYNAMIC_POST_PAYLOAD,
dynamic_payload_len, "Wrong dynamic resource data");
}
ZTEST(server_function_tests, test_http2_dynamic_get)
{
static const uint8_t request_get_dynamic[] = {
TEST_HTTP2_MAGIC,
TEST_HTTP2_SETTINGS,
TEST_HTTP2_SETTINGS_ACK,
TEST_HTTP2_HEADERS_GET_DYNAMIC_STREAM_1,
TEST_HTTP2_GOAWAY,
};
size_t offset = 0;
int ret;
dynamic_payload_len = strlen(TEST_DYNAMIC_GET_PAYLOAD);
memcpy(dynamic_payload, TEST_DYNAMIC_GET_PAYLOAD, dynamic_payload_len);
ret = zsock_send(client_fd, request_get_dynamic,
sizeof(request_get_dynamic), 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
memset(buf, 0, sizeof(buf));
expect_http2_settings_frame(&offset, false);
expect_http2_settings_frame(&offset, true);
expect_http2_headers_frame(&offset, TEST_STREAM_ID_1,
HTTP2_FLAG_END_HEADERS);
expect_http2_data_frame(&offset, TEST_STREAM_ID_1, TEST_DYNAMIC_GET_PAYLOAD,
strlen(TEST_DYNAMIC_GET_PAYLOAD), 0);
expect_http2_data_frame(&offset, TEST_STREAM_ID_1, NULL, 0,
HTTP2_FLAG_END_STREAM);
}
ZTEST(server_function_tests, test_http1_dynamic_upgrade_get)
{
static const char http1_request[] =
"GET /dynamic HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: curl/7.68.0\r\n"
"Accept: */*\r\n"
"Accept-Encoding: deflate, gzip, br\r\n"
"Connection: Upgrade, HTTP2-Settings\r\n"
"Upgrade: h2c\r\n"
"HTTP2-Settings: AAMAAABkAAQAoAAAAAIAAAAA\r\n"
"\r\n";
size_t offset = 0;
int ret;
dynamic_payload_len = strlen(TEST_DYNAMIC_GET_PAYLOAD);
memcpy(dynamic_payload, TEST_DYNAMIC_GET_PAYLOAD, dynamic_payload_len);
ret = zsock_send(client_fd, http1_request, strlen(http1_request), 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
memset(buf, 0, sizeof(buf));
/* Verify HTTP1 switching protocols response. */
expect_http1_switching_protocols(&offset);
/* Verify HTTP2 frames. */
expect_http2_settings_frame(&offset, false);
expect_http2_headers_frame(&offset, UPGRADE_STREAM_ID,
HTTP2_FLAG_END_HEADERS);
expect_http2_data_frame(&offset, UPGRADE_STREAM_ID, TEST_DYNAMIC_GET_PAYLOAD,
strlen(TEST_DYNAMIC_GET_PAYLOAD), 0);
expect_http2_data_frame(&offset, UPGRADE_STREAM_ID, NULL, 0,
HTTP2_FLAG_END_STREAM);
}
ZTEST(server_function_tests, test_http1_dynamic_get)
{
static const char http1_request[] =
"GET /dynamic HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: curl/7.68.0\r\n"
"Accept: */*\r\n"
"Accept-Encoding: deflate, gzip, br\r\n"
"\r\n";
static const char expected_response[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"10\r\n" TEST_DYNAMIC_GET_PAYLOAD "\r\n"
"0\r\n\r\n";
size_t offset = 0;
int ret;
dynamic_payload_len = strlen(TEST_DYNAMIC_GET_PAYLOAD);
memcpy(dynamic_payload, TEST_DYNAMIC_GET_PAYLOAD, dynamic_payload_len);
ret = zsock_send(client_fd, http1_request, strlen(http1_request), 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
memset(buf, 0, sizeof(buf));
test_read_data(&offset, sizeof(expected_response) - 1);
zassert_mem_equal(buf, expected_response, sizeof(expected_response) - 1,
"Received data doesn't match expected response");
}
ZTEST(server_function_tests, test_http1_connection_close)
{
static const char http1_request_1[] =
@ -508,6 +790,9 @@ static void http_server_tests_before(void *fixture)
ARG_UNUSED(fixture);
memset(dynamic_payload, 0, sizeof(dynamic_payload));
dynamic_payload_len = 0;
ret = http_server_start();
if (ret < 0) {
printk("Failed to start the server\n");