diff options
-rw-r--r-- | NetworkPkg/HttpBootDxe/HttpBootClient.c | 182 | ||||
-rw-r--r-- | NetworkPkg/HttpBootDxe/HttpBootClient.h | 1 | ||||
-rw-r--r-- | NetworkPkg/HttpBootDxe/HttpBootDxe.h | 2 | ||||
-rw-r--r-- | NetworkPkg/HttpBootDxe/HttpBootDxe.inf | 6 | ||||
-rw-r--r-- | NetworkPkg/HttpBootDxe/HttpBootImpl.c | 73 | ||||
-rw-r--r-- | NetworkPkg/NetworkPkg.dec | 10 |
6 files changed, 252 insertions, 22 deletions
diff --git a/NetworkPkg/HttpBootDxe/HttpBootClient.c b/NetworkPkg/HttpBootDxe/HttpBootClient.c index 40f64fcb6b..858e7c2103 100644 --- a/NetworkPkg/HttpBootDxe/HttpBootClient.c +++ b/NetworkPkg/HttpBootDxe/HttpBootClient.c @@ -923,6 +923,9 @@ HttpBootGetBootFileCallback ( BufferSize has been updated with the size needed to complete
the request.
@retval EFI_ACCESS_DENIED The server needs to authenticate the client.
+ @retval EFI_NOT_READY Data transfer has timed-out, call HttpBootGetBootFile again to resume
+ the download operation using HTTP Range headers.
+ @retval EFI_UNSUPPORTED Some HTTP response header is not supported.
@retval Others Unexpected error happened.
**/
@@ -955,6 +958,10 @@ HttpBootGetBootFile ( CHAR8 BaseAuthValue[80];
EFI_HTTP_HEADER *HttpHeader;
CHAR8 *Data;
+ UINTN HeadersCount;
+ BOOLEAN ResumingOperation;
+ CHAR8 *ContentRangeResponseValue;
+ CHAR8 RangeValue[64];
ASSERT (Private != NULL);
ASSERT (Private->HttpCreated);
@@ -985,6 +992,16 @@ HttpBootGetBootFile ( }
}
+ // Check if this is a previous download that has failed and need to be resumed
+ if ((!HeaderOnly) &&
+ (Private->PartialTransferredSize > 0) &&
+ (Private->BootFileSize == *BufferSize))
+ {
+ ResumingOperation = TRUE;
+ } else {
+ ResumingOperation = FALSE;
+ }
+
//
// Not found in cache, try to download it through HTTP.
//
@@ -1014,8 +1031,23 @@ HttpBootGetBootFile ( // Accept
// User-Agent
// [Authorization]
+ // [Range]
+ // [If-Match]|[If-Unmodified-Since]
//
- HttpIoHeader = HttpIoCreateHeader ((Private->AuthData != NULL) ? 4 : 3);
+ HeadersCount = 3;
+ if (Private->AuthData != NULL) {
+ HeadersCount++;
+ }
+
+ if (ResumingOperation) {
+ HeadersCount++;
+ if (Private->LastModifiedOrEtag) {
+ HeadersCount++;
+ }
+ }
+
+ HttpIoHeader = HttpIoCreateHeader (HeadersCount);
+
if (HttpIoHeader == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ERROR_2;
@@ -1098,6 +1130,62 @@ HttpBootGetBootFile ( }
//
+ // Add HTTP header field 5 (optional): Range
+ //
+ if (ResumingOperation) {
+ // Resuming a failed download. Prepare the HTTP Range Header
+ Status = AsciiSPrint (
+ RangeValue,
+ sizeof (RangeValue),
+ "bytes=%lu-%lu",
+ Private->PartialTransferredSize,
+ Private->BootFileSize - 1
+ );
+ if (EFI_ERROR (Status)) {
+ goto ERROR_3;
+ }
+
+ Status = HttpIoSetHeader (HttpIoHeader, "Range", RangeValue);
+ if (EFI_ERROR (Status)) {
+ goto ERROR_3;
+ }
+
+ DEBUG (
+ (DEBUG_WARN | DEBUG_INFO,
+ "HttpBootGetBootFile: Resuming failed download. Range: %a\n",
+ RangeValue)
+ );
+
+ //
+ // Add HTTP header field 6 (optional): If-Match or If-Unmodified-Since
+ //
+ if (Private->LastModifiedOrEtag) {
+ if (Private->LastModifiedOrEtag[0] == '"') {
+ // An ETag value starts with "
+ DEBUG (
+ (DEBUG_WARN | DEBUG_INFO,
+ "HttpBootGetBootFile: If-Match=%a\n",
+ Private->LastModifiedOrEtag)
+ );
+ // Add If-Match header with the ETag value got from the first request.
+ Status = HttpIoSetHeader (HttpIoHeader, HTTP_HEADER_IF_MATCH, Private->LastModifiedOrEtag);
+ } else {
+ DEBUG (
+ (DEBUG_WARN | DEBUG_INFO,
+ "HttpBootGetBootFile: If-Unmodified-Since=%a\n",
+ Private->LastModifiedOrEtag)
+ );
+ // Add If-Unmodified-Since header with the timestamp value (Last-Modified) got from the first request.
+ Status = HttpIoSetHeader (HttpIoHeader, HTTP_HEADER_IF_UNMODIFIED_SINCE, Private->LastModifiedOrEtag);
+ }
+
+ if (EFI_ERROR (Status)) {
+ goto ERROR_3;
+ }
+ }
+ }
+
+ //
// 2.2 Build the rest of HTTP request info.
//
RequestData = AllocatePool (sizeof (EFI_HTTP_REQUEST_DATA));
@@ -1245,6 +1333,62 @@ HttpBootGetBootFile ( Cache->ImageType = *ImageType;
}
+ // Cache ETag or Last-Modified response header value to
+ // be used when resuming an interrupted download.
+ HttpHeader = HttpFindHeader (
+ ResponseData->HeaderCount,
+ ResponseData->Headers,
+ HTTP_HEADER_ETAG
+ );
+ if (HttpHeader == NULL) {
+ HttpHeader = HttpFindHeader (
+ ResponseData->HeaderCount,
+ ResponseData->Headers,
+ HTTP_HEADER_LAST_MODIFIED
+ );
+ }
+
+ if (HttpHeader) {
+ if (Private->LastModifiedOrEtag) {
+ FreePool (Private->LastModifiedOrEtag);
+ }
+
+ Private->LastModifiedOrEtag = AllocateCopyPool (AsciiStrSize (HttpHeader->FieldValue), HttpHeader->FieldValue);
+ }
+
+ //
+ // 3.2.2 Validate the range response. If operation is being resumed,
+ // server must respond with Content-Range.
+ //
+ if (ResumingOperation) {
+ HttpHeader = HttpFindHeader (
+ ResponseData->HeaderCount,
+ ResponseData->Headers,
+ HTTP_HEADER_CONTENT_RANGE
+ );
+ if ((HttpHeader == NULL) ||
+ (AsciiStrnCmp (HttpHeader->FieldValue, "bytes", 5) != 0))
+ {
+ Status = EFI_UNSUPPORTED;
+ goto ERROR_5;
+ }
+
+ // Gets the total size of ranged data (Content-Range: <unit> <range-start>-<range-end>/<size>)
+ // and check if it remains the same
+ ContentRangeResponseValue = AsciiStrStr (HttpHeader->FieldValue, "/");
+ if (ContentRangeResponseValue == NULL) {
+ Status = EFI_INVALID_PARAMETER;
+ goto ERROR_5;
+ }
+
+ ContentRangeResponseValue++;
+ ContentLength = AsciiStrDecimalToUintn (ContentRangeResponseValue);
+ if (ContentLength != *BufferSize) {
+ Status = EFI_INVALID_PARAMETER;
+ goto ERROR_5;
+ }
+ }
+
//
// 3.3 Init a message-body parser from the header information.
//
@@ -1295,10 +1439,15 @@ HttpBootGetBootFile ( // In identity transfer-coding there is no need to parse the message body,
// just download the message body to the user provided buffer directly.
//
+ if (ResumingOperation && ((ContentLength + Private->PartialTransferredSize) > *BufferSize)) {
+ Status = EFI_INVALID_PARAMETER;
+ goto ERROR_6;
+ }
+
ReceivedSize = 0;
while (ReceivedSize < ContentLength) {
- ResponseBody.Body = (CHAR8 *)Buffer + ReceivedSize;
- ResponseBody.BodyLength = *BufferSize - ReceivedSize;
+ ResponseBody.Body = (CHAR8 *)Buffer + (ReceivedSize + Private->PartialTransferredSize);
+ ResponseBody.BodyLength = *BufferSize - (ReceivedSize + Private->PartialTransferredSize);
Status = HttpIoRecvResponse (
&Private->HttpIo,
FALSE,
@@ -1309,6 +1458,20 @@ HttpBootGetBootFile ( Status = ResponseBody.Status;
}
+ if ((Status == EFI_TIMEOUT) || (Status == EFI_DEVICE_ERROR)) {
+ // For EFI_TIMEOUT and EFI_DEVICE_ERROR errors, we may resume the operation.
+ // We will not check if server sent Accept-Ranges header, because some back-ends
+ // do not report this header, even when supporting it. Know example: CloudFlare CDN Cache.
+ Private->PartialTransferredSize = ReceivedSize;
+ DEBUG (
+ (
+ DEBUG_WARN | DEBUG_INFO,
+ "HttpBootGetBootFile: Transfer error. Bytes transferred so far: %lu.\n",
+ ReceivedSize
+ )
+ );
+ }
+
goto ERROR_6;
}
@@ -1326,6 +1489,9 @@ HttpBootGetBootFile ( }
}
}
+
+ // download completed, there is no more partial data
+ Private->PartialTransferredSize = 0;
} else {
//
// In "chunked" transfer-coding mode, so we need to parse the received
@@ -1385,9 +1551,13 @@ HttpBootGetBootFile ( //
// 3.5 Message-body receive & parse is completed, we should be able to get the file size now.
//
- Status = HttpGetEntityLength (Parser, &ContentLength);
- if (EFI_ERROR (Status)) {
- goto ERROR_6;
+ if (!ResumingOperation) {
+ Status = HttpGetEntityLength (Parser, &ContentLength);
+ if (EFI_ERROR (Status)) {
+ goto ERROR_6;
+ }
+ } else {
+ ContentLength = Private->BootFileSize;
}
if (*BufferSize < ContentLength) {
diff --git a/NetworkPkg/HttpBootDxe/HttpBootClient.h b/NetworkPkg/HttpBootDxe/HttpBootClient.h index 86a28bc91a..406eefb542 100644 --- a/NetworkPkg/HttpBootDxe/HttpBootClient.h +++ b/NetworkPkg/HttpBootDxe/HttpBootClient.h @@ -108,6 +108,7 @@ HttpBootCreateHttpIo ( BufferSize has been updated with the size needed to complete
the request.
@retval EFI_ACCESS_DENIED The server needs to authenticate the client.
+ @retval EFI_UNSUPPORTED Some HTTP response header is not supported.
@retval Others Unexpected error happened.
**/
diff --git a/NetworkPkg/HttpBootDxe/HttpBootDxe.h b/NetworkPkg/HttpBootDxe/HttpBootDxe.h index 5ff8ad4698..193235dabb 100644 --- a/NetworkPkg/HttpBootDxe/HttpBootDxe.h +++ b/NetworkPkg/HttpBootDxe/HttpBootDxe.h @@ -214,6 +214,8 @@ struct _HTTP_BOOT_PRIVATE_DATA { CHAR8 *BootFileUri;
VOID *BootFileUriParser;
UINTN BootFileSize;
+ UINTN PartialTransferredSize;
+ CHAR8 *LastModifiedOrEtag;
BOOLEAN NoGateway;
HTTP_BOOT_IMAGE_TYPE ImageType;
diff --git a/NetworkPkg/HttpBootDxe/HttpBootDxe.inf b/NetworkPkg/HttpBootDxe/HttpBootDxe.inf index cffa642a4b..3f87e58a14 100644 --- a/NetworkPkg/HttpBootDxe/HttpBootDxe.inf +++ b/NetworkPkg/HttpBootDxe/HttpBootDxe.inf @@ -95,8 +95,10 @@ gEfiAdapterInfoUndiIpv6SupportGuid ## SOMETIMES_CONSUMES ## GUID
[Pcd]
- gEfiNetworkPkgTokenSpaceGuid.PcdAllowHttpConnections ## CONSUMES
- gEfiNetworkPkgTokenSpaceGuid.PcdHttpIoTimeout ## CONSUMES
+ gEfiNetworkPkgTokenSpaceGuid.PcdAllowHttpConnections ## CONSUMES
+ gEfiNetworkPkgTokenSpaceGuid.PcdHttpIoTimeout ## CONSUMES
+ gEfiNetworkPkgTokenSpaceGuid.PcdMaxHttpResumeRetries ## CONSUMES
+ gEfiNetworkPkgTokenSpaceGuid.PcdHttpDelayBetweenResumeRetries ## CONSUMES
[UserExtensions.TianoCore."ExtraFiles"]
HttpBootDxeExtra.uni
diff --git a/NetworkPkg/HttpBootDxe/HttpBootImpl.c b/NetworkPkg/HttpBootDxe/HttpBootImpl.c index fa27941f80..4f84e59a21 100644 --- a/NetworkPkg/HttpBootDxe/HttpBootImpl.c +++ b/NetworkPkg/HttpBootDxe/HttpBootImpl.c @@ -304,6 +304,7 @@ HttpBootGetBootFileCaller ( {
HTTP_GET_BOOT_FILE_STATE State;
EFI_STATUS Status;
+ UINT32 Retries;
if (Private->BootFileSize == 0) {
State = GetBootFileHead;
@@ -370,13 +371,40 @@ HttpBootGetBootFileCaller ( //
// Load the boot file into Buffer
//
- Status = HttpBootGetBootFile (
- Private,
- FALSE,
- BufferSize,
- Buffer,
- ImageType
- );
+ for (Retries = 1; Retries <= PcdGet32 (PcdMaxHttpResumeRetries); Retries++) {
+ Status = HttpBootGetBootFile (
+ Private,
+ FALSE,
+ BufferSize,
+ Buffer,
+ ImageType
+ );
+ if (!EFI_ERROR (Status) ||
+ ((Status != EFI_TIMEOUT) && (Status != EFI_DEVICE_ERROR)))
+ {
+ break;
+ }
+
+ //
+ // HttpBootGetBootFile returned EFI_TIMEOUT or EFI_DEVICE_ERROR.
+ // We may attempt to resume the interrupted download.
+ //
+
+ Private->HttpCreated = FALSE;
+ HttpIoDestroyIo (&Private->HttpIo);
+ Status = HttpBootCreateHttpIo (Private);
+ if (EFI_ERROR (Status)) {
+ break;
+ }
+
+ DEBUG ((DEBUG_WARN | DEBUG_INFO, "HttpBootGetBootFileCaller: NBP file download interrupted, will try to resume the operation.\n"));
+ gBS->Stall (1000 * 1000 * PcdGet32 (PcdHttpDelayBetweenResumeRetries));
+ }
+
+ if (EFI_ERROR (Status) && (Retries >= PcdGet32 (PcdMaxHttpResumeRetries))) {
+ DEBUG ((DEBUG_ERROR, "HttpBootGetBootFileCaller: Error downloading NBP file, even after trying to resume %d times.\n", Retries));
+ }
+
return Status;
case GetBootFileError:
@@ -522,12 +550,13 @@ HttpBootStop ( ZeroMem (&Private->StationIp, sizeof (EFI_IP_ADDRESS));
ZeroMem (&Private->SubnetMask, sizeof (EFI_IP_ADDRESS));
ZeroMem (&Private->GatewayIp, sizeof (EFI_IP_ADDRESS));
- Private->Port = 0;
- Private->BootFileUri = NULL;
- Private->BootFileUriParser = NULL;
- Private->BootFileSize = 0;
- Private->SelectIndex = 0;
- Private->SelectProxyType = HttpOfferTypeMax;
+ Private->Port = 0;
+ Private->BootFileUri = NULL;
+ Private->BootFileUriParser = NULL;
+ Private->BootFileSize = 0;
+ Private->SelectIndex = 0;
+ Private->SelectProxyType = HttpOfferTypeMax;
+ Private->PartialTransferredSize = 0;
if (!Private->UsingIpv6) {
//
@@ -577,6 +606,11 @@ HttpBootStop ( Private->FilePathUriParser = NULL;
}
+ if (Private->LastModifiedOrEtag != NULL) {
+ FreePool (Private->LastModifiedOrEtag);
+ Private->LastModifiedOrEtag = NULL;
+ }
+
ZeroMem (Private->OfferBuffer, sizeof (Private->OfferBuffer));
Private->OfferNum = 0;
ZeroMem (Private->OfferCount, sizeof (Private->OfferCount));
@@ -765,7 +799,8 @@ HttpBootCallback ( if (Data != NULL) {
HttpMessage = (EFI_HTTP_MESSAGE *)Data;
if ((HttpMessage->Data.Request->Method == HttpMethodGet) &&
- (HttpMessage->Data.Request->Url != NULL))
+ (HttpMessage->Data.Request->Url != NULL) &&
+ (Private->PartialTransferredSize == 0))
{
Print (L"\n URI: %s\n", HttpMessage->Data.Request->Url);
}
@@ -797,6 +832,16 @@ HttpBootCallback ( }
}
+ // If download was resumed, do not change progress variables
+ HttpHeader = HttpFindHeader (
+ HttpMessage->HeaderCount,
+ HttpMessage->Headers,
+ HTTP_HEADER_CONTENT_RANGE
+ );
+ if (HttpHeader) {
+ break;
+ }
+
HttpHeader = HttpFindHeader (
HttpMessage->HeaderCount,
HttpMessage->Headers,
diff --git a/NetworkPkg/NetworkPkg.dec b/NetworkPkg/NetworkPkg.dec index 7c4289b77b..29fc0c046c 100644 --- a/NetworkPkg/NetworkPkg.dec +++ b/NetworkPkg/NetworkPkg.dec @@ -104,6 +104,16 @@ # @Prompt Max size of total HTTP chunk transfer. the default value is 12MB.
gEfiNetworkPkgTokenSpaceGuid.PcdMaxHttpChunkTransfer|0x0C00000|UINT32|0x0000000E
+ ## The maximum number of retries while attempting to resume an
+ # interrupted HTTP download using a HTTP Range request header.
+ # @Prompt Max number of HTTP download resume retries. Default value is 5.
+ gEfiNetworkPkgTokenSpaceGuid.PcdMaxHttpResumeRetries|0x00000005|UINT32|0x00000012
+
+ ## Delay in seconds between each attempt to resume an
+ # interrupted HTTP download.
+ # @Prompt Delay in seconds between each HTTP resume retry. Default value is 2s.
+ gEfiNetworkPkgTokenSpaceGuid.PcdHttpDelayBetweenResumeRetries|0x00000002|UINT32|0x00000013
+
[PcdsFixedAtBuild, PcdsPatchableInModule]
## Indicates whether HTTP connections (i.e., unsecured) are permitted or not.
# TRUE - HTTP connections are allowed. Both the "https://" and "http://" URI schemes are permitted.
|