// SPDX-License-Identifier: GPL-2.0-or-later /* * efi_selftest_http * * This unit test covers the IPv4 Config2 Protocol, Http Service Binding Protocol, * and Http Protocol. * * An Http HEAD and an Http GET request are sent to the same destination. The test * is successful if the HEAD request gets a response with a valid Content-Length header * and the subsequent GET request receives the amount of bytes informed by the previous * Content-Length header. * */ #include #include #include static struct efi_boot_services *boottime; static struct efi_http_protocol *http; static struct efi_service_binding_protocol *http_service; static struct efi_ip4_config2_protocol *ip4_config2; static efi_handle_t http_protocol_handle; static const efi_guid_t efi_http_guid = EFI_HTTP_PROTOCOL_GUID; static const efi_guid_t efi_http_service_binding_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID; static const efi_guid_t efi_ip4_config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID; static int callback_done; /* * Setup unit test. * * * @handle: handle of the loaded image * @systable: system table * Return: EFI_ST_SUCCESS for success */ static int setup(const efi_handle_t handle, const struct efi_system_table *systable) { efi_status_t ret; efi_handle_t *net_handle; efi_uintn_t num_handles; efi_handle_t *handles; struct efi_http_config_data http_config; struct efi_httpv4_access_point ipv4_node; boottime = systable->boottime; num_handles = 0; boottime->locate_handle_buffer(BY_PROTOCOL, &efi_ip4_config2_guid, NULL, &num_handles, &handles); if (!num_handles) { efi_st_error("Failed to locate ipv4 config2 protocol\n"); return EFI_ST_FAILURE; } for (net_handle = handles; num_handles--; net_handle++) { ret = boottime->open_protocol(*net_handle, &efi_ip4_config2_guid, (void **)&ip4_config2, 0, 0, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (ret != EFI_SUCCESS || !ip4_config2) continue; ret = boottime->open_protocol(*net_handle, &efi_http_service_binding_guid, (void **)&http_service, 0, 0, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (ret != EFI_SUCCESS || !http_service) continue; break; // Get first handle that supports both protocols } if (!ip4_config2 || !http_service) { efi_st_error("Failed to open ipv4 config2 or http service binding protocol\n"); return EFI_ST_FAILURE; } http_protocol_handle = NULL; ret = http_service->create_child(http_service, &http_protocol_handle); if (ret != EFI_SUCCESS || !http_protocol_handle) { efi_st_error("Failed to create an http service instance\n"); return EFI_ST_FAILURE; } ret = boottime->open_protocol(http_protocol_handle, &efi_http_guid, (void **)&http, 0, 0, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (ret != EFI_SUCCESS || !http) { efi_st_error("Failed to open http protocol\n"); return EFI_ST_FAILURE; } efi_st_printf("HTTP Service Binding: child created successfully\n"); http_config.http_version = HTTPVERSION11; http_config.is_ipv6 = false; http_config.access_point.ipv4_node = &ipv4_node; ipv4_node.use_default_address = true; ret = http->configure(http, &http_config); if (ret != EFI_SUCCESS) { efi_st_error("Failed to configure http instance\n"); return EFI_ST_FAILURE; } return EFI_ST_SUCCESS; } void EFIAPI efi_test_http_callback(struct efi_event *event, void *context) { callback_done = 1; } /* * Execute unit test. * * * Return: EFI_ST_SUCCESS for success */ static int execute(void) { efi_status_t ret; struct efi_http_request_data request_data; struct efi_http_message request_message; struct efi_http_token request_token; struct efi_http_response_data response_data; struct efi_http_message response_message; struct efi_http_token response_token; enum efi_http_status_code status_code; void *response_buffer; efi_uintn_t len, sum; char *url = "http://example.com/"; u16 url_16[64]; u16 *tmp; /* Setup may have failed */ if (!ip4_config2 || !http) { efi_st_error("Cannot proceed with test after setup failure\n"); return EFI_ST_FAILURE; } tmp = url_16; utf8_utf16_strcpy(&tmp, url); request_data.url = url_16; request_data.method = HTTP_METHOD_GET; request_message.data.request = &request_data; request_message.header_count = 3; request_message.body_length = 0; request_message.body = NULL; /* request token */ request_token.event = NULL; request_token.status = EFI_NOT_READY; request_token.message = &request_message; callback_done = 0; ret = boottime->create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, efi_test_http_callback, NULL, &request_token.event); if (ret != EFI_SUCCESS) { efi_st_error("Failed to create request event\n"); return EFI_ST_FAILURE; } ret = http->request(http, &request_token); if (ret != EFI_SUCCESS) { boottime->close_event(request_token.event); efi_st_printf("Failed to proceed with the http request\n"); return EFI_ST_SUCCESS; } while (!callback_done) http->poll(http); response_data.status_code = HTTP_STATUS_UNSUPPORTED_STATUS; response_message.data.response = &response_data; response_message.header_count = 0; response_message.headers = NULL; response_message.body_length = 0; response_message.body = NULL; response_token.event = NULL; ret = boottime->create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, efi_test_http_callback, NULL, &response_token.event); if (ret != EFI_SUCCESS) { boottime->close_event(request_token.event); efi_st_error("Failed to create response event\n"); return EFI_ST_FAILURE; } response_token.status = EFI_SUCCESS; response_token.message = &response_message; callback_done = 0; ret = http->response(http, &response_token); if (ret != EFI_SUCCESS) { efi_st_error("Failed http first response\n"); goto fail; } while (!callback_done) http->poll(http); if (response_message.data.response->status_code != HTTP_STATUS_200_OK) { status_code = response_message.data.response->status_code; if (status_code == HTTP_STATUS_404_NOT_FOUND) { efi_st_error("File not found\n"); } else { efi_st_error("Bad http status %d\n", response_message.data.response->status_code); } goto fail_free_hdr; } ret = boottime->allocate_pool(EFI_LOADER_CODE, response_message.body_length, &response_buffer); if (ret != EFI_SUCCESS) { efi_st_error("Failed allocating response buffer\n"); goto fail_free_hdr; } len = response_message.body_length; sum = 0; while (len) { response_message.data.response = NULL; response_message.header_count = 0; response_message.headers = NULL; response_message.body_length = len; response_message.body = response_buffer + sum; response_token.message = &response_message; response_token.status = EFI_NOT_READY; callback_done = 0; ret = http->response(http, &response_token); if (ret != EFI_SUCCESS) { efi_st_error("Failed http second response\n"); goto fail_free_buf; } while (!callback_done) http->poll(http); if (!response_message.body_length) break; len -= response_message.body_length; sum += response_message.body_length; } if (len) goto fail_free_buf; boottime->free_pool(response_buffer); if (response_message.headers) boottime->free_pool(response_message.headers); boottime->close_event(request_token.event); boottime->close_event(response_token.event); efi_st_printf("Efi Http request executed successfully\n"); return EFI_ST_SUCCESS; fail_free_buf: boottime->free_pool(response_buffer); fail_free_hdr: if (response_message.headers) boottime->free_pool(response_message.headers); fail: boottime->close_event(request_token.event); boottime->close_event(response_token.event); return EFI_ST_FAILURE; } /* * Tear down unit test. * * Return: EFI_ST_SUCCESS for success */ static int teardown(void) { efi_status_t ret; int exit_status = EFI_ST_SUCCESS; if (!http_service || !http_protocol_handle) { efi_st_error("No handles to destroy http instance"); exit_status = EFI_ST_FAILURE; } else { ret = http_service->destroy_child(http_service, http_protocol_handle); if (ret != EFI_SUCCESS) { efi_st_error("Failed to destroy http instance"); exit_status = EFI_ST_FAILURE; } efi_st_printf("HTTP Service Binding: child destroyed successfully\n"); } return exit_status; } EFI_UNIT_TEST(http) = { .name = "http protocol", .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, .setup = setup, .execute = execute, .teardown = teardown, #ifdef CONFIG_SANDBOX /* * Running this test on the sandbox requires setting environment * variable ethact to a network interface connected to a DHCP server and * ethrotate to 'no'. */ .on_request = true, #endif };