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
;
462 // Copy data if caller has provided a buffer.
464 if (CallbackData
->BufferSize
> CallbackData
->CopyedSize
) {
466 CallbackData
->Buffer
+ CallbackData
->CopyedSize
,
468 MIN (Length
, CallbackData
->BufferSize
- CallbackData
->CopyedSize
)
470 CallbackData
->CopyedSize
+= MIN (Length
, CallbackData
->BufferSize
- CallbackData
->CopyedSize
);
474 // The caller doesn't provide a buffer, save the block into cache list.
476 if (CallbackData
->Cache
!= NULL
) {
477 NewEntityData
= AllocatePool (sizeof (HTTP_BOOT_ENTITY_DATA
));
478 if (NewEntityData
== NULL
) {
479 return EFI_OUT_OF_RESOURCES
;
481 if (CallbackData
->NewBlock
) {
482 NewEntityData
->Block
= CallbackData
->Block
;
483 CallbackData
->Block
= NULL
;
485 NewEntityData
->DataLength
= Length
;
486 NewEntityData
->DataStart
= (UINT8
*) Data
;
487 InsertTailList (&CallbackData
->Cache
->EntityDataList
, &NewEntityData
->Link
);
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 if caller doesn't provide buffer.
572 if ((!HeaderOnly
) && (*BufferSize
== 0)) {
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.
738 ZeroMem (&ResponseBody
, sizeof (HTTP_IO_RESOPNSE_DATA
));
739 while (!HttpIsMessageComplete (Parser
)) {
741 // Allocate a block to hold the message-body, if caller doesn't provide
742 // a buffer, the block will be cached and we will allocate a new one here.
744 if (Block
== NULL
|| Context
.BufferSize
== 0) {
745 Block
= AllocatePool (HTTP_BOOT_BLOCK_SIZE
);
747 Status
= EFI_OUT_OF_RESOURCES
;
750 Context
.NewBlock
= TRUE
;
751 Context
.Block
= Block
;
753 Context
.NewBlock
= FALSE
;
756 ResponseBody
.Body
= (CHAR8
*) Block
;
757 ResponseBody
.BodyLength
= HTTP_BOOT_BLOCK_SIZE
;
758 Status
= HttpIoRecvResponse (
763 if (EFI_ERROR (Status
)) {
768 // Parse the new received block of the message-body, the block will be saved in cache.
770 Status
= HttpParseMessageBody (
772 ResponseBody
.BodyLength
,
775 if (EFI_ERROR (Status
)) {
782 // 3.5 Message-body receive & parse is completed, get the file size.
784 Status
= HttpGetEntityLength (Parser
, &ContentLength
);
785 if (EFI_ERROR (Status
)) {
789 if (*BufferSize
< ContentLength
) {
790 Status
= EFI_BUFFER_TOO_SMALL
;
792 *BufferSize
= ContentLength
;
795 // 4. Save the cache item to driver's cache list and return.
798 Cache
->EntityLength
= ContentLength
;
799 InsertTailList (&Private
->CacheList
, &Cache
->Link
);
802 if (Parser
!= NULL
) {
803 HttpFreeMsgParser (Parser
);
809 if (Parser
!= NULL
) {
810 HttpFreeMsgParser (Parser
);
812 if (Context
.Block
!= NULL
) {
813 FreePool (Context
.Block
);
815 HttpBootFreeCache (Cache
);
818 if (ResponseData
!= NULL
) {
819 FreePool (ResponseData
);
822 if (RequestData
!= NULL
) {
823 FreePool (RequestData
);
826 HttpBootFreeHeader (HttpIoHeader
);