Adriano Cordova | d7339f4 | 2024-12-04 00:05:28 -0300 | [diff] [blame] | 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 | */ |
| 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 | |
| 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 | */ |
| 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 | */ |
| 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 | }; |