1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * efi_selftest_http
4  *
5  * This unit test covers the IPv4 Config2 Protocol, Http Service Binding Protocol,
6  * and Http Protocol.
7  *
8  * An Http HEAD and an Http GET request are sent to the same destination. The test
9  * is successful if the HEAD request gets a response with a valid Content-Length header
10  * and the subsequent GET request receives the amount of bytes informed by the previous
11  * Content-Length header.
12  *
13  */
14 
15 #include <efi_selftest.h>
16 #include <charset.h>
17 #include <net.h>
18 
19 static struct efi_boot_services *boottime;
20 
21 static struct efi_http_protocol *http;
22 static struct efi_service_binding_protocol *http_service;
23 static struct efi_ip4_config2_protocol *ip4_config2;
24 static efi_handle_t http_protocol_handle;
25 
26 static const efi_guid_t efi_http_guid = EFI_HTTP_PROTOCOL_GUID;
27 static const efi_guid_t efi_http_service_binding_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID;
28 static const efi_guid_t efi_ip4_config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID;
29 static int callback_done;
30 
31 /*
32  * Setup unit test.
33  *
34  *
35  * @handle:	handle of the loaded image
36  * @systable:	system table
37  * Return:	EFI_ST_SUCCESS for success
38  */
setup(const efi_handle_t handle,const struct efi_system_table * systable)39 static int setup(const efi_handle_t handle,
40 		 const struct efi_system_table *systable)
41 {
42 	efi_status_t ret;
43 	efi_handle_t *net_handle;
44 	efi_uintn_t num_handles;
45 	efi_handle_t *handles;
46 	struct efi_http_config_data http_config;
47 	struct efi_httpv4_access_point ipv4_node;
48 
49 	boottime = systable->boottime;
50 
51 	num_handles = 0;
52 	boottime->locate_handle_buffer(BY_PROTOCOL, &efi_ip4_config2_guid,
53 				       NULL, &num_handles, &handles);
54 
55 	if (!num_handles) {
56 		efi_st_error("Failed to locate ipv4 config2 protocol\n");
57 		return EFI_ST_FAILURE;
58 	}
59 
60 	for (net_handle = handles; num_handles--; net_handle++) {
61 		ret = boottime->open_protocol(*net_handle, &efi_ip4_config2_guid,
62 					      (void **)&ip4_config2, 0, 0,
63 					      EFI_OPEN_PROTOCOL_GET_PROTOCOL);
64 		if (ret != EFI_SUCCESS || !ip4_config2)
65 			continue;
66 		ret = boottime->open_protocol(*net_handle,
67 					      &efi_http_service_binding_guid,
68 					      (void **)&http_service, 0, 0,
69 					      EFI_OPEN_PROTOCOL_GET_PROTOCOL);
70 		if (ret != EFI_SUCCESS || !http_service)
71 			continue;
72 		break; // Get first handle that supports both protocols
73 	}
74 
75 	if (!ip4_config2 || !http_service) {
76 		efi_st_error("Failed to open ipv4 config2 or http service binding protocol\n");
77 		return EFI_ST_FAILURE;
78 	}
79 
80 	http_protocol_handle = NULL;
81 	ret = http_service->create_child(http_service, &http_protocol_handle);
82 	if (ret != EFI_SUCCESS || !http_protocol_handle) {
83 		efi_st_error("Failed to create an http service instance\n");
84 		return EFI_ST_FAILURE;
85 	}
86 
87 	ret = boottime->open_protocol(http_protocol_handle, &efi_http_guid,
88 				      (void **)&http, 0, 0, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
89 	if (ret != EFI_SUCCESS || !http) {
90 		efi_st_error("Failed to open http protocol\n");
91 		return EFI_ST_FAILURE;
92 	}
93 	efi_st_printf("HTTP Service Binding: child created successfully\n");
94 
95 	http_config.http_version = HTTPVERSION11;
96 	http_config.is_ipv6 = false;
97 	http_config.access_point.ipv4_node = &ipv4_node;
98 	ipv4_node.use_default_address = true;
99 
100 	ret = http->configure(http, &http_config);
101 	if (ret != EFI_SUCCESS) {
102 		efi_st_error("Failed to configure http instance\n");
103 		return EFI_ST_FAILURE;
104 	}
105 
106 	return EFI_ST_SUCCESS;
107 }
108 
efi_test_http_callback(struct efi_event * event,void * context)109 void EFIAPI efi_test_http_callback(struct efi_event *event, void *context)
110 {
111 	callback_done = 1;
112 }
113 
114 /*
115  * Execute unit test.
116  *
117  *
118  * Return:	EFI_ST_SUCCESS for success
119  */
execute(void)120 static int execute(void)
121 {
122 	efi_status_t ret;
123 	struct efi_http_request_data request_data;
124 	struct efi_http_message request_message;
125 	struct efi_http_token request_token;
126 	struct efi_http_response_data response_data;
127 	struct efi_http_message response_message;
128 	struct efi_http_token response_token;
129 	enum efi_http_status_code status_code;
130 	void *response_buffer;
131 	efi_uintn_t len, sum;
132 	char *url = "http://example.com/";
133 	u16 url_16[64];
134 	u16 *tmp;
135 
136 	/* Setup may have failed */
137 	if (!ip4_config2 || !http) {
138 		efi_st_error("Cannot proceed with test after setup failure\n");
139 		return EFI_ST_FAILURE;
140 	}
141 
142 	tmp = url_16;
143 	utf8_utf16_strcpy(&tmp, url);
144 	request_data.url = url_16;
145 	request_data.method = HTTP_METHOD_GET;
146 
147 	request_message.data.request = &request_data;
148 	request_message.header_count = 3;
149 	request_message.body_length = 0;
150 	request_message.body = NULL;
151 
152 	/* request token */
153 	request_token.event = NULL;
154 	request_token.status = EFI_NOT_READY;
155 	request_token.message = &request_message;
156 	callback_done = 0;
157 	ret = boottime->create_event(EVT_NOTIFY_SIGNAL,
158 			TPL_CALLBACK,
159 			efi_test_http_callback,
160 			NULL,
161 			&request_token.event);
162 
163 	if (ret != EFI_SUCCESS) {
164 		efi_st_error("Failed to create request event\n");
165 		return EFI_ST_FAILURE;
166 	}
167 
168 	ret = http->request(http, &request_token);
169 
170 	if (ret != EFI_SUCCESS) {
171 		boottime->close_event(request_token.event);
172 		efi_st_printf("Failed to proceed with the http request\n");
173 		return EFI_ST_SUCCESS;
174 	}
175 
176 	while (!callback_done)
177 		http->poll(http);
178 
179 	response_data.status_code = HTTP_STATUS_UNSUPPORTED_STATUS;
180 	response_message.data.response = &response_data;
181 	response_message.header_count = 0;
182 	response_message.headers = NULL;
183 	response_message.body_length = 0;
184 	response_message.body = NULL;
185 	response_token.event = NULL;
186 
187 	ret = boottime->create_event(EVT_NOTIFY_SIGNAL,
188 			TPL_CALLBACK,
189 			efi_test_http_callback,
190 			NULL,
191 			&response_token.event);
192 
193 	if (ret != EFI_SUCCESS) {
194 		boottime->close_event(request_token.event);
195 		efi_st_error("Failed to create response event\n");
196 		return EFI_ST_FAILURE;
197 	}
198 
199 	response_token.status = EFI_SUCCESS;
200 	response_token.message = &response_message;
201 
202 	callback_done = 0;
203 	ret = http->response(http, &response_token);
204 
205 	if (ret != EFI_SUCCESS) {
206 		efi_st_error("Failed http first response\n");
207 		goto fail;
208 	}
209 
210 	while (!callback_done)
211 		http->poll(http);
212 
213 	if (response_message.data.response->status_code != HTTP_STATUS_200_OK) {
214 		status_code = response_message.data.response->status_code;
215 		if (status_code == HTTP_STATUS_404_NOT_FOUND) {
216 			efi_st_error("File not found\n");
217 		} else {
218 			efi_st_error("Bad http status %d\n",
219 				     response_message.data.response->status_code);
220 		}
221 		goto fail_free_hdr;
222 	}
223 
224 	ret = boottime->allocate_pool(EFI_LOADER_CODE, response_message.body_length,
225 				      &response_buffer);
226 	if (ret != EFI_SUCCESS) {
227 		efi_st_error("Failed allocating response buffer\n");
228 		goto fail_free_hdr;
229 	}
230 
231 	len = response_message.body_length;
232 	sum = 0;
233 	while (len) {
234 		response_message.data.response = NULL;
235 		response_message.header_count = 0;
236 		response_message.headers = NULL;
237 		response_message.body_length = len;
238 		response_message.body = response_buffer + sum;
239 
240 		response_token.message = &response_message;
241 		response_token.status = EFI_NOT_READY;
242 
243 		callback_done = 0;
244 		ret = http->response(http, &response_token);
245 		if (ret != EFI_SUCCESS) {
246 			efi_st_error("Failed http second response\n");
247 			goto fail_free_buf;
248 		}
249 
250 		while (!callback_done)
251 			http->poll(http);
252 
253 		if (!response_message.body_length)
254 			break;
255 
256 		len -= response_message.body_length;
257 		sum += response_message.body_length;
258 	}
259 
260 	if (len)
261 		goto fail_free_buf;
262 
263 	boottime->free_pool(response_buffer);
264 	if (response_message.headers)
265 		boottime->free_pool(response_message.headers);
266 	boottime->close_event(request_token.event);
267 	boottime->close_event(response_token.event);
268 	efi_st_printf("Efi Http request executed successfully\n");
269 	return EFI_ST_SUCCESS;
270 
271 fail_free_buf:
272 	boottime->free_pool(response_buffer);
273 fail_free_hdr:
274 	if (response_message.headers)
275 		boottime->free_pool(response_message.headers);
276 fail:
277 	boottime->close_event(request_token.event);
278 	boottime->close_event(response_token.event);
279 	return EFI_ST_FAILURE;
280 }
281 
282 /*
283  * Tear down unit test.
284  *
285  * Return:	EFI_ST_SUCCESS for success
286  */
teardown(void)287 static int teardown(void)
288 {
289 	efi_status_t ret;
290 	int exit_status = EFI_ST_SUCCESS;
291 
292 	if (!http_service || !http_protocol_handle) {
293 		efi_st_error("No handles to destroy http instance");
294 		exit_status = EFI_ST_FAILURE;
295 	} else {
296 		ret = http_service->destroy_child(http_service, http_protocol_handle);
297 		if (ret != EFI_SUCCESS) {
298 			efi_st_error("Failed to destroy http instance");
299 			exit_status = EFI_ST_FAILURE;
300 		}
301 		efi_st_printf("HTTP Service Binding: child destroyed successfully\n");
302 	}
303 
304 	return exit_status;
305 }
306 
307 EFI_UNIT_TEST(http) = {
308 	.name = "http protocol",
309 	.phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
310 	.setup = setup,
311 	.execute = execute,
312 	.teardown = teardown,
313 #ifdef CONFIG_SANDBOX
314 	/*
315 	 * Running this test on the sandbox requires setting environment
316 	 * variable ethact to a network interface connected to a DHCP server and
317 	 * ethrotate to 'no'.
318 	 */
319 	.on_request = true,
320 #endif
321 };
322