NetworkPkg:Fix Http boot download issue.
[mirror_edk2.git] / NetworkPkg / HttpBootDxe / HttpBootImpl.c
1 /** @file
2 The implementation of EFI_LOAD_FILE_PROTOCOL for UEFI HTTP boot.
3
4 Copyright (c) 2015 - 2016, Intel Corporation. All rights reserved.<BR>
5 This program and the accompanying materials are licensed and made available under
6 the terms and conditions of the BSD License that accompanies this distribution.
7 The full text of the license may be found at
8 http://opensource.org/licenses/bsd-license.php.
9
10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12
13 **/
14
15 #include "HttpBootDxe.h"
16
17 /**
18 Enable the use of UEFI HTTP boot function.
19
20 @param[in] Private The pointer to the driver's private data.
21 @param[in] UsingIpv6 Specifies the type of IP addresses that are to be
22 used during the session that is being started.
23 Set to TRUE for IPv6, and FALSE for IPv4.
24 @param[in] FilePath The device specific path of the file to load.
25
26 @retval EFI_SUCCESS HTTP boot was successfully enabled.
27 @retval EFI_INVALID_PARAMETER Private is NULL.
28 @retval EFI_ALREADY_STARTED The driver is already in started state.
29 @retval EFI_OUT_OF_RESOURCES There are not enough resources.
30
31 **/
32 EFI_STATUS
33 HttpBootStart (
34 IN HTTP_BOOT_PRIVATE_DATA *Private,
35 IN BOOLEAN UsingIpv6,
36 IN EFI_DEVICE_PATH_PROTOCOL *FilePath
37 )
38 {
39 UINTN Index;
40 EFI_STATUS Status;
41
42 if (Private == NULL || FilePath == NULL) {
43 return EFI_INVALID_PARAMETER;
44 }
45
46 if (Private->Started) {
47 return EFI_ALREADY_STARTED;
48 }
49
50 //
51 // Detect whether using ipv6 or not, and set it to the private data.
52 //
53 if (UsingIpv6 && Private->Ip6Nic != NULL) {
54 Private->UsingIpv6 = TRUE;
55 } else if (!UsingIpv6 && Private->Ip4Nic != NULL) {
56 Private->UsingIpv6 = FALSE;
57 } else {
58 return EFI_UNSUPPORTED;
59 }
60
61 //
62 // Check whether the URI address is specified.
63 //
64 Status = HttpBootParseFilePath (FilePath, &Private->FilePathUri);
65 if (EFI_ERROR (Status)) {
66 return EFI_INVALID_PARAMETER;
67 }
68
69 if (Private->FilePathUri != NULL) {
70 Status = HttpParseUrl (
71 Private->FilePathUri,
72 (UINT32) AsciiStrLen (Private->FilePathUri),
73 FALSE,
74 &Private->FilePathUriParser
75 );
76 if (EFI_ERROR (Status)) {
77 return Status;
78 }
79 }
80
81 //
82 // Init the content of cached DHCP offer list.
83 //
84 ZeroMem (Private->OfferBuffer, sizeof (Private->OfferBuffer));
85 if (!Private->UsingIpv6) {
86 for (Index = 0; Index < HTTP_BOOT_OFFER_MAX_NUM; Index++) {
87 Private->OfferBuffer[Index].Dhcp4.Packet.Offer.Size = HTTP_BOOT_DHCP4_PACKET_MAX_SIZE;
88 }
89 } else {
90 for (Index = 0; Index < HTTP_BOOT_OFFER_MAX_NUM; Index++) {
91 Private->OfferBuffer[Index].Dhcp6.Packet.Offer.Size = HTTP_BOOT_DHCP6_PACKET_MAX_SIZE;
92 }
93 }
94
95 if (Private->UsingIpv6) {
96 //
97 // Set Ip6 policy to Automatic to start the Ip6 router discovery.
98 //
99 Status = HttpBootSetIp6Policy (Private);
100 if (EFI_ERROR (Status)) {
101 return Status;
102 }
103 }
104 Private->Started = TRUE;
105
106 return EFI_SUCCESS;
107 }
108
109 /**
110 Attempt to complete a DHCPv4 D.O.R.A or DHCPv6 S.R.A.A sequence to retrieve the boot resource information.
111
112 @param[in] Private The pointer to the driver's private data.
113
114 @retval EFI_SUCCESS Boot info was successfully retrieved.
115 @retval EFI_INVALID_PARAMETER Private is NULL.
116 @retval EFI_NOT_STARTED The driver is in stopped state.
117 @retval EFI_DEVICE_ERROR An unexpected network error occurred.
118 @retval Others Other errors as indicated.
119
120 **/
121 EFI_STATUS
122 HttpBootDhcp (
123 IN HTTP_BOOT_PRIVATE_DATA *Private
124 )
125 {
126 EFI_STATUS Status;
127
128 if (Private == NULL) {
129 return EFI_INVALID_PARAMETER;
130 }
131
132 if (!Private->Started) {
133 return EFI_NOT_STARTED;
134 }
135
136 Status = EFI_DEVICE_ERROR;
137
138 if (!Private->UsingIpv6) {
139 //
140 // Start D.O.R.A process to get a IPv4 address and other boot information.
141 //
142 Status = HttpBootDhcp4Dora (Private);
143 } else {
144 //
145 // Start S.A.R.R process to get a IPv6 address and other boot information.
146 //
147 Status = HttpBootDhcp6Sarr (Private);
148 }
149
150 return Status;
151 }
152
153 /**
154 Attempt to download the boot file through HTTP message exchange.
155
156 @param[in] Private The pointer to the driver's private data.
157 @param[in, out] BufferSize On input the size of Buffer in bytes. On output with a return
158 code of EFI_SUCCESS, the amount of data transferred to
159 Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
160 the size of Buffer required to retrieve the requested file.
161 @param[in] Buffer The memory buffer to transfer the file to. If Buffer is NULL,
162 then the size of the requested file is returned in
163 BufferSize.
164
165 @retval EFI_SUCCESS Boot file was loaded successfully.
166 @retval EFI_INVALID_PARAMETER Private is NULL.
167 @retval EFI_NOT_STARTED The driver is in stopped state.
168 @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the boot file. BufferSize has
169 been updated with the size needed to complete the request.
170 @retval EFI_DEVICE_ERROR An unexpected network error occurred.
171 @retval Others Other errors as indicated.
172
173 **/
174 EFI_STATUS
175 HttpBootLoadFile (
176 IN HTTP_BOOT_PRIVATE_DATA *Private,
177 IN OUT UINTN *BufferSize,
178 IN VOID *Buffer OPTIONAL
179 )
180 {
181 EFI_STATUS Status;
182
183 if (Private == NULL) {
184 return EFI_INVALID_PARAMETER;
185 }
186
187 if (!Private->Started) {
188 return EFI_NOT_STARTED;
189 }
190
191 Status = EFI_DEVICE_ERROR;
192
193 if (Private->BootFileUri == NULL) {
194 //
195 // Parse the cached offer to get the boot file URL first.
196 //
197 Status = HttpBootDiscoverBootInfo (Private);
198 if (EFI_ERROR (Status)) {
199 return Status;
200 }
201 }
202
203 if (!Private->HttpCreated) {
204 //
205 // Create HTTP child.
206 //
207 Status = HttpBootCreateHttpIo (Private);
208 if (EFI_ERROR (Status)) {
209 return Status;
210 }
211 }
212
213 if (Private->BootFileSize == 0) {
214 //
215 // Discover the information about the bootfile if we haven't.
216 //
217
218 //
219 // Try to use HTTP HEAD method.
220 //
221 Status = HttpBootGetBootFile (
222 Private,
223 TRUE,
224 &Private->BootFileSize,
225 NULL
226 );
227 if (EFI_ERROR (Status) && Status != EFI_BUFFER_TOO_SMALL) {
228 //
229 // Failed to get file size by HEAD method, may be trunked encoding, try HTTP GET method.
230 //
231 ASSERT (Private->BootFileSize == 0);
232 Status = HttpBootGetBootFile (
233 Private,
234 FALSE,
235 &Private->BootFileSize,
236 NULL
237 );
238 if (EFI_ERROR (Status) && Status != EFI_BUFFER_TOO_SMALL) {
239 return Status;
240 }
241 }
242 }
243
244 if (*BufferSize < Private->BootFileSize) {
245 *BufferSize = Private->BootFileSize;
246 return EFI_BUFFER_TOO_SMALL;
247 }
248
249 //
250 // Load the boot file into Buffer
251 //
252 return HttpBootGetBootFile (
253 Private,
254 FALSE,
255 BufferSize,
256 Buffer
257 );
258 }
259
260 /**
261 Disable the use of UEFI HTTP boot function.
262
263 @param[in] Private The pointer to the driver's private data.
264
265 @retval EFI_SUCCESS HTTP boot was successfully disabled.
266 @retval EFI_NOT_STARTED The driver is already in stopped state.
267 @retval EFI_INVALID_PARAMETER Private is NULL.
268 @retval Others Unexpected error when stop the function.
269
270 **/
271 EFI_STATUS
272 HttpBootStop (
273 IN HTTP_BOOT_PRIVATE_DATA *Private
274 )
275 {
276 UINTN Index;
277
278 if (Private == NULL) {
279 return EFI_INVALID_PARAMETER;
280 }
281
282 if (!Private->Started) {
283 return EFI_NOT_STARTED;
284 }
285
286 if (Private->HttpCreated) {
287 HttpIoDestroyIo (&Private->HttpIo);
288 Private->HttpCreated = FALSE;
289 }
290
291 Private->Started = FALSE;
292 ZeroMem (&Private->StationIp, sizeof (EFI_IP_ADDRESS));
293 ZeroMem (&Private->SubnetMask, sizeof (EFI_IP_ADDRESS));
294 ZeroMem (&Private->GatewayIp, sizeof (EFI_IP_ADDRESS));
295 Private->Port = 0;
296 Private->BootFileUri = NULL;
297 Private->BootFileUriParser = NULL;
298 Private->BootFileSize = 0;
299 Private->SelectIndex = 0;
300 Private->SelectProxyType = HttpOfferTypeMax;
301
302 if (!Private->UsingIpv6) {
303 //
304 // Stop and release the DHCP4 child.
305 //
306 Private->Dhcp4->Stop (Private->Dhcp4);
307 Private->Dhcp4->Configure (Private->Dhcp4, NULL);
308
309 for (Index = 0; Index < HTTP_BOOT_OFFER_MAX_NUM; Index++) {
310 if (Private->OfferBuffer[Index].Dhcp4.UriParser) {
311 HttpUrlFreeParser (Private->OfferBuffer[Index].Dhcp4.UriParser);
312 }
313 }
314 } else {
315 //
316 // Stop and release the DHCP6 child.
317 //
318 Private->Dhcp6->Stop (Private->Dhcp6);
319 Private->Dhcp6->Configure (Private->Dhcp6, NULL);
320
321 for (Index = 0; Index < HTTP_BOOT_OFFER_MAX_NUM; Index++) {
322 if (Private->OfferBuffer[Index].Dhcp6.UriParser) {
323 HttpUrlFreeParser (Private->OfferBuffer[Index].Dhcp6.UriParser);
324 }
325 }
326 }
327
328 if (Private->FilePathUri!= NULL) {
329 FreePool (Private->FilePathUri);
330 HttpUrlFreeParser (Private->FilePathUriParser);
331 Private->FilePathUri = NULL;
332 Private->FilePathUriParser = NULL;
333 }
334
335 ZeroMem (Private->OfferBuffer, sizeof (Private->OfferBuffer));
336 Private->OfferNum = 0;
337 ZeroMem (Private->OfferCount, sizeof (Private->OfferCount));
338 ZeroMem (Private->OfferIndex, sizeof (Private->OfferIndex));
339
340 HttpBootFreeCacheList (Private);
341
342 return EFI_SUCCESS;
343 }
344
345 /**
346 Causes the driver to load a specified file.
347
348 @param This Protocol instance pointer.
349 @param FilePath The device specific path of the file to load.
350 @param BootPolicy If TRUE, indicates that the request originates from the
351 boot manager is attempting to load FilePath as a boot
352 selection. If FALSE, then FilePath must match as exact file
353 to be loaded.
354 @param BufferSize On input the size of Buffer in bytes. On output with a return
355 code of EFI_SUCCESS, the amount of data transferred to
356 Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
357 the size of Buffer required to retrieve the requested file.
358 @param Buffer The memory buffer to transfer the file to. IF Buffer is NULL,
359 then the size of the requested file is returned in
360 BufferSize.
361
362 @retval EFI_SUCCESS The file was loaded.
363 @retval EFI_UNSUPPORTED The device does not support the provided BootPolicy
364 @retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or
365 BufferSize is NULL.
366 @retval EFI_NO_MEDIA No medium was present to load the file.
367 @retval EFI_DEVICE_ERROR The file was not loaded due to a device error.
368 @retval EFI_NO_RESPONSE The remote system did not respond.
369 @retval EFI_NOT_FOUND The file was not found.
370 @retval EFI_ABORTED The file load process was manually cancelled.
371 @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current directory entry.
372 BufferSize has been updated with the size needed to complete
373 the request.
374
375 **/
376 EFI_STATUS
377 EFIAPI
378 HttpBootDxeLoadFile (
379 IN EFI_LOAD_FILE_PROTOCOL *This,
380 IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
381 IN BOOLEAN BootPolicy,
382 IN OUT UINTN *BufferSize,
383 IN VOID *Buffer OPTIONAL
384 )
385 {
386 HTTP_BOOT_PRIVATE_DATA *Private;
387 HTTP_BOOT_VIRTUAL_NIC *VirtualNic;
388 BOOLEAN MediaPresent;
389 BOOLEAN UsingIpv6;
390 EFI_STATUS Status;
391
392 if (This == NULL || BufferSize == NULL || FilePath == NULL) {
393 return EFI_INVALID_PARAMETER;
394 }
395
396 //
397 // Only support BootPolicy
398 //
399 if (!BootPolicy) {
400 return EFI_UNSUPPORTED;
401 }
402
403 VirtualNic = HTTP_BOOT_VIRTUAL_NIC_FROM_LOADFILE (This);
404 Private = VirtualNic->Private;
405
406 //
407 // Check media status before HTTP boot start
408 //
409 MediaPresent = TRUE;
410 NetLibDetectMedia (Private->Controller, &MediaPresent);
411 if (!MediaPresent) {
412 return EFI_NO_MEDIA;
413 }
414
415 //
416 // Check whether the virtual nic is using IPv6 or not.
417 //
418 UsingIpv6 = FALSE;
419 if (VirtualNic == Private->Ip6Nic) {
420 UsingIpv6 = TRUE;
421 }
422
423 //
424 // Initialize HTTP boot.
425 //
426 Status = HttpBootStart (Private, UsingIpv6, FilePath);
427 if (Status == EFI_ALREADY_STARTED) {
428 //
429 // Restart the HTTP boot driver in 2 cases:
430 // 1. Http boot Driver has already been started but not on the required IP version.
431 // 2. The required boot FilePath is different with the one we produced in the device path
432 // protocol.
433 //
434 if ((UsingIpv6 != Private->UsingIpv6) || ((Private->FilePathUri != NULL) && (AsciiStrCmp (Private->BootFileUri, Private->FilePathUri) != 0))) {
435 Status = HttpBootStop (Private);
436 if (!EFI_ERROR (Status)) {
437 Status = HttpBootStart (Private, UsingIpv6, FilePath);
438 }
439 }
440 }
441
442 //
443 // Load the boot file.
444 //
445 if (Status == EFI_SUCCESS || Status == EFI_ALREADY_STARTED) {
446 Status = HttpBootLoadFile (Private, BufferSize, Buffer);
447 }
448
449 if (Status != EFI_SUCCESS && Status != EFI_BUFFER_TOO_SMALL) {
450 HttpBootStop (Private);
451 } else {
452 if (!Private->UsingIpv6) {
453 //
454 // Stop and release the DHCP4 child.
455 //
456 Private->Dhcp4->Stop (Private->Dhcp4);
457 Private->Dhcp4->Configure (Private->Dhcp4, NULL);
458 } else {
459 //
460 // Stop and release the DHCP6 child.
461 //
462 Private->Dhcp6->Stop (Private->Dhcp6);
463 Private->Dhcp6->Configure (Private->Dhcp6, NULL);
464 }
465 }
466
467 return Status;
468 }
469
470 ///
471 /// Load File Protocol instance
472 ///
473 GLOBAL_REMOVE_IF_UNREFERENCED
474 EFI_LOAD_FILE_PROTOCOL gHttpBootDxeLoadFile = {
475 HttpBootDxeLoadFile
476 };