2 Implementation of the boot file download function.
4 Copyright (c) 2015, 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.
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.
15 #include "HttpBootDxe.h"
18 Update the IP and URL device path node to include the boot resource information.
20 @param[in] Private The pointer to the driver's private data.
22 @retval EFI_SUCCESS Device patch successfully updated.
23 @retval EFI_OUT_OF_RESOURCES Could not allocate needed resources.
24 @retval Others Unexpected error happened.
28 HttpBootUpdateDevicePath (
29 IN HTTP_BOOT_PRIVATE_DATA
*Private
33 EFI_DEVICE_PATH_PROTOCOL
*TmpDevicePath
;
34 EFI_DEVICE_PATH_PROTOCOL
*NewDevicePath
;
41 // Update the IP node with DHCP assigned information.
43 if (!Private
->UsingIpv6
) {
44 Node
= AllocateZeroPool (sizeof (IPv4_DEVICE_PATH
));
46 return EFI_OUT_OF_RESOURCES
;
48 Node
->Ipv4
.Header
.Type
= MESSAGING_DEVICE_PATH
;
49 Node
->Ipv4
.Header
.SubType
= MSG_IPv4_DP
;
50 SetDevicePathNodeLength (Node
, sizeof (IPv4_DEVICE_PATH
));
51 CopyMem (&Node
->Ipv4
.LocalIpAddress
, &Private
->StationIp
, sizeof (EFI_IPv4_ADDRESS
));
52 Node
->Ipv4
.RemotePort
= Private
->Port
;
53 Node
->Ipv4
.Protocol
= EFI_IP_PROTO_TCP
;
54 Node
->Ipv4
.StaticIpAddress
= FALSE
;
55 CopyMem (&Node
->Ipv4
.GatewayIpAddress
, &Private
->GatewayIp
, sizeof (EFI_IPv4_ADDRESS
));
56 CopyMem (&Node
->Ipv4
.SubnetMask
, &Private
->SubnetMask
, sizeof (EFI_IPv4_ADDRESS
));
58 TmpDevicePath
= AppendDevicePathNode (Private
->ParentDevicePath
, (EFI_DEVICE_PATH_PROTOCOL
*) Node
);
60 if (TmpDevicePath
== NULL
) {
61 return EFI_OUT_OF_RESOURCES
;
68 // Update the URI node with the boot file URI.
70 Length
= sizeof (EFI_DEVICE_PATH_PROTOCOL
) + AsciiStrSize (Private
->BootFileUri
);
71 Node
= AllocatePool (Length
);
73 FreePool (TmpDevicePath
);
74 return EFI_OUT_OF_RESOURCES
;
76 Node
->DevPath
.Type
= MESSAGING_DEVICE_PATH
;
77 Node
->DevPath
.SubType
= MSG_URI_DP
;
78 SetDevicePathNodeLength (Node
, Length
);
79 CopyMem ((UINT8
*) Node
+ sizeof (EFI_DEVICE_PATH_PROTOCOL
), Private
->BootFileUri
, AsciiStrSize (Private
->BootFileUri
));
81 NewDevicePath
= AppendDevicePathNode (TmpDevicePath
, (EFI_DEVICE_PATH_PROTOCOL
*) Node
);
83 FreePool (TmpDevicePath
);
84 if (NewDevicePath
== NULL
) {
85 return EFI_OUT_OF_RESOURCES
;
89 // Reinstall the device path protocol of the child handle.
91 Status
= gBS
->ReinstallProtocolInterface (
93 &gEfiDevicePathProtocolGuid
,
97 if (EFI_ERROR (Status
)) {
101 FreePool (Private
->DevicePath
);
102 Private
->DevicePath
= NewDevicePath
;
107 Parse the boot file URI information from the selected Dhcp4 offer packet.
109 @param[in] Private The pointer to the driver's private data.
111 @retval EFI_SUCCESS Successfully parsed out all the boot information.
112 @retval Others Failed to parse out the boot information.
116 HttpBootExtractUriInfo (
117 IN HTTP_BOOT_PRIVATE_DATA
*Private
120 HTTP_BOOT_DHCP4_PACKET_CACHE
*SelectOffer
;
121 HTTP_BOOT_DHCP4_PACKET_CACHE
*HttpOffer
;
124 EFI_DHCP4_PACKET_OPTION
*Option
;
127 ASSERT (Private
!= NULL
);
128 ASSERT (Private
->SelectIndex
!= 0);
129 SelectIndex
= Private
->SelectIndex
- 1;
130 ASSERT (SelectIndex
< HTTP_BOOT_OFFER_MAX_NUM
);
132 Status
= EFI_SUCCESS
;
135 // SelectOffer contains the IP address configuration and name server configuration.
136 // HttpOffer contains the boot file URL.
138 SelectOffer
= &Private
->OfferBuffer
[SelectIndex
].Dhcp4
;
139 if ((SelectOffer
->OfferType
== HttpOfferTypeDhcpIpUri
) || (SelectOffer
->OfferType
== HttpOfferTypeDhcpNameUriDns
)) {
140 HttpOffer
= SelectOffer
;
142 ASSERT (Private
->SelectProxyType
!= HttpOfferTypeMax
);
143 ProxyIndex
= Private
->OfferIndex
[Private
->SelectProxyType
][0];
144 HttpOffer
= &Private
->OfferBuffer
[ProxyIndex
].Dhcp4
;
148 // Configure the default DNS server if server assigned.
150 if ((SelectOffer
->OfferType
== HttpOfferTypeDhcpNameUriDns
) || (SelectOffer
->OfferType
== HttpOfferTypeDhcpDns
)) {
151 Option
= SelectOffer
->OptList
[HTTP_BOOT_DHCP4_TAG_INDEX_DNS_SERVER
];
152 ASSERT (Option
!= NULL
);
153 Status
= HttpBootRegisterIp4Dns (
158 if (EFI_ERROR (Status
)) {
164 // Extract the port from URL, and use default HTTP port 80 if not provided.
166 Status
= HttpUrlGetPort (
167 (CHAR8
*) HttpOffer
->OptList
[HTTP_BOOT_DHCP4_TAG_INDEX_BOOTFILE
]->Data
,
168 HttpOffer
->UriParser
,
171 if (EFI_ERROR (Status
) || Private
->Port
== 0) {
176 // Record the URI of boot file from the selected HTTP offer.
178 Private
->BootFileUriParser
= HttpOffer
->UriParser
;
179 Private
->BootFileUri
= (CHAR8
*) HttpOffer
->OptList
[HTTP_BOOT_DHCP4_TAG_INDEX_BOOTFILE
]->Data
;
183 // All boot informations are valid here.
185 AsciiPrint ("\n URI: %a", Private
->BootFileUri
);
188 // Update the device path to include the IP and boot URI information.
190 Status
= HttpBootUpdateDevicePath (Private
);
196 Discover all the boot information for boot file.
198 @param[in, out] Private The pointer to the driver's private data.
200 @retval EFI_SUCCESS Successfully obtained all the boot information .
201 @retval Others Failed to retrieve the boot information.
205 HttpBootDiscoverBootInfo (
206 IN OUT HTTP_BOOT_PRIVATE_DATA
*Private
212 // Start D.O.R.A/S.A.R.R exchange to acquire station ip address and
213 // other Http boot information.
215 Status
= HttpBootDhcp (Private
);
216 if (EFI_ERROR (Status
)) {
220 if (!Private
->UsingIpv6
) {
221 Status
= HttpBootExtractUriInfo (Private
);
230 Create a HttpIo instance for the file download.
232 @param[in] Private The pointer to the driver's private data.
234 @retval EFI_SUCCESS Successfully created.
235 @retval Others Failed to create HttpIo.
239 HttpBootCreateHttpIo (
240 IN HTTP_BOOT_PRIVATE_DATA
*Private
243 HTTP_IO_CONFIG_DATA ConfigData
;
246 ASSERT (Private
!= NULL
);
248 ZeroMem (&ConfigData
, sizeof (HTTP_IO_CONFIG_DATA
));
249 if (!Private
->UsingIpv6
) {
250 ConfigData
.Config4
.HttpVersion
= HttpVersion11
;
251 ConfigData
.Config4
.RequestTimeOut
= HTTP_BOOT_REQUEST_TIMEOUT
;
252 IP4_COPY_ADDRESS (&ConfigData
.Config4
.LocalIp
, &Private
->StationIp
.v4
);
253 IP4_COPY_ADDRESS (&ConfigData
.Config4
.SubnetMask
, &Private
->SubnetMask
.v4
);
258 Status
= HttpIoCreateIo (
261 Private
->UsingIpv6
? IP_VERSION_6
: IP_VERSION_4
,
265 if (EFI_ERROR (Status
)) {
269 Private
->HttpCreated
= TRUE
;
274 Get the file content from cached data.
276 @param[in] Private The pointer to the driver's private data.
277 @param[in] Uri Uri of the file to be retrieved from cache.
278 @param[in, out] BufferSize On input the size of Buffer in bytes. On output with a return
279 code of EFI_SUCCESS, the amount of data transferred to
280 Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
281 the size of Buffer required to retrieve the requested file.
282 @param[out] Buffer The memory buffer to transfer the file to. IF Buffer is NULL,
283 then the size of the requested file is returned in
286 @retval EFI_SUCCESS Successfully created.
287 @retval Others Failed to create HttpIo.
291 HttpBootGetFileFromCache (
292 IN HTTP_BOOT_PRIVATE_DATA
*Private
,
294 IN OUT UINTN
*BufferSize
,
300 HTTP_BOOT_CACHE_CONTENT
*Cache
;
301 HTTP_BOOT_ENTITY_DATA
*EntityData
;
304 if (Uri
== NULL
|| BufferSize
== 0 || Buffer
== NULL
) {
305 return EFI_INVALID_PARAMETER
;
308 NET_LIST_FOR_EACH (Entry
, &Private
->CacheList
) {
309 Cache
= NET_LIST_USER_STRUCT (Entry
, HTTP_BOOT_CACHE_CONTENT
, Link
);
311 // Compare the URI to see whether we already have a cache for this file.
313 if ((Cache
->RequestData
!= NULL
) &&
314 (Cache
->RequestData
->Url
!= NULL
) &&
315 (StrCmp (Uri
, Cache
->RequestData
->Url
) == 0))
318 // Hit cache, check buffer size.
320 if (*BufferSize
< Cache
->EntityLength
) {
321 *BufferSize
= Cache
->EntityLength
;
322 return EFI_BUFFER_TOO_SMALL
;
326 // Fill data to buffer.
329 NET_LIST_FOR_EACH (Entry2
, &Cache
->EntityDataList
) {
330 EntityData
= NET_LIST_USER_STRUCT (Entry2
, HTTP_BOOT_ENTITY_DATA
, Link
);
331 if (*BufferSize
> CopyedSize
) {
334 EntityData
->DataStart
,
335 MIN (EntityData
->DataLength
, *BufferSize
- CopyedSize
)
337 CopyedSize
+= MIN (EntityData
->DataLength
, *BufferSize
- CopyedSize
);
340 *BufferSize
= CopyedSize
;
345 return EFI_NOT_FOUND
;
349 Release all the resource of a cache item.
351 @param[in] Cache The pointer to the cache item.
356 IN HTTP_BOOT_CACHE_CONTENT
*Cache
361 LIST_ENTRY
*NextEntry
;
362 HTTP_BOOT_ENTITY_DATA
*EntityData
;
366 // Free the request data
368 if (Cache
->RequestData
!= NULL
) {
369 if (Cache
->RequestData
->Url
!= NULL
) {
370 FreePool (Cache
->RequestData
->Url
);
372 FreePool (Cache
->RequestData
);
376 // Free the response header
378 if (Cache
->ResponseData
!= NULL
) {
379 if (Cache
->ResponseData
->Headers
!= NULL
) {
380 for (Index
= 0; Index
< Cache
->ResponseData
->HeaderCount
; Index
++) {
381 FreePool (Cache
->ResponseData
->Headers
[Index
].FieldName
);
382 FreePool (Cache
->ResponseData
->Headers
[Index
].FieldValue
);
384 FreePool (Cache
->ResponseData
->Headers
);
389 // Free the response body
391 NET_LIST_FOR_EACH_SAFE (Entry
, NextEntry
, &Cache
->EntityDataList
) {
392 EntityData
= NET_LIST_USER_STRUCT (Entry
, HTTP_BOOT_ENTITY_DATA
, Link
);
393 if (EntityData
->Block
!= NULL
) {
394 FreePool (EntityData
->Block
);
396 RemoveEntryList (&EntityData
->Link
);
397 FreePool (EntityData
);
405 Clean up all cached data.
407 @param[in] Private The pointer to the driver's private data.
411 HttpBootFreeCacheList (
412 IN HTTP_BOOT_PRIVATE_DATA
*Private
416 LIST_ENTRY
*NextEntry
;
417 HTTP_BOOT_CACHE_CONTENT
*Cache
;
419 NET_LIST_FOR_EACH_SAFE (Entry
, NextEntry
, &Private
->CacheList
) {
420 Cache
= NET_LIST_USER_STRUCT (Entry
, HTTP_BOOT_CACHE_CONTENT
, Link
);
421 RemoveEntryList (&Cache
->Link
);
422 HttpBootFreeCache (Cache
);
427 A callback function to intercept events during message parser.
429 This function will be invoked during HttpParseMessageBody() with various events type. An error
430 return status of the callback function will cause the HttpParseMessageBody() aborted.
432 @param[in] EventType Event type of this callback call.
433 @param[in] Data A pointer to data buffer.
434 @param[in] Length Length in bytes of the Data.
435 @param[in] Context Callback context set by HttpInitMsgParser().
437 @retval EFI_SUCCESS Continue to parser the message body.
438 @retval Others Abort the parse.
443 HttpBootGetBootFileCallback (
444 IN HTTP_BODY_PARSE_EVENT EventType
,
450 HTTP_BOOT_CALLBACK_DATA
*CallbackData
;
451 HTTP_BOOT_ENTITY_DATA
*NewEntityData
;
454 // We only care about the entity data.
456 if (EventType
!= BodyParseEventOnData
) {
460 CallbackData
= (HTTP_BOOT_CALLBACK_DATA
*) Context
;
463 // Save the data into cache list.
465 NewEntityData
= AllocatePool (sizeof (HTTP_BOOT_ENTITY_DATA
));
466 if (NewEntityData
== NULL
) {
467 return EFI_OUT_OF_RESOURCES
;
469 if (CallbackData
->NewBlock
) {
470 NewEntityData
->Block
= CallbackData
->Block
;
471 CallbackData
->Block
= NULL
;
473 NewEntityData
->DataLength
= Length
;
474 NewEntityData
->DataStart
= (UINT8
*) Data
;
475 InsertTailList (&CallbackData
->Cache
->EntityDataList
, &NewEntityData
->Link
);
478 // Copy data if caller has provided a buffer.
480 if (CallbackData
->BufferSize
> CallbackData
->CopyedSize
) {
482 CallbackData
->Buffer
+ CallbackData
->CopyedSize
,
484 MIN (Length
, CallbackData
->BufferSize
- CallbackData
->CopyedSize
)
486 CallbackData
->CopyedSize
+= MIN (Length
, CallbackData
->BufferSize
- CallbackData
->CopyedSize
);
493 This function download the boot file by using UEFI HTTP protocol.
495 @param[in] Private The pointer to the driver's private data.
496 @param[in] HeaderOnly Only request the response header, it could save a lot of time if
497 the caller only want to know the size of the requested file.
498 @param[in, out] BufferSize On input the size of Buffer in bytes. On output with a return
499 code of EFI_SUCCESS, the amount of data transferred to
500 Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
501 the size of Buffer required to retrieve the requested file.
502 @param[out] Buffer The memory buffer to transfer the file to. IF Buffer is NULL,
503 then the size of the requested file is returned in
506 @retval EFI_SUCCESS The file was loaded.
507 @retval EFI_INVALID_PARAMETER BufferSize is NULL or Buffer Size is not NULL but Buffer is NULL.
508 @retval EFI_OUT_OF_RESOURCES Could not allocate needed resources
509 @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current directory entry.
510 BufferSize has been updated with the size needed to complete
512 @retval Others Unexpected error happened.
516 HttpBootGetBootFile (
517 IN HTTP_BOOT_PRIVATE_DATA
*Private
,
518 IN BOOLEAN HeaderOnly
,
519 IN OUT UINTN
*BufferSize
,
525 EFI_HTTP_REQUEST_DATA
*RequestData
;
526 HTTP_IO_RESOPNSE_DATA
*ResponseData
;
527 HTTP_IO_RESOPNSE_DATA ResponseBody
;
529 HTTP_IO_HEADER
*HttpIoHeader
;
531 HTTP_BOOT_CALLBACK_DATA Context
;
533 HTTP_BOOT_CACHE_CONTENT
*Cache
;
537 ASSERT (Private
!= NULL
);
538 ASSERT (Private
->HttpCreated
);
540 if (BufferSize
== NULL
) {
541 return EFI_INVALID_PARAMETER
;
544 if (*BufferSize
!= 0 && Buffer
== NULL
) {
545 return EFI_INVALID_PARAMETER
;
549 // First, check whether we already cached the requested Uri.
551 Url
= AllocatePool ((AsciiStrLen (Private
->BootFileUri
) + 1) * sizeof (CHAR16
));
553 return EFI_OUT_OF_RESOURCES
;
555 AsciiStrToUnicodeStr (Private
->BootFileUri
, Url
);
557 Status
= HttpBootGetFileFromCache (Private
, Url
, BufferSize
, Buffer
);
558 if (Status
!= EFI_NOT_FOUND
) {
565 // Not found in cache, try to download it through HTTP.
569 // 1. Create a temp cache item for the requested URI.
573 Cache
= AllocateZeroPool (sizeof (HTTP_BOOT_CACHE_CONTENT
));
575 Status
= EFI_OUT_OF_RESOURCES
;
578 InitializeListHead (&Cache
->EntityDataList
);
582 // 2. Send HTTP request message.
586 // 2.1 Build HTTP header for the request, 3 header is needed to download a boot file:
591 HttpIoHeader
= HttpBootCreateHeader (3);
592 if (HttpIoHeader
== NULL
) {
593 Status
= EFI_OUT_OF_RESOURCES
;
598 // Add HTTP header field 1: Host
601 Status
= HttpUrlGetHostName (
602 Private
->BootFileUri
,
603 Private
->BootFileUriParser
,
606 if (EFI_ERROR (Status
)) {
609 Status
= HttpBootSetHeader (
611 HTTP_FIELD_NAME_HOST
,
615 if (EFI_ERROR (Status
)) {
620 // Add HTTP header field 2: Accept
622 Status
= HttpBootSetHeader (
624 HTTP_FIELD_NAME_ACCEPT
,
627 if (EFI_ERROR (Status
)) {
632 // Add HTTP header field 3: User-Agent
634 Status
= HttpBootSetHeader (
636 HTTP_FIELD_NAME_USER_AGENT
,
637 HTTP_USER_AGENT_EFI_HTTP_BOOT
639 if (EFI_ERROR (Status
)) {
644 // 2.2 Build the rest of HTTP request info.
646 RequestData
= AllocatePool (sizeof (EFI_HTTP_REQUEST_DATA
));
647 if (RequestData
== NULL
) {
648 Status
= EFI_OUT_OF_RESOURCES
;
651 RequestData
->Method
= HeaderOnly
? HttpMethodHead
: HttpMethodGet
;
652 RequestData
->Url
= Url
;
653 if (RequestData
->Url
== NULL
) {
654 Status
= EFI_OUT_OF_RESOURCES
;
657 AsciiStrToUnicodeStr (Private
->BootFileUri
, RequestData
->Url
);
660 // 2.3 Record the request info in a temp cache item.
663 Cache
->RequestData
= RequestData
;
667 // 2.4 Send out the request to HTTP server.
669 HttpIo
= &Private
->HttpIo
;
670 Status
= HttpIoSendRequest (
673 HttpIoHeader
->HeaderCount
,
674 HttpIoHeader
->Headers
,
678 if (EFI_ERROR (Status
)) {
683 // 3. Receive HTTP response message.
687 // 3.1 First step, use zero BodyLength to only receive the response headers.
689 ResponseData
= AllocateZeroPool (sizeof(HTTP_IO_RESOPNSE_DATA
));
690 if (ResponseData
== NULL
) {
691 Status
= EFI_OUT_OF_RESOURCES
;
694 Status
= HttpIoRecvResponse (
699 if (EFI_ERROR (Status
)) {
704 // 3.2 Cache the response header.
707 Cache
->ResponseData
= ResponseData
;
711 // 3.3 Init a message-body parser from the header information.
714 Context
.NewBlock
= FALSE
;
715 Context
.Block
= NULL
;
716 Context
.CopyedSize
= 0;
717 Context
.Buffer
= Buffer
;
718 Context
.BufferSize
= *BufferSize
;
719 Context
.Cache
= Cache
;
720 Status
= HttpInitMsgParser (
721 HeaderOnly
? HttpMethodHead
: HttpMethodGet
,
722 ResponseData
->Response
.StatusCode
,
723 ResponseData
->HeaderCount
,
724 ResponseData
->Headers
,
725 HttpBootGetBootFileCallback
,
729 if (EFI_ERROR (Status
)) {
734 // 3.4 Continue to receive and parse message-body if needed.
737 ZeroMem (&ResponseBody
, sizeof (HTTP_IO_RESOPNSE_DATA
));
738 while (!HttpIsMessageComplete (Parser
)) {
740 // Allocate a new block to hold the message-body.
742 Block
= AllocatePool (HTTP_BOOT_BLOCK_SIZE
);
744 Status
= EFI_OUT_OF_RESOURCES
;
747 ResponseBody
.Body
= (CHAR8
*) Block
;
748 ResponseBody
.BodyLength
= HTTP_BOOT_BLOCK_SIZE
;
749 Status
= HttpIoRecvResponse (
754 if (EFI_ERROR (Status
)) {
759 // Parse the new received block of the message-body, the block will be saved in cache.
761 Context
.NewBlock
= TRUE
;
762 Context
.Block
= Block
;
763 Status
= HttpParseMessageBody (
765 ResponseBody
.BodyLength
,
768 if (EFI_ERROR (Status
)) {
775 // 3.5 Message-body receive & parse is completed, get the file size.
777 Status
= HttpGetEntityLength (Parser
, &ContentLength
);
778 if (EFI_ERROR (Status
)) {
782 if (*BufferSize
< ContentLength
) {
783 Status
= EFI_BUFFER_TOO_SMALL
;
785 *BufferSize
= ContentLength
;
788 // 4. Save the cache item to driver's cache list and return.
791 Cache
->EntityLength
= ContentLength
;
792 InsertTailList (&Private
->CacheList
, &Cache
->Link
);
795 if (Parser
!= NULL
) {
796 HttpFreeMsgParser (Parser
);
802 if (Parser
!= NULL
) {
803 HttpFreeMsgParser (Parser
);
805 if (Context
.Block
!= NULL
) {
806 FreePool (Context
.Block
);
808 HttpBootFreeCache (Cache
);
811 if (ResponseData
!= NULL
) {
812 FreePool (ResponseData
);
815 if (RequestData
!= NULL
) {
816 FreePool (RequestData
);
819 HttpBootFreeHeader (HttpIoHeader
);