1 /*
2  * Copyright (c) 2025 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/misc/lorem_ipsum.h>
8 #include <zephyr/net/http/client.h>
9 #include <zephyr/net/http/service.h>
10 #include <zephyr/ztest.h>
11 
12 #define SERVER_IPV6_ADDR "::1"
13 #define SERVER_PORT 8080
14 #define TEST_BUF_SIZE 1200
15 
16 static uint16_t test_http_service_port = SERVER_PORT;
17 HTTP_SERVICE_DEFINE(test_http_service, SERVER_IPV6_ADDR,
18 		    &test_http_service_port, 1, 10, NULL, NULL, NULL);
19 
20 static const char static_resource_payload[] = LOREM_IPSUM_SHORT;
21 struct http_resource_detail_static static_resource_detail = {
22 	.common = {
23 		.type = HTTP_RESOURCE_TYPE_STATIC,
24 		.bitmask_of_supported_http_methods = BIT(HTTP_GET),
25 	},
26 	.static_data = static_resource_payload,
27 	.static_data_len = sizeof(static_resource_payload) - 1,
28 };
29 HTTP_RESOURCE_DEFINE(static_resource, test_http_service, "/static",
30 		     &static_resource_detail);
31 
32 static uint8_t dynamic_buf[TEST_BUF_SIZE];
33 static size_t dynamic_len;
34 
dynamic_cb(struct http_client_ctx * client,enum http_data_status status,const struct http_request_ctx * request_ctx,struct http_response_ctx * response_ctx,void * user_data)35 static int dynamic_cb(struct http_client_ctx *client, enum http_data_status status,
36 		      const struct http_request_ctx *request_ctx,
37 		      struct http_response_ctx *response_ctx, void *user_data)
38 {
39 	static size_t offset;
40 
41 	if (status == HTTP_SERVER_DATA_ABORTED) {
42 		offset = 0;
43 		return 0;
44 	}
45 
46 	switch (client->method) {
47 	case HTTP_GET:
48 		response_ctx->body = dynamic_buf;
49 		response_ctx->body_len = dynamic_len;
50 		response_ctx->final_chunk = true;
51 		break;
52 	case HTTP_POST:
53 		if (request_ctx->data_len + offset > sizeof(dynamic_buf)) {
54 			return -ENOMEM;
55 		}
56 
57 		if (request_ctx->data_len > 0) {
58 			memcpy(dynamic_buf + offset, request_ctx->data,
59 			       request_ctx->data_len);
60 			offset += request_ctx->data_len;
61 		}
62 
63 		if (status == HTTP_SERVER_DATA_FINAL) {
64 			/* All data received, reset progress. */
65 			dynamic_len = offset;
66 			offset = 0;
67 		}
68 
69 		break;
70 	default:
71 		return -ENOTSUP;
72 	}
73 
74 	return 0;
75 }
76 
77 struct http_resource_detail_dynamic dynamic_resource_detail = {
78 	.common = {
79 		.type = HTTP_RESOURCE_TYPE_DYNAMIC,
80 		.bitmask_of_supported_http_methods =
81 			BIT(HTTP_GET) | BIT(HTTP_POST),
82 	},
83 	.cb = dynamic_cb,
84 	.user_data = NULL,
85 };
86 HTTP_RESOURCE_DEFINE(dynamic_resource, test_http_service, "/dynamic",
87 		     &dynamic_resource_detail);
88 
89 struct test_ctx {
90 	uint8_t *buf;
91 	size_t buflen;
92 	size_t offset;
93 	uint16_t status;
94 	bool abort;
95 	bool final;
96 };
97 
response_cb(struct http_response * rsp,enum http_final_call final_data,void * user_data)98 static int response_cb(struct http_response *rsp,
99 		       enum http_final_call final_data,
100 		       void *user_data)
101 {
102 	struct test_ctx *ctx = user_data;
103 
104 	if (ctx == NULL) {
105 		return 0;
106 	}
107 
108 	if (ctx->abort) {
109 		return -EBADMSG;
110 	}
111 
112 	/* Final event should only be received once. */
113 	zassert_false(ctx->final);
114 
115 	if (final_data == HTTP_DATA_FINAL) {
116 		ctx->final = true;
117 		ctx->status = rsp->http_status_code;
118 	}
119 
120 	/* Copy response body */
121 	if (ctx->buf != NULL && rsp->body_frag_start != NULL &&
122 	    rsp->body_frag_len > 0) {
123 		zassert_true(ctx->offset + rsp->body_frag_len < ctx->buflen,
124 			     "Response too long");
125 		memcpy(ctx->buf + ctx->offset, rsp->body_frag_start,
126 		       rsp->body_frag_len);
127 		ctx->offset += rsp->body_frag_len;
128 	}
129 
130 	return 0;
131 }
132 
133 static int client_fd = -1;
134 static uint8_t recv_buf[64];
135 static uint8_t response_buf[TEST_BUF_SIZE];
136 static size_t resp_offset;
137 
common_request_init(struct http_request * req)138 static void common_request_init(struct http_request *req)
139 {
140 	req->host = SERVER_IPV6_ADDR;
141 	req->protocol = "HTTP/1.1";
142 	req->response = response_cb;
143 	req->recv_buf = recv_buf;
144 	req->recv_buf_len = sizeof(recv_buf);
145 }
146 
ZTEST(http_client,test_http1_client_get)147 ZTEST(http_client, test_http1_client_get)
148 {
149 	struct http_request req = { 0 };
150 	struct test_ctx ctx = { 0 };
151 	int ret;
152 
153 	common_request_init(&req);
154 	req.method = HTTP_GET;
155 	req.url = "/static";
156 
157 	ctx.buf = response_buf;
158 	ctx.buflen = sizeof(response_buf);
159 
160 	ret = http_client_req(client_fd, &req, -1, &ctx);
161 	zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
162 	zassert_true(ctx.final, "No final event received");
163 	zassert_equal(ctx.status, 200, "Unexpected HTTP status code");
164 	zassert_equal(ctx.offset, strlen(static_resource_payload),
165 		      "Invalid payload length");
166 	zassert_mem_equal(response_buf, static_resource_payload, ctx.offset,
167 			  "Invalid payload");
168 }
169 
test_http1_client_get_cb_common(struct http_parser_settings * http_cb)170 static void test_http1_client_get_cb_common(struct http_parser_settings *http_cb)
171 {
172 	struct http_request req = { 0 };
173 	int ret;
174 
175 	common_request_init(&req);
176 	req.method = HTTP_GET;
177 	req.url = "/static";
178 	req.http_cb = http_cb;
179 
180 	ret = http_client_req(client_fd, &req, -1, NULL);
181 	zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
182 }
183 
test_common_cb(struct http_parser * parser,const char * at,size_t length)184 int test_common_cb(struct http_parser *parser, const char *at, size_t length)
185 {
186 	zassert_true(resp_offset + length <= sizeof(response_buf),
187 		     "HTTP field too long");
188 
189 	memcpy(response_buf + resp_offset, at, length);
190 	resp_offset += length;
191 
192 	return 0;
193 }
194 
ZTEST(http_client,test_http1_client_get_status_cb)195 ZTEST(http_client, test_http1_client_get_status_cb)
196 {
197 	struct http_parser_settings http_cb = {
198 		.on_status = test_common_cb,
199 	};
200 
201 	test_http1_client_get_cb_common(&http_cb);
202 
203 	zassert_str_equal(response_buf, "OK", "Wrong status");
204 }
205 
ZTEST(http_client,test_http1_client_get_body_cb)206 ZTEST(http_client, test_http1_client_get_body_cb)
207 {
208 	struct http_parser_settings http_cb = {
209 		.on_body = test_common_cb,
210 	};
211 
212 	test_http1_client_get_cb_common(&http_cb);
213 
214 	zassert_str_equal(response_buf, LOREM_IPSUM_SHORT, "Wrong body payload");
215 }
216 
217 static bool test_on_header_field;
218 
test_header_field_cb(struct http_parser * parser,const char * at,size_t length)219 int test_header_field_cb(struct http_parser *parser, const char *at, size_t length)
220 {
221 	if (resp_offset > 0 && !test_on_header_field) {
222 		response_buf[resp_offset++] = '\n';
223 	}
224 
225 	zassert_true(resp_offset + length <= sizeof(response_buf),
226 		     "HTTP field too long");
227 
228 	memcpy(response_buf + resp_offset, at, length);
229 	resp_offset += length;
230 
231 	test_on_header_field = true;
232 
233 	return 0;
234 }
235 
test_header_value_cb(struct http_parser * parser,const char * at,size_t length)236 int test_header_value_cb(struct http_parser *parser, const char *at, size_t length)
237 {
238 	if (test_on_header_field) {
239 		response_buf[resp_offset++] = ':';
240 		response_buf[resp_offset++] = ' ';
241 
242 		test_on_header_field = false;
243 	}
244 
245 	zassert_true(resp_offset + length <= sizeof(response_buf),
246 		     "HTTP field too long");
247 
248 	memcpy(response_buf + resp_offset, at, length);
249 	resp_offset += length;
250 
251 	return 0;
252 }
253 
ZTEST(http_client,test_http1_client_get_headers_cb)254 ZTEST(http_client, test_http1_client_get_headers_cb)
255 {
256 	struct http_parser_settings http_cb = {
257 		.on_header_field = test_header_field_cb,
258 		.on_header_value = test_header_value_cb,
259 	};
260 	uint8_t *header_field;
261 
262 	test_http1_client_get_cb_common(&http_cb);
263 
264 	header_field = strstr(response_buf, "Content-Type: text/html");
265 	zassert_not_null(header_field, "Header field not found");
266 	header_field = strstr(response_buf, "Content-Length: 445");
267 	zassert_not_null(header_field, "Header field not found");
268 }
269 
ZTEST(http_client,test_http1_client_get_abort)270 ZTEST(http_client, test_http1_client_get_abort)
271 {
272 	struct http_request req = { 0 };
273 	struct test_ctx ctx = { 0 };
274 	int ret;
275 
276 	common_request_init(&req);
277 	req.method = HTTP_GET;
278 	req.url = "/static";
279 
280 	ctx.abort = true;
281 
282 	ret = http_client_req(client_fd, &req, -1, &ctx);
283 	zassert_equal(ret, -ECONNABORTED,
284 		      "http_client_req() should've reported abort (%d)", ret);
285 }
286 
ZTEST(http_client,test_http1_client_get_no_resource)287 ZTEST(http_client, test_http1_client_get_no_resource)
288 {
289 	struct http_request req = { 0 };
290 	struct test_ctx ctx = { 0 };
291 	int ret;
292 
293 	common_request_init(&req);
294 	req.method = HTTP_GET;
295 	req.url = "/not_found";
296 
297 	ret = http_client_req(client_fd, &req, -1, &ctx);
298 	zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
299 	zassert_true(ctx.final, "No final event received");
300 	zassert_equal(ctx.status, 404, "Unexpected HTTP status code");
301 }
302 
ZTEST(http_client,test_http1_client_post)303 ZTEST(http_client, test_http1_client_post)
304 {
305 	struct http_request req = { 0 };
306 	struct test_ctx ctx = { 0 };
307 	int ret;
308 
309 	common_request_init(&req);
310 	req.method = HTTP_POST;
311 	req.url = "/dynamic";
312 	req.payload = LOREM_IPSUM;
313 	req.payload_len = LOREM_IPSUM_STRLEN;
314 
315 	ret = http_client_req(client_fd, &req, -1, &ctx);
316 	zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
317 	zassert_true(ctx.final, "No final event received");
318 	zassert_equal(ctx.status, 200, "Unexpected HTTP status code");
319 	zassert_equal(dynamic_len, LOREM_IPSUM_STRLEN,
320 		      "Invalid payload length uploaded");
321 	zassert_mem_equal(dynamic_buf, LOREM_IPSUM, dynamic_len,
322 			  "Invalid payload uploaded");
323 }
324 
test_payload_cb(int sock,struct http_request * req,void * user_data)325 static int test_payload_cb(int sock, struct http_request *req, void *user_data)
326 {
327 	int ret;
328 
329 	ret = zsock_send(sock, LOREM_IPSUM_SHORT, LOREM_IPSUM_SHORT_STRLEN, 0);
330 	zassert_equal(ret, LOREM_IPSUM_SHORT_STRLEN,
331 		     "Failed to send payload (%d)", ret);
332 
333 	return ret;
334 }
335 
ZTEST(http_client,test_http1_client_post_payload_cb)336 ZTEST(http_client, test_http1_client_post_payload_cb)
337 {
338 	static const char * const headers[] = {
339 		"Content-Length: " STRINGIFY(LOREM_IPSUM_SHORT_STRLEN) "\r\n",
340 		NULL
341 	};
342 	struct http_request req = { 0 };
343 	struct test_ctx ctx = { 0 };
344 	int ret;
345 
346 	common_request_init(&req);
347 	req.method = HTTP_POST;
348 	req.url = "/dynamic";
349 	req.header_fields = headers;
350 	req.payload_cb = test_payload_cb;
351 
352 	ret = http_client_req(client_fd, &req, -1, &ctx);
353 	zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
354 	zassert_true(ctx.final, "No final event received");
355 	zassert_equal(ctx.status, 200, "Unexpected HTTP status code");
356 	zassert_equal(dynamic_len, LOREM_IPSUM_SHORT_STRLEN,
357 		      "Invalid payload length uploaded %d", dynamic_len);
358 	zassert_mem_equal(dynamic_buf, LOREM_IPSUM_SHORT, dynamic_len,
359 			  "Invalid payload uploaded %d", dynamic_len);
360 }
361 
client_tests_before(void * fixture)362 static void client_tests_before(void *fixture)
363 {
364 	struct net_sockaddr_in6 sa;
365 	int ret;
366 
367 	ARG_UNUSED(fixture);
368 
369 	dynamic_len = 0;
370 	resp_offset = 0;
371 	test_on_header_field = false;
372 	memset(recv_buf, 0, sizeof(recv_buf));
373 	memset(response_buf, 0, sizeof(response_buf));
374 	memset(dynamic_buf, 0, sizeof(dynamic_buf));
375 
376 	ret = http_server_start();
377 	if (ret < 0) {
378 		printk("Failed to start the server\n");
379 		return;
380 	}
381 
382 	ret = zsock_socket(NET_AF_INET6, NET_SOCK_STREAM, NET_IPPROTO_TCP);
383 	if (ret < 0) {
384 		printk("Failed to create client socket (%d)\n", errno);
385 		return;
386 	}
387 	client_fd = ret;
388 
389 	sa.sin6_family = NET_AF_INET6;
390 	sa.sin6_port = net_htons(SERVER_PORT);
391 
392 	ret = zsock_inet_pton(NET_AF_INET6, SERVER_IPV6_ADDR, &sa.sin6_addr.s6_addr);
393 	if (ret != 1) {
394 		printk("inet_pton() failed to convert %s\n", SERVER_IPV6_ADDR);
395 		return;
396 	}
397 
398 	ret = zsock_connect(client_fd, (struct net_sockaddr *)&sa, sizeof(sa));
399 	if (ret < 0) {
400 		printk("Failed to connect (%d)\n", errno);
401 	}
402 }
403 
client_tests_after(void * fixture)404 static void client_tests_after(void *fixture)
405 {
406 	ARG_UNUSED(fixture);
407 
408 	if (client_fd >= 0) {
409 		(void)zsock_close(client_fd);
410 		client_fd = -1;
411 	}
412 
413 	(void)http_server_stop();
414 
415 	k_yield();
416 }
417 
418 ZTEST_SUITE(http_client, NULL, NULL, client_tests_before, client_tests_after, NULL);
419