Skip to content
File

Blob: firmware/platform/tests/http/http_test.c

c292 lines
1#include <assert.h>
2#include <stdint.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include "esp_http_client.h"
7#include "esp_heap_caps.h"
8#include "radio_bridge.h"
9 
10#define RESPONSE_LIMIT 24576
11 
12struct fake_http_client {
13 const esp_http_client_config_t *config;
14 const char *body;
15 int body_length;
16 const char *content_type;
17};
18 
19typedef struct {
20 const uint8_t *bytes;
21 size_t length;
22} chunk;
23 
24static struct {
25 chunk chunks[4];
26 size_t count;
27 int status;
28 int perform_result;
29 int set_url_result;
30 int set_body_result;
31 int release_body_result;
32 int performs;
33 int releases;
34 int null_clears;
35 int form_defaults;
36 int closes;
37 int cleanups;
38 bool online;
39 void *response_allocation;
40 esp_http_client_handle_t client;
41} fake;
42 
43static const uint8_t request[] = "{}";
44 
45void *heap_caps_malloc(size_t length, uint32_t capabilities)
46{
47 assert(length == RESPONSE_LIMIT);
48 assert(capabilities == (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
49 fake.response_allocation = malloc(length);
50 return fake.response_allocation;
51}
52 
53esp_err_t esp_crt_bundle_attach(void *context)
54{
55 (void)context;
56 return ESP_OK;
57}
58 
59int32_t radio_network_ready(void)
60{
61 return fake.online;
62}
63 
64esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *config)
65{
66 assert(config->disable_auto_redirect);
67 assert(config->crt_bundle_attach);
68 esp_http_client_handle_t client = calloc(1, sizeof(*client));
69 assert(client);
70 client->config = config;
71 fake.client = client;
72 return client;
73}
74 
75esp_err_t esp_http_client_set_method(esp_http_client_handle_t client, int method)
76{
77 assert(client && method == HTTP_METHOD_POST);
78 return ESP_OK;
79}
80 
81esp_err_t esp_http_client_set_header(esp_http_client_handle_t client, const char *name,
82 const char *value)
83{
84 assert(client && name);
85 if (strcmp(name, "Content-Type") == 0)
86 client->content_type = value;
87 else
88 assert(value);
89 return ESP_OK;
90}
91 
92esp_err_t esp_http_client_set_url(esp_http_client_handle_t client, const char *url)
93{
94 assert(client && strstr(url, "/api/device/"));
95 return fake.set_url_result;
96}
97 
98esp_err_t esp_http_client_set_post_field(esp_http_client_handle_t client, const char *body,
99 int length)
100{
101 /* Match ESP-IDF: the pointer is stored before header work can fail. NULL
102 * also deletes Content-Type; a later body installs the form default. */
103 client->body = body;
104 client->body_length = length;
105 if (!body) {
106 assert(length == 0);
107 client->content_type = NULL;
108 fake.null_clears++;
109 return ESP_OK;
110 }
111 if (!client->content_type) {
112 client->content_type = "application/x-www-form-urlencoded";
113 fake.form_defaults++;
114 }
115 if (length == 0) {
116 fake.releases++;
117 return fake.release_body_result;
118 }
119 if (fake.set_body_result)
120 return fake.set_body_result;
121 return ESP_OK;
122}
123 
124esp_err_t esp_http_client_perform(esp_http_client_handle_t client)
125{
126 assert(client->body == (const char *)request);
127 assert(client->body_length == sizeof(request) - 1);
128 assert(strcmp(client->content_type, "application/json") == 0);
129 fake.performs++;
130 for (size_t i = 0; i < fake.count; i++) {
131 esp_http_client_event_t event = {.user_data = client->config->user_data,
132 .event_id = HTTP_EVENT_ON_DATA,
133 .data_len = (int)fake.chunks[i].length,
134 .data = (void *)fake.chunks[i].bytes};
135 assert(client->config->event_handler(&event) == ESP_OK);
136 }
137 return fake.perform_result;
138}
139 
140int esp_http_client_get_status_code(esp_http_client_handle_t client)
141{
142 assert(client);
143 return fake.status;
144}
145 
146esp_err_t esp_http_client_close(esp_http_client_handle_t client)
147{
148 assert(client->body);
149 assert(client->body != (const char *)request);
150 assert(client->body_length == 0);
151 assert(strcmp(client->content_type, "application/json") == 0);
152 fake.closes++;
153 return ESP_OK;
154}
155 
156esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client)
157{
158 assert(client->body);
159 assert(client->body != (const char *)request);
160 assert(client->body_length == 0);
161 assert(strcmp(client->content_type, "application/json") == 0);
162 /* The retained configuration remains valid until cleanup completes. */
163 assert(client->config->user_data);
164 fake.cleanups++;
165 free(client);
166 return ESP_OK;
167}
168 
169static void expect_failure(radio_http *http, const char *path, size_t request_length)
170{
171 uint8_t *large_request = NULL;
172 if (request_length > sizeof(request) - 1) {
173 large_request = calloc(request_length, 1);
174 assert(large_request);
175 }
176 const uint8_t *response = (const uint8_t *)(uintptr_t)1;
177 size_t used = SIZE_MAX;
178 assert(radio_http_post(http, path, large_request ? large_request : request, request_length,
179 &response, &used) == -1);
180 assert(response == NULL && used == 0);
181 assert(fake.client->body);
182 assert(fake.client->body != (const char *)request);
183 assert(fake.client->body_length == 0);
184 assert(strcmp(fake.client->content_type, "application/json") == 0);
185 free(large_request);
186}
187 
188static void expect_response(radio_http *http, int status, const uint8_t *bytes, size_t length)
189{
190 const uint8_t *response = NULL;
191 size_t used = SIZE_MAX;
192 int releases_before = fake.releases;
193 assert(radio_http_post(http, "heartbeat", request, sizeof(request) - 1, &response, &used) ==
194 status);
195 assert(fake.releases == releases_before + 1);
196 assert(fake.client->body);
197 assert(fake.client->body != (const char *)request);
198 assert(fake.client->body_length == 0);
199 assert(strcmp(fake.client->content_type, "application/json") == 0);
200 /* A successful response borrows the original allocation, without a copy. */
201 assert(response == fake.response_allocation && used == length);
202 if (length)
203 assert(memcmp(response, bytes, length) == 0);
204}
205 
206int main(void)
207{
208 fake.online = true;
209 fake.status = 200;
210 radio_http *http = radio_http_open();
211 assert(http);
212 
213 static const uint8_t first[] = "{\"ok\":true}";
214 fake.chunks[0] = (chunk){first, 3};
215 fake.chunks[1] = (chunk){first + 3, sizeof(first) - 4};
216 fake.count = 2;
217 expect_response(http, 200, first, sizeof(first) - 1);
218 
219 /* A shorter next response must not expose the previous response's tail. */
220 fake.chunks[0] = (chunk){request, sizeof(request) - 1};
221 fake.count = 1;
222 expect_response(http, 200, request, sizeof(request) - 1);
223 fake.count = 0;
224 fake.status = 204;
225 expect_response(http, 204, NULL, 0);
226 
227 /* Error-status bodies are still complete HTTP responses for retry policy. */
228 fake.status = 503;
229 fake.count = 1;
230 expect_response(http, 503, request, sizeof(request) - 1);
231 fake.status = 200;
232 
233 uint8_t *large = malloc(RESPONSE_LIMIT + 1);
234 assert(large);
235 memset(large, 'x', RESPONSE_LIMIT + 1);
236 fake.chunks[0] = (chunk){large, RESPONSE_LIMIT};
237 expect_response(http, 200, large, RESPONSE_LIMIT);
238 
239 /* Overflow latches across chunks, including later chunks that would fit. */
240 fake.chunks[0] = (chunk){large, RESPONSE_LIMIT - 1};
241 fake.chunks[1] = (chunk){large, 2};
242 fake.chunks[2] = (chunk){large, 1};
243 fake.count = 3;
244 int closed_before = fake.closes;
245 expect_failure(http, "heartbeat", sizeof(request) - 1);
246 assert(fake.closes == closed_before + 1);
247 fake.chunks[0] = (chunk){large, RESPONSE_LIMIT + 1};
248 fake.count = 1;
249 expect_failure(http, "heartbeat", sizeof(request) - 1);
250 
251 /* A partial transport failure never lends its partial response. */
252 fake.chunks[0] = (chunk){first, sizeof(first) - 1};
253 fake.perform_result = -1;
254 expect_failure(http, "heartbeat", sizeof(request) - 1);
255 fake.perform_result = 0;
256 expect_response(http, 200, first, sizeof(first) - 1);
257 
258 fake.set_url_result = -1;
259 expect_failure(http, "heartbeat", sizeof(request) - 1);
260 fake.set_url_result = 0;
261 fake.set_body_result = -1;
262 expect_failure(http, "heartbeat", sizeof(request) - 1);
263 fake.set_body_result = 0;
264 
265 /* A failed release still replaces the borrowed Rust pointer before return. */
266 fake.release_body_result = -1;
267 expect_failure(http, "heartbeat", sizeof(request) - 1);
268 fake.release_body_result = 0;
269 expect_response(http, 200, first, sizeof(first) - 1);
270 
271 int performed_before = fake.performs;
272 fake.online = false;
273 expect_failure(http, "heartbeat", sizeof(request) - 1);
274 fake.online = true;
275 expect_failure(http, "heartbeat", 20001);
276 char long_path[300];
277 memset(long_path, 'a', sizeof(long_path) - 1);
278 long_path[sizeof(long_path) - 1] = '\0';
279 expect_failure(http, long_path, sizeof(request) - 1);
280 assert(fake.performs == performed_before);
281 expect_response(http, 200, first, sizeof(first) - 1);
282 assert(fake.null_clears == 0);
283 assert(fake.form_defaults == 0);
284 
285 radio_http_free(http);
286 assert(fake.cleanups == 1);
287 radio_http_free(NULL);
288 free(large);
289 puts("HTTP response borrowing, bounds, retry state and cleanup passed");
290 return 0;
291}