[edk2-devel] [[edk2-staging/HttpProxy] 3/3] NetworkPkg/HttpBootDxe: Add Support for HTTPS Proxy Server for HTTP Boot

Saloni Kasbekar posted 1 patch 6 days, 5 hours ago
Failed in applying to current master (apply log)
NetworkPkg/HttpBootDxe/HttpBootClient.c    | 211 ++++++++++++++++-
NetworkPkg/HttpBootDxe/HttpBootClient.h    |  15 ++
NetworkPkg/HttpBootDxe/HttpBootDxe.h       |   6 +
NetworkPkg/HttpBootDxe/HttpBootImpl.c      | 262 ++++++++++++++++-----
NetworkPkg/HttpBootDxe/HttpBootImpl.h      |   8 +
NetworkPkg/HttpBootDxe/HttpBootSupport.c   |  24 +-
NetworkPkg/HttpBootDxe/HttpBootSupport.h   |   8 +-
NetworkPkg/HttpDxe/HttpDriver.h            |   2 +
NetworkPkg/HttpDxe/HttpDxe.inf             |   1 +
NetworkPkg/HttpDxe/HttpImpl.c              | 139 +++++++++--
NetworkPkg/HttpDxe/HttpProto.c             |  41 ++--
NetworkPkg/HttpDxe/HttpProto.h             |  14 +-
NetworkPkg/HttpDxe/HttpsSupport.c          |  14 +-
NetworkPkg/Library/DxeHttpLib/DxeHttpLib.c |   5 +
14 files changed, 642 insertions(+), 108 deletions(-)
[edk2-devel] [[edk2-staging/HttpProxy] 3/3] NetworkPkg/HttpBootDxe: Add Support for HTTPS Proxy Server for HTTP Boot
Posted by Saloni Kasbekar 6 days, 5 hours ago
Add CONNECT HTTP command to create a tunnel from Proxy to EndPoint Server.
Process the multi-URI device path in the input FilePath.

Cc: Maciej Rabeda <maciej.rabeda@linux.intel.com>
Cc: Wu Jiaxin <jiaxin.wu@intel.com>
Cc: Siyuan Fu <siyuan.fu@intel.com>
Signed-off-by: Saloni Kasbekar <saloni.kasbekar@intel.com>
---
 NetworkPkg/HttpBootDxe/HttpBootClient.c    | 211 ++++++++++++++++-
 NetworkPkg/HttpBootDxe/HttpBootClient.h    |  15 ++
 NetworkPkg/HttpBootDxe/HttpBootDxe.h       |   6 +
 NetworkPkg/HttpBootDxe/HttpBootImpl.c      | 262 ++++++++++++++++-----
 NetworkPkg/HttpBootDxe/HttpBootImpl.h      |   8 +
 NetworkPkg/HttpBootDxe/HttpBootSupport.c   |  24 +-
 NetworkPkg/HttpBootDxe/HttpBootSupport.h   |   8 +-
 NetworkPkg/HttpDxe/HttpDriver.h            |   2 +
 NetworkPkg/HttpDxe/HttpDxe.inf             |   1 +
 NetworkPkg/HttpDxe/HttpImpl.c              | 139 +++++++++--
 NetworkPkg/HttpDxe/HttpProto.c             |  41 ++--
 NetworkPkg/HttpDxe/HttpProto.h             |  14 +-
 NetworkPkg/HttpDxe/HttpsSupport.c          |  14 +-
 NetworkPkg/Library/DxeHttpLib/DxeHttpLib.c |   5 +
 14 files changed, 642 insertions(+), 108 deletions(-)

diff --git a/NetworkPkg/HttpBootDxe/HttpBootClient.c b/NetworkPkg/HttpBootDxe/HttpBootClient.c
index 40f64fcb6b..bfad4809de 100644
--- a/NetworkPkg/HttpBootDxe/HttpBootClient.c
+++ b/NetworkPkg/HttpBootDxe/HttpBootClient.c
@@ -678,6 +678,10 @@ HttpBootFreeCache (
         FreePool (Cache->RequestData->Url);
       }
 
+      if (Cache->RequestData->EndPointUrl != NULL) {
+        FreePool (Cache->RequestData->EndPointUrl);
+      }
+
       FreePool (Cache->RequestData);
     }
 
@@ -901,6 +905,189 @@ HttpBootGetBootFileCallback (
   return EFI_SUCCESS;
 }
 
+/**
+  This function establishes a connection through a proxy server
+
+  @param[in]       Private         The pointer to the driver's private data.
+
+  @retval EFI_SUCCESS              Connection successful.
+  @retval EFI_OUT_OF_RESOURCES     Could not allocate needed resources
+  @retval Others                   Unexpected error happened.
+
+**/
+EFI_STATUS
+HttpBootConnectProxy (
+  IN     HTTP_BOOT_PRIVATE_DATA  *Private
+  )
+{
+  EFI_STATUS             Status;
+  EFI_HTTP_STATUS_CODE   StatusCode;
+  CHAR8                  *HostName;
+  EFI_HTTP_REQUEST_DATA  *RequestData;
+  HTTP_IO_RESPONSE_DATA  *ResponseData;
+  HTTP_IO                *HttpIo;
+  HTTP_IO_HEADER         *HttpIoHeader;
+  CHAR16                 *Url;
+  CHAR16                 *EndPointUrl;
+  UINTN                  UrlSize;
+  VOID                   *UrlParser;
+
+  Url          = NULL;
+  EndPointUrl  = NULL;
+  RequestData  = NULL;
+  ResponseData = NULL;
+  HttpIoHeader = NULL;
+
+  UrlSize = AsciiStrSize (Private->BootFileUri);
+  Url     = AllocatePool (UrlSize * sizeof (CHAR16));
+  if (Url == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  AsciiStrToUnicodeStrS (Private->BootFileUri, Url, UrlSize);
+
+  UrlSize     = AsciiStrSize (Private->EndPointUri);
+  EndPointUrl = AllocatePool (UrlSize * (sizeof (CHAR16)));
+  if (EndPointUrl == NULL) {
+    Status = EFI_OUT_OF_RESOURCES;
+    goto ERROR;
+  }
+
+  AsciiStrToUnicodeStrS (Private->EndPointUri, EndPointUrl, UrlSize);
+
+  //
+  // Send HTTP request message.
+  //
+
+  //
+  // Build HTTP header for the request, 2 headers are needed to send a CONNECT method:
+  //   Host
+  //   User
+  //
+  HttpIoHeader = HttpIoCreateHeader (2);
+  if (HttpIoHeader == NULL) {
+    Status = EFI_OUT_OF_RESOURCES;
+    goto ERROR;
+  }
+
+  //
+  // Add HTTP header field 1: Host (EndPoint URI)
+  //
+  Status = HttpParseUrl (Private->EndPointUri, (UINT32)AsciiStrLen (Private->EndPointUri), FALSE, &UrlParser);
+  if (EFI_ERROR (Status)) {
+    goto ERROR;
+  }
+
+  Status = HttpUrlGetHostName (
+             Private->EndPointUri,
+             UrlParser,
+             &HostName
+             );
+  if (EFI_ERROR (Status)) {
+    goto ERROR;
+  }
+
+  Status = HttpIoSetHeader (
+             HttpIoHeader,
+             HTTP_HEADER_HOST,
+             HostName
+             );
+  if (EFI_ERROR (Status)) {
+    goto ERROR;
+  }
+
+  //
+  // Add HTTP header field 2: User-Agent
+  //
+  Status = HttpIoSetHeader (
+             HttpIoHeader,
+             HTTP_HEADER_USER_AGENT,
+             HTTP_USER_AGENT_EFI_HTTP_BOOT
+             );
+  if (EFI_ERROR (Status)) {
+    goto ERROR;
+  }
+
+  //
+  // Build the rest of HTTP request info.
+  //
+  RequestData = AllocatePool (sizeof (EFI_HTTP_REQUEST_DATA));
+  if (RequestData == NULL) {
+    Status = EFI_OUT_OF_RESOURCES;
+    goto ERROR;
+  }
+
+  RequestData->Method      = HttpMethodConnect;
+  RequestData->Url         = Url;
+  RequestData->EndPointUrl = EndPointUrl;
+
+  //
+  // Send out the request to HTTP server.
+  //
+  HttpIo = &Private->HttpIo;
+  Status = HttpIoSendRequest (
+             HttpIo,
+             RequestData,
+             HttpIoHeader->HeaderCount,
+             HttpIoHeader->Headers,
+             0,
+             NULL
+             );
+  if (EFI_ERROR (Status)) {
+    goto ERROR;
+  }
+
+  //
+  // Receive HTTP response message.
+  //
+
+  //
+  // Use zero BodyLength to only receive the response headers.
+  //
+  ResponseData = AllocateZeroPool (sizeof (HTTP_IO_RESPONSE_DATA));
+  if (ResponseData == NULL) {
+    Status = EFI_OUT_OF_RESOURCES;
+    goto ERROR;
+  }
+
+  Status = HttpIoRecvResponse (
+             &Private->HttpIo,
+             TRUE,
+             ResponseData
+             );
+
+  if (EFI_ERROR (Status) || EFI_ERROR (ResponseData->Status)) {
+    if (EFI_ERROR (ResponseData->Status)) {
+      StatusCode = HttpIo->RspToken.Message->Data.Response->StatusCode;
+      HttpBootPrintErrorMessage (StatusCode);
+      Status = ResponseData->Status;
+    }
+
+    goto ERROR;
+  }
+
+ERROR:
+  if (ResponseData != NULL) {
+    FreePool (ResponseData);
+  }
+
+  if (RequestData != NULL) {
+    FreePool (RequestData);
+  }
+
+  HttpIoFreeHeader (HttpIoHeader);
+
+  if (EndPointUrl != NULL) {
+    FreePool (EndPointUrl);
+  }
+
+  if (Url != NULL) {
+    FreePool (Url);
+  }
+
+  return Status;
+}
+
 /**
   This function download the boot file by using UEFI HTTP protocol.
 
@@ -950,6 +1137,7 @@ HttpBootGetBootFile (
   UINT8                    *Block;
   UINTN                    UrlSize;
   CHAR16                   *Url;
+  CHAR16                   *EndPointUrl;
   BOOLEAN                  IdentityMode;
   UINTN                    ReceivedSize;
   CHAR8                    BaseAuthValue[80];
@@ -977,6 +1165,20 @@ HttpBootGetBootFile (
   }
 
   AsciiStrToUnicodeStrS (Private->BootFileUri, Url, UrlSize);
+
+  if (Private->EndPointUri != NULL) {
+    UrlSize     = AsciiStrSize (Private->EndPointUri);
+    EndPointUrl = AllocatePool (UrlSize * (sizeof (CHAR16)));
+    if (EndPointUrl == NULL) {
+      Status = EFI_OUT_OF_RESOURCES;
+      goto ERROR_1;
+    }
+
+    AsciiStrToUnicodeStrS (Private->EndPointUri, EndPointUrl, UrlSize);
+  } else {
+    EndPointUrl = NULL;
+  }
+
   if (!HeaderOnly && (Buffer != NULL)) {
     Status = HttpBootGetFileFromCache (Private, Url, BufferSize, Buffer, ImageType);
     if (Status != EFI_NOT_FOUND) {
@@ -1106,8 +1308,9 @@ HttpBootGetBootFile (
     goto ERROR_3;
   }
 
-  RequestData->Method = HeaderOnly ? HttpMethodHead : HttpMethodGet;
-  RequestData->Url    = Url;
+  RequestData->Method      = HeaderOnly ? HttpMethodHead : HttpMethodGet;
+  RequestData->Url         = Url;
+  RequestData->EndPointUrl = EndPointUrl;
 
   //
   // 2.3 Record the request info in a temp cache item.
@@ -1441,6 +1644,10 @@ ERROR_2:
   }
 
 ERROR_1:
+  if (EndPointUrl != NULL) {
+    FreePool (EndPointUrl);
+  }
+
   if (Url != NULL) {
     FreePool (Url);
   }
diff --git a/NetworkPkg/HttpBootDxe/HttpBootClient.h b/NetworkPkg/HttpBootDxe/HttpBootClient.h
index 2fba713679..fcd624f536 100644
--- a/NetworkPkg/HttpBootDxe/HttpBootClient.h
+++ b/NetworkPkg/HttpBootDxe/HttpBootClient.h
@@ -86,6 +86,21 @@ HttpBootCreateHttpIo (
   IN     HTTP_BOOT_PRIVATE_DATA  *Private
   );
 
+/**
+  This function establishes a connection through a proxy server
+
+  @param[in]       Private         The pointer to the driver's private data.
+
+  @retval EFI_SUCCESS              Connection successful.
+  @retval EFI_OUT_OF_RESOURCES     Could not allocate needed resources
+  @retval Others                   Unexpected error happened.
+
+**/
+EFI_STATUS
+HttpBootConnectProxy (
+  IN     HTTP_BOOT_PRIVATE_DATA  *Private
+  );
+
 /**
   This function download the boot file by using UEFI HTTP protocol.
 
diff --git a/NetworkPkg/HttpBootDxe/HttpBootDxe.h b/NetworkPkg/HttpBootDxe/HttpBootDxe.h
index 5ff8ad4698..e2eb1ffc45 100644
--- a/NetworkPkg/HttpBootDxe/HttpBootDxe.h
+++ b/NetworkPkg/HttpBootDxe/HttpBootDxe.h
@@ -223,6 +223,12 @@ struct _HTTP_BOOT_PRIVATE_DATA {
   CHAR8                                        *FilePathUri;
   VOID                                         *FilePathUriParser;
 
+  //
+  // URI string for the endpoint host if BootFileUri contains a proxy
+  // server in the path
+  //
+  CHAR8                                        *EndPointUri;
+
   //
   // Cached HTTP data
   //
diff --git a/NetworkPkg/HttpBootDxe/HttpBootImpl.c b/NetworkPkg/HttpBootDxe/HttpBootImpl.c
index b4c61925b9..6fafd800bd 100644
--- a/NetworkPkg/HttpBootDxe/HttpBootImpl.c
+++ b/NetworkPkg/HttpBootDxe/HttpBootImpl.c
@@ -116,8 +116,14 @@ HttpBootStart (
   UINTN       Index;
   EFI_STATUS  Status;
   CHAR8       *Uri;
+  CHAR8       *EndPointUri;
+  CHAR8       *BootFilePath;
+  UINTN       FilePathUriLen;
+  UINTN       EndPointUriLen;
 
-  Uri = NULL;
+  Uri          = NULL;
+  EndPointUri  = NULL;
+  BootFilePath = NULL;
 
   if ((Private == NULL) || (FilePath == NULL)) {
     return EFI_INVALID_PARAMETER;
@@ -127,7 +133,7 @@ HttpBootStart (
   // Check the URI in the input FilePath, in order to see whether it is
   // required to boot from a new specified boot file.
   //
-  Status = HttpBootParseFilePath (FilePath, &Uri);
+  Status = HttpBootParseFilePath (FilePath, &Uri, &EndPointUri);
   if (EFI_ERROR (Status)) {
     return EFI_INVALID_PARAMETER;
   }
@@ -154,6 +160,10 @@ HttpBootStart (
           FreePool (Uri);
         }
 
+        if (EndPointUri != NULL) {
+          FreePool (EndPointUri);
+        }
+
         return Status;
       }
     } else {
@@ -164,6 +174,10 @@ HttpBootStart (
         FreePool (Uri);
       }
 
+      if (EndPointUri != NULL) {
+        FreePool (EndPointUri);
+      }
+
       return EFI_ALREADY_STARTED;
     }
   }
@@ -180,13 +194,63 @@ HttpBootStart (
       FreePool (Uri);
     }
 
+    if (EndPointUri != NULL) {
+      FreePool (EndPointUri);
+    }
+
     return EFI_UNSUPPORTED;
   }
 
   //
   // Record the specified URI and prepare the URI parser if needed.
   //
-  Private->FilePathUri = Uri;
+  Private->EndPointUri = EndPointUri;
+  if (Private->EndPointUri != NULL) {
+    //
+    // When a Proxy Server URI is included in the device path, the file path
+    // is a part of the EndPoint URI.
+    // Move the file path from EndPointUri to FilePathUri
+    //
+    Status = HttpParseUrl (
+               Private->EndPointUri,
+               (UINT32)AsciiStrLen (Private->EndPointUri),
+               FALSE,
+               &Private->FilePathUriParser
+               );
+    if (EFI_ERROR (Status)) {
+      return Status;
+    }
+
+    Status = HttpUrlGetPath (
+               Private->EndPointUri,
+               Private->FilePathUriParser,
+               &BootFilePath
+               );
+    if (EFI_ERROR (Status)) {
+      return Status;
+    }
+
+    //
+    // Remove the file path from EndPointUri
+    //
+    EndPointUriLen       = AsciiStrLen (EndPointUri) - AsciiStrLen (BootFilePath) + 1;
+    Private->EndPointUri = AllocateZeroPool (EndPointUriLen);
+    AsciiSPrint (Private->EndPointUri, EndPointUriLen, "%a", EndPointUri);
+
+    //
+    // Add the file path to FilePathUri
+    //
+    FilePathUriLen       = AsciiStrLen (Uri) + AsciiStrLen (BootFilePath) + 1;
+    Private->FilePathUri = AllocateZeroPool (FilePathUriLen);
+    AsciiSPrint (Private->FilePathUri, FilePathUriLen, "%a%a", Uri, BootFilePath);
+
+    FreePool (BootFilePath);
+    FreePool (Private->FilePathUriParser);
+    FreePool (EndPointUri);
+  } else {
+    Private->FilePathUri = Uri;
+  }
+
   if (Private->FilePathUri != NULL) {
     Status = HttpParseUrl (
                Private->FilePathUri,
@@ -274,6 +338,136 @@ HttpBootDhcp (
   return Status;
 }
 
+/**
+  Issue calls to HttpBootGetBootFile() based on current Boot File State
+
+  @param[in]          Private         The pointer to the driver's private data.
+  @param[in, out]     BufferSize      On input the size of Buffer in bytes. On output with a return
+                                      code of EFI_SUCCESS, the amount of data transferred to
+                                      Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
+                                      the size of Buffer required to retrieve the requested file.
+  @param[in]          Buffer          The memory buffer to transfer the file to. If Buffer is NULL,
+                                      then the size of the requested file is returned in
+                                      BufferSize.
+  @param[out]         ImageType       The image type of the downloaded file.
+
+  @retval EFI_SUCCESS              The file was loaded.
+  @retval EFI_INVALID_PARAMETER    BufferSize is NULL or Buffer Size is not NULL but Buffer is NULL.
+  @retval EFI_OUT_OF_RESOURCES     Could not allocate needed resources
+  @retval EFI_BUFFER_TOO_SMALL     The BufferSize is too small to read the current directory entry.
+                                   BufferSize has been updated with the size needed to complete
+                                   the request.
+  @retval EFI_ACCESS_DENIED        Server authentication failed.
+  @retval Others                   Unexpected error happened.
+
+**/
+EFI_STATUS
+HttpBootGetBootFileCaller (
+  IN     HTTP_BOOT_PRIVATE_DATA  *Private,
+  IN OUT UINTN                   *BufferSize,
+  IN     VOID                    *Buffer        OPTIONAL,
+  OUT HTTP_BOOT_IMAGE_TYPE       *ImageType
+  )
+{
+  HTTP_GET_BOOT_FILE_STATE  State;
+  EFI_STATUS                Status;
+
+  if (Private->BootFileSize == 0) {
+    if (Private->EndPointUri != NULL) {
+      State = ConnectToProxy;
+    } else {
+      State = GetBootFileHead;
+    }
+  } else {
+    State = LoadBootFile;
+  }
+
+  for ( ; ;) {
+    switch (State) {
+      case GetBootFileHead:
+        //
+        // Try to use HTTP HEAD method.
+        //
+        Status = HttpBootGetBootFile (
+                   Private,
+                   TRUE,
+                   &Private->BootFileSize,
+                   NULL,
+                   &Private->ImageType
+                   );
+        if ((EFI_ERROR (Status)) && (Status != EFI_BUFFER_TOO_SMALL)) {
+          if ((Private->AuthData != NULL) && (Status == EFI_ACCESS_DENIED)) {
+            //
+            // Try to use HTTP HEAD method again since the Authentication information is provided.
+            //
+            State = GetBootFileHead;
+          } else {
+            State = GetBootFileGet;
+          }
+        } else {
+          State = LoadBootFile;
+        }
+
+        break;
+
+      case GetBootFileGet:
+        //
+        // Failed to get file size by HEAD method, may be trunked encoding, try HTTP GET method.
+        //
+        ASSERT (Private->BootFileSize == 0);
+        Status = HttpBootGetBootFile (
+                   Private,
+                   FALSE,
+                   &Private->BootFileSize,
+                   NULL,
+                   &Private->ImageType
+                   );
+        if (EFI_ERROR (Status) && (Status != EFI_BUFFER_TOO_SMALL)) {
+          State = GetBootFileError;
+        } else {
+          State = LoadBootFile;
+        }
+
+        break;
+
+      case ConnectToProxy:
+        Status = HttpBootConnectProxy (Private);
+        if (Status == EFI_SUCCESS) {
+          State = GetBootFileHead;
+        } else {
+          State = GetBootFileError;
+        }
+
+        break;
+
+      case LoadBootFile:
+        if (*BufferSize < Private->BootFileSize) {
+          *BufferSize = Private->BootFileSize;
+          *ImageType  = Private->ImageType;
+          Status      = EFI_BUFFER_TOO_SMALL;
+          return Status;
+        }
+
+        //
+        // Load the boot file into Buffer
+        //
+        Status = HttpBootGetBootFile (
+                   Private,
+                   FALSE,
+                   BufferSize,
+                   Buffer,
+                   ImageType
+                   );
+        return Status;
+
+      case GetBootFileError:
+      default:
+        AsciiPrint ("\n  Error: Could not retrieve NBP file size from HTTP server.\n");
+        return Status;
+    }
+  }
+}
+
 /**
   Attempt to download the boot file through HTTP message exchange.
 
@@ -345,68 +539,10 @@ HttpBootLoadFile (
     }
   }
 
-  if (Private->BootFileSize == 0) {
-    //
-    // Discover the information about the bootfile if we haven't.
-    //
-
-    //
-    // Try to use HTTP HEAD method.
-    //
-    Status = HttpBootGetBootFile (
-               Private,
-               TRUE,
-               &Private->BootFileSize,
-               NULL,
-               &Private->ImageType
-               );
-    if ((Private->AuthData != NULL) && (Status == EFI_ACCESS_DENIED)) {
-      //
-      // Try to use HTTP HEAD method again since the Authentication information is provided.
-      //
-      Status = HttpBootGetBootFile (
-                 Private,
-                 TRUE,
-                 &Private->BootFileSize,
-                 NULL,
-                 &Private->ImageType
-                 );
-    } else if ((EFI_ERROR (Status)) && (Status != EFI_BUFFER_TOO_SMALL)) {
-      //
-      // Failed to get file size by HEAD method, may be trunked encoding, try HTTP GET method.
-      //
-      ASSERT (Private->BootFileSize == 0);
-      Status = HttpBootGetBootFile (
-                 Private,
-                 FALSE,
-                 &Private->BootFileSize,
-                 NULL,
-                 &Private->ImageType
-                 );
-      if (EFI_ERROR (Status) && (Status != EFI_BUFFER_TOO_SMALL)) {
-        AsciiPrint ("\n  Error: Could not retrieve NBP file size from HTTP server.\n");
-        goto ON_EXIT;
-      }
-    }
-  }
-
-  if (*BufferSize < Private->BootFileSize) {
-    *BufferSize = Private->BootFileSize;
-    *ImageType  = Private->ImageType;
-    Status      = EFI_BUFFER_TOO_SMALL;
-    goto ON_EXIT;
-  }
-
   //
-  // Load the boot file into Buffer
+  // Load the Boot File
   //
-  Status = HttpBootGetBootFile (
-             Private,
-             FALSE,
-             BufferSize,
-             Buffer,
-             ImageType
-             );
+  Status = HttpBootGetBootFileCaller (Private, BufferSize, Buffer, ImageType);
 
 ON_EXIT:
   HttpBootUninstallCallback (Private);
diff --git a/NetworkPkg/HttpBootDxe/HttpBootImpl.h b/NetworkPkg/HttpBootDxe/HttpBootImpl.h
index 55adc9cb50..e4ffc3ed48 100644
--- a/NetworkPkg/HttpBootDxe/HttpBootImpl.h
+++ b/NetworkPkg/HttpBootDxe/HttpBootImpl.h
@@ -11,6 +11,14 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
 
 #define HTTP_BOOT_CHECK_MEDIA_WAITING_TIME  EFI_TIMER_PERIOD_SECONDS(20)
 
+typedef enum {
+  GetBootFileHead,
+  GetBootFileGet,
+  ConnectToProxy,
+  LoadBootFile,
+  GetBootFileError
+} HTTP_GET_BOOT_FILE_STATE;
+
 /**
   Attempt to complete a DHCPv4 D.O.R.A or DHCPv6 S.R.A.A sequence to retrieve the boot resource information.
 
diff --git a/NetworkPkg/HttpBootDxe/HttpBootSupport.c b/NetworkPkg/HttpBootDxe/HttpBootSupport.c
index 236ef25931..c857488036 100644
--- a/NetworkPkg/HttpBootDxe/HttpBootSupport.c
+++ b/NetworkPkg/HttpBootDxe/HttpBootSupport.c
@@ -558,6 +558,7 @@ HttpBootCheckUriScheme (
 
   @param[in]   FilePath         Pointer to the device path which contains a URI device path node.
   @param[out]  UriAddress       The URI address string extract from the device path.
+  @param[out]  EndPointUriAddress The URI address string for the endpoint host if UriAddress contains the address of a proxy server
 
   @retval EFI_SUCCESS            The URI string is returned.
   @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory.
@@ -566,19 +567,24 @@ HttpBootCheckUriScheme (
 EFI_STATUS
 HttpBootParseFilePath (
   IN     EFI_DEVICE_PATH_PROTOCOL  *FilePath,
-  OUT CHAR8                        **UriAddress
+  OUT CHAR8                        **UriAddress,
+  OUT CHAR8                        **EndPointUriAddress
   )
 {
   EFI_DEVICE_PATH_PROTOCOL  *TempDevicePath;
   URI_DEVICE_PATH           *UriDevicePath;
   CHAR8                     *Uri;
+  CHAR8                     *TempUri;
   UINTN                     UriStrLength;
 
   if (FilePath == NULL) {
     return EFI_INVALID_PARAMETER;
   }
 
-  *UriAddress = NULL;
+  Uri                 = NULL;
+  *UriAddress         = NULL;
+  *EndPointUriAddress = NULL;
+  TempUri             = NULL;
 
   //
   // Extract the URI address from the FilePath
@@ -601,6 +607,15 @@ HttpBootParseFilePath (
         break;
       }
 
+      if (Uri != NULL) {
+        //
+        // Device Path with Proxy Server will be described as
+        //   ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv4(...)[/Dns(...)]/Uri(ProxyServer)/Uri(EndPointServer/FilePath)
+        //   ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv6(...)[/Dns(...)]/Uri(ProxyServer)/Uri(EndPointServer/FilePath)
+        //
+        TempUri = Uri;
+      }
+
       Uri = AllocatePool (UriStrLength + 1);
       if (Uri == NULL) {
         return EFI_OUT_OF_RESOURCES;
@@ -615,6 +630,11 @@ HttpBootParseFilePath (
     TempDevicePath = NextDevicePathNode (TempDevicePath);
   }
 
+  if (TempUri != NULL) {
+    *UriAddress         = TempUri;
+    *EndPointUriAddress = Uri;
+  }
+
   return EFI_SUCCESS;
 }
 
diff --git a/NetworkPkg/HttpBootDxe/HttpBootSupport.h b/NetworkPkg/HttpBootDxe/HttpBootSupport.h
index 3698e55936..6228f37e36 100644
--- a/NetworkPkg/HttpBootDxe/HttpBootSupport.h
+++ b/NetworkPkg/HttpBootDxe/HttpBootSupport.h
@@ -138,8 +138,9 @@ HttpBootCheckUriScheme (
 
   Caller need to free the buffer in the UriAddress pointer.
 
-  @param[in]   FilePath         Pointer to the device path which contains a URI device path node.
-  @param[out]  UriAddress       The URI address string extract from the device path.
+  @param[in]   FilePath           Pointer to the device path which contains a URI device path node.
+  @param[out]  UriAddress         The URI address string extract from the device path.
+  @param[out]  EndPointUriAddress The URI address string for the endpoint host if UriAddress contains the address of a proxy server
 
   @retval EFI_SUCCESS            The URI string is returned.
   @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory.
@@ -148,7 +149,8 @@ HttpBootCheckUriScheme (
 EFI_STATUS
 HttpBootParseFilePath (
   IN     EFI_DEVICE_PATH_PROTOCOL  *FilePath,
-  OUT CHAR8                        **UriAddress
+  OUT CHAR8                        **UriAddress,
+  OUT CHAR8                        **EndPointUriAddress
   );
 
 /**
diff --git a/NetworkPkg/HttpDxe/HttpDriver.h b/NetworkPkg/HttpDxe/HttpDriver.h
index 01a6bb7f4b..e0917f431e 100644
--- a/NetworkPkg/HttpDxe/HttpDriver.h
+++ b/NetworkPkg/HttpDxe/HttpDriver.h
@@ -26,6 +26,7 @@
 #include <Library/NetLib.h>
 #include <Library/HttpLib.h>
 #include <Library/DpcLib.h>
+#include <Library/PrintLib.h>
 
 //
 // UEFI Driver Model Protocols
@@ -64,6 +65,7 @@
 // Driver Version
 //
 #define HTTP_DRIVER_VERSION  0xa
+#define URI_STR_MAX_SIZE     255
 
 //
 // Protocol instances
diff --git a/NetworkPkg/HttpDxe/HttpDxe.inf b/NetworkPkg/HttpDxe/HttpDxe.inf
index c9502d0bb6..30b7de1951 100644
--- a/NetworkPkg/HttpDxe/HttpDxe.inf
+++ b/NetworkPkg/HttpDxe/HttpDxe.inf
@@ -47,6 +47,7 @@
   NetLib
   HttpLib
   DpcLib
+  PrintLib
 
 [Protocols]
   gEfiHttpServiceBindingProtocolGuid               ## BY_START
diff --git a/NetworkPkg/HttpDxe/HttpImpl.c b/NetworkPkg/HttpDxe/HttpImpl.c
index 7c5c925cf7..24ce87fd7d 100644
--- a/NetworkPkg/HttpDxe/HttpImpl.c
+++ b/NetworkPkg/HttpDxe/HttpImpl.c
@@ -233,35 +233,45 @@ EfiHttpRequest (
   EFI_HTTP_MESSAGE       *HttpMsg;
   EFI_HTTP_REQUEST_DATA  *Request;
   VOID                   *UrlParser;
+  VOID                   *EndPointUrlParser;
   EFI_STATUS             Status;
   CHAR8                  *HostName;
+  CHAR8                  *EndPointHostName;
   UINTN                  HostNameSize;
   UINT16                 RemotePort;
+  UINT16                 EndPointRemotePort;
   HTTP_PROTOCOL          *HttpInstance;
   BOOLEAN                Configure;
   BOOLEAN                ReConfigure;
   BOOLEAN                TlsConfigure;
   CHAR8                  *RequestMsg;
   CHAR8                  *Url;
+  CHAR8                  *EndPointUrl;
   UINTN                  UrlLen;
   CHAR16                 *HostNameStr;
   HTTP_TOKEN_WRAP        *Wrap;
   CHAR8                  *FileUrl;
   UINTN                  RequestMsgSize;
   EFI_HANDLE             ImageHandle;
+  CHAR8                  *EndPointUrlMsg;
 
   //
   // Initializations
   //
-  Url          = NULL;
-  UrlParser    = NULL;
-  RemotePort   = 0;
-  HostName     = NULL;
-  RequestMsg   = NULL;
-  HostNameStr  = NULL;
-  Wrap         = NULL;
-  FileUrl      = NULL;
-  TlsConfigure = FALSE;
+  Url                = NULL;
+  UrlParser          = NULL;
+  EndPointUrlParser  = NULL;
+  RemotePort         = 0;
+  EndPointRemotePort = 0;
+  HostName           = NULL;
+  EndPointHostName   = NULL;
+  RequestMsg         = NULL;
+  HostNameStr        = NULL;
+  Wrap               = NULL;
+  FileUrl            = NULL;
+  TlsConfigure       = FALSE;
+  EndPointUrl        = NULL;
+  EndPointUrlMsg     = NULL;
 
   if ((This == NULL) || (Token == NULL)) {
     return EFI_INVALID_PARAMETER;
@@ -275,16 +285,20 @@ EfiHttpRequest (
   Request = HttpMsg->Data.Request;
 
   //
-  // Only support GET, HEAD, DELETE, PATCH, PUT and POST method in current implementation.
+  // Only support GET, HEAD, DELETE, PATCH, PUT, CONNECT and POST method in current implementation.
   //
   if ((Request != NULL) && (Request->Method != HttpMethodGet) &&
       (Request->Method != HttpMethodHead) && (Request->Method != HttpMethodDelete) &&
       (Request->Method != HttpMethodPut) && (Request->Method != HttpMethodPost) &&
-      (Request->Method != HttpMethodPatch))
+      (Request->Method != HttpMethodPatch) && (Request->Method != HttpMethodConnect))
   {
     return EFI_UNSUPPORTED;
   }
 
+  if ((Request->Method == HttpMethodConnect) && (Request->EndPointUrl == NULL)) {
+    return EFI_INVALID_PARAMETER;
+  }
+
   HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This);
 
   //
@@ -353,11 +367,25 @@ EfiHttpRequest (
 
     UnicodeStrToAsciiStrS (Request->Url, Url, UrlLen);
 
+    if (Request->EndPointUrl != NULL) {
+      UrlLen      = StrLen (Request->EndPointUrl) + 1;
+      EndPointUrl = AllocateZeroPool (UrlLen);
+      if (EndPointUrl == NULL) {
+        goto Error1;
+      }
+
+      UnicodeStrToAsciiStrS (Request->EndPointUrl, EndPointUrl, UrlLen);
+    }
+
     //
     // From the information in Url, the HTTP instance will
     // be able to determine whether to use http or https.
     //
-    HttpInstance->UseHttps = IsHttpsUrl (Url);
+    if (HttpInstance->ProxyConnected) {
+      HttpInstance->UseHttps = IsHttpsUrl (EndPointUrl);
+    } else {
+      HttpInstance->UseHttps = IsHttpsUrl (Url);
+    }
 
     //
     // HTTP is disabled, return directly if the URI is not HTTPS.
@@ -444,9 +472,10 @@ EfiHttpRequest (
       if ((HttpInstance->ConnectionClose == FALSE) &&
           (HttpInstance->RemotePort == RemotePort) &&
           (AsciiStrCmp (HttpInstance->RemoteHost, HostName) == 0) &&
-          (!HttpInstance->UseHttps || (HttpInstance->UseHttps &&
-                                       !TlsConfigure &&
-                                       (HttpInstance->TlsSessionState == EfiTlsSessionDataTransferring))))
+          (!HttpInstance->UseHttps ||
+           HttpInstance->ProxyConnected || (HttpInstance->UseHttps &&
+                                            !TlsConfigure &&
+                                            (HttpInstance->TlsSessionState == EfiTlsSessionDataTransferring))))
       {
         //
         // Host Name and port number of the request URL are the same with previous call to Request().
@@ -599,7 +628,7 @@ EfiHttpRequest (
     goto Error2;
   }
 
-  if (!Configure && !ReConfigure && !TlsConfigure) {
+  if ((!Configure && !ReConfigure) && ((HttpInstance->ProxyConnected && TlsConfigure) || (!TlsConfigure))) {
     //
     // For the new HTTP token, create TX TCP token events.
     //
@@ -632,7 +661,48 @@ EfiHttpRequest (
     }
   }
 
-  Status = HttpGenRequestMessage (HttpMsg, FileUrl, &RequestMsg, &RequestMsgSize);
+  if (HttpInstance->Method == HttpMethodConnect) {
+    Status = HttpParseUrl (EndPointUrl, (UINT32)AsciiStrLen (EndPointUrl), FALSE, &EndPointUrlParser);
+    if (EFI_ERROR (Status)) {
+      goto Error3;
+    }
+
+    Status = HttpUrlGetHostName (
+               EndPointUrl,
+               EndPointUrlParser,
+               &EndPointHostName
+               );
+    if (EFI_ERROR (Status)) {
+      goto Error3;
+    }
+
+    Status = HttpUrlGetPort (EndPointUrl, EndPointUrlParser, &EndPointRemotePort);
+    if (EFI_ERROR (Status)) {
+      if (IsHttpsUrl (EndPointUrl)) {
+        EndPointRemotePort = HTTPS_DEFAULT_PORT;
+      } else {
+        EndPointRemotePort = HTTP_DEFAULT_PORT;
+      }
+    }
+
+    EndPointUrlMsg = AllocateZeroPool (URI_STR_MAX_SIZE);
+    if (EndPointUrlMsg == NULL) {
+      Status = EFI_OUT_OF_RESOURCES;
+      goto Error3;
+    }
+
+    AsciiSPrint (
+      EndPointUrlMsg,
+      URI_STR_MAX_SIZE,
+      "%a:%d",
+      EndPointHostName,
+      EndPointRemotePort
+      );
+
+    Status = HttpGenRequestMessage (HttpMsg, EndPointUrlMsg, &RequestMsg, &RequestMsgSize);
+  } else {
+    Status = HttpGenRequestMessage (HttpMsg, FileUrl, &RequestMsg, &RequestMsgSize);
+  }
 
   if (EFI_ERROR (Status) || (NULL == RequestMsg)) {
     goto Error3;
@@ -668,6 +738,23 @@ EfiHttpRequest (
 
   DispatchDpc ();
 
+  if (HttpInstance->Method == HttpMethodConnect) {
+    HttpInstance->ProxyConnected     = TRUE;
+    HttpInstance->EndPointRemoteHost = EndPointHostName;
+
+    if (EndPointUrlParser != NULL) {
+      HttpUrlFreeParser (EndPointUrlParser);
+    }
+  }
+
+  if (EndPointUrlMsg != NULL) {
+    FreePool (EndPointUrlMsg);
+  }
+
+  if (EndPointUrl != NULL) {
+    FreePool (EndPointUrl);
+  }
+
   if (HostName != NULL) {
     FreePool (HostName);
   }
@@ -698,6 +785,20 @@ Error3:
     TlsCloseTxRxEvent (HttpInstance);
   }
 
+  if (HttpInstance->Method == HttpMethodConnect) {
+    if (EndPointHostName != NULL) {
+      FreePool (EndPointHostName);
+    }
+
+    if (EndPointUrlParser != NULL) {
+      HttpUrlFreeParser (EndPointUrlParser);
+    }
+  }
+
+  if (EndPointUrlMsg != NULL) {
+    FreePool (EndPointUrlMsg);
+  }
+
 Error2:
   HttpCloseConnection (HttpInstance);
 
@@ -725,6 +826,10 @@ Error1:
     HttpUrlFreeParser (UrlParser);
   }
 
+  if (EndPointUrl != NULL) {
+    FreePool (EndPointUrl);
+  }
+
   return Status;
 }
 
diff --git a/NetworkPkg/HttpDxe/HttpProto.c b/NetworkPkg/HttpDxe/HttpProto.c
index 33ae622c3f..b87fbeeb54 100644
--- a/NetworkPkg/HttpDxe/HttpProto.c
+++ b/NetworkPkg/HttpDxe/HttpProto.c
@@ -849,6 +849,11 @@ HttpCleanProtocol (
     HttpInstance->Url = NULL;
   }
 
+  if (HttpInstance->EndPointRemoteHost != NULL) {
+    FreePool (HttpInstance->EndPointRemoteHost);
+    HttpInstance->EndPointRemoteHost = NULL;
+  }
+
   NetMapClean (&HttpInstance->TxTokens);
   NetMapClean (&HttpInstance->RxTokens);
 
@@ -1206,6 +1211,7 @@ HttpConfigureTcp6 (
   connect one TLS session if required.
 
   @param[in]  HttpInstance       The HTTP instance private data.
+  @param[in]  TlsConfigure       The Flag indicates whether it's the new Tls session.
 
   @retval EFI_SUCCESS            The TCP connection is established.
   @retval EFI_NOT_READY          TCP4 protocol child is not created or configured.
@@ -1214,7 +1220,8 @@ HttpConfigureTcp6 (
 **/
 EFI_STATUS
 HttpConnectTcp4 (
-  IN  HTTP_PROTOCOL  *HttpInstance
+  IN  HTTP_PROTOCOL  *HttpInstance,
+  IN  BOOLEAN        TlsConfigure
   )
 {
   EFI_STATUS                 Status;
@@ -1237,16 +1244,18 @@ HttpConnectTcp4 (
     return Status;
   }
 
-  if (Tcp4State == Tcp4StateEstablished) {
+  if ((Tcp4State == Tcp4StateEstablished) && (!HttpInstance->ProxyConnected || !TlsConfigure)) {
     return EFI_SUCCESS;
   } else if (Tcp4State > Tcp4StateEstablished ) {
     HttpCloseConnection (HttpInstance);
   }
 
-  Status = HttpCreateConnection (HttpInstance);
-  if (EFI_ERROR (Status)) {
-    DEBUG ((DEBUG_ERROR, "Tcp4 Connection fail - %x\n", Status));
-    return Status;
+  if (!HttpInstance->ProxyConnected) {
+    Status = HttpCreateConnection (HttpInstance);
+    if (EFI_ERROR (Status)) {
+      DEBUG ((DEBUG_ERROR, "Tcp4 Connection fail - %x\n", Status));
+      return Status;
+    }
   }
 
   //
@@ -1298,6 +1307,7 @@ HttpConnectTcp4 (
   connect one TLS session if required.
 
   @param[in]  HttpInstance       The HTTP instance private data.
+  @param[in]  TlsConfigure       The Flag indicates whether it's the new Tls session.
 
   @retval EFI_SUCCESS            The TCP connection is established.
   @retval EFI_NOT_READY          TCP6 protocol child is not created or configured.
@@ -1306,7 +1316,8 @@ HttpConnectTcp4 (
 **/
 EFI_STATUS
 HttpConnectTcp6 (
-  IN  HTTP_PROTOCOL  *HttpInstance
+  IN  HTTP_PROTOCOL  *HttpInstance,
+  IN  BOOLEAN        TlsConfigure
   )
 {
   EFI_STATUS                 Status;
@@ -1330,16 +1341,18 @@ HttpConnectTcp6 (
     return Status;
   }
 
-  if (Tcp6State == Tcp6StateEstablished) {
+  if ((Tcp6State == Tcp6StateEstablished) && (!HttpInstance->ProxyConnected || !TlsConfigure)) {
     return EFI_SUCCESS;
   } else if (Tcp6State > Tcp6StateEstablished ) {
     HttpCloseConnection (HttpInstance);
   }
 
-  Status = HttpCreateConnection (HttpInstance);
-  if (EFI_ERROR (Status)) {
-    DEBUG ((DEBUG_ERROR, "Tcp6 Connection fail - %x\n", Status));
-    return Status;
+  if (!HttpInstance->ProxyConnected) {
+    Status = HttpCreateConnection (HttpInstance);
+    if (EFI_ERROR (Status)) {
+      DEBUG ((DEBUG_ERROR, "Tcp6 Connection fail - %x\n", Status));
+      return Status;
+    }
   }
 
   //
@@ -1434,7 +1447,7 @@ HttpInitSession (
     //
     // Connect TCP.
     //
-    Status = HttpConnectTcp4 (HttpInstance);
+    Status = HttpConnectTcp4 (HttpInstance, TlsConfigure);
     if (EFI_ERROR (Status)) {
       return Status;
     }
@@ -1452,7 +1465,7 @@ HttpInitSession (
     //
     // Connect TCP.
     //
-    Status = HttpConnectTcp6 (HttpInstance);
+    Status = HttpConnectTcp6 (HttpInstance, TlsConfigure);
     if (EFI_ERROR (Status)) {
       return Status;
     }
diff --git a/NetworkPkg/HttpDxe/HttpProto.h b/NetworkPkg/HttpDxe/HttpProto.h
index 620eb39158..2e8d516359 100644
--- a/NetworkPkg/HttpDxe/HttpProto.h
+++ b/NetworkPkg/HttpDxe/HttpProto.h
@@ -165,6 +165,12 @@ typedef struct _HTTP_PROTOCOL {
 
   CHAR8                             *Url;
 
+  //
+  // Proxy Server Support
+  //
+  CHAR8                             *EndPointRemoteHost;
+  BOOLEAN                           ProxyConnected;
+
   //
   // Https Support
   //
@@ -398,6 +404,7 @@ HttpConfigureTcp6 (
   connect one TLS session if required.
 
   @param[in]  HttpInstance       The HTTP instance private data.
+  @param[in]  TlsConfigure       The Flag indicates whether it's the new Tls session.
 
   @retval EFI_SUCCESS            The TCP connection is established.
   @retval EFI_NOT_READY          TCP4 protocol child is not created or configured.
@@ -406,7 +413,8 @@ HttpConfigureTcp6 (
 **/
 EFI_STATUS
 HttpConnectTcp4 (
-  IN  HTTP_PROTOCOL  *HttpInstance
+  IN  HTTP_PROTOCOL  *HttpInstance,
+  IN  BOOLEAN        TlsConfigure
   );
 
 /**
@@ -414,6 +422,7 @@ HttpConnectTcp4 (
   connect one TLS session if required.
 
   @param[in]  HttpInstance       The HTTP instance private data.
+  @param[in]  TlsConfigure       The Flag indicates whether it's the new Tls session.
 
   @retval EFI_SUCCESS            The TCP connection is established.
   @retval EFI_NOT_READY          TCP6 protocol child is not created or configured.
@@ -422,7 +431,8 @@ HttpConnectTcp4 (
 **/
 EFI_STATUS
 HttpConnectTcp6 (
-  IN  HTTP_PROTOCOL  *HttpInstance
+  IN  HTTP_PROTOCOL  *HttpInstance,
+  IN  BOOLEAN        TlsConfigure
   );
 
 /**
diff --git a/NetworkPkg/HttpDxe/HttpsSupport.c b/NetworkPkg/HttpDxe/HttpsSupport.c
index ad611e7c38..7dc2b752ec 100644
--- a/NetworkPkg/HttpDxe/HttpsSupport.c
+++ b/NetworkPkg/HttpDxe/HttpsSupport.c
@@ -644,11 +644,15 @@ TlsConfigureSession (
   //
   // TlsConfigData initialization
   //
-  HttpInstance->TlsConfigData.ConnectionEnd       = EfiTlsClient;
-  HttpInstance->TlsConfigData.VerifyMethod        = EFI_TLS_VERIFY_PEER;
-  HttpInstance->TlsConfigData.VerifyHost.Flags    = EFI_TLS_VERIFY_FLAG_NONE;
-  HttpInstance->TlsConfigData.VerifyHost.HostName = HttpInstance->RemoteHost;
-  HttpInstance->TlsConfigData.SessionState        = EfiTlsSessionNotStarted;
+  HttpInstance->TlsConfigData.ConnectionEnd    = EfiTlsClient;
+  HttpInstance->TlsConfigData.SessionState     = EfiTlsSessionNotStarted;
+  HttpInstance->TlsConfigData.VerifyMethod     = EFI_TLS_VERIFY_PEER;
+  HttpInstance->TlsConfigData.VerifyHost.Flags = EFI_TLS_VERIFY_FLAG_NONE;
+  if (HttpInstance->ProxyConnected) {
+    HttpInstance->TlsConfigData.VerifyHost.HostName = HttpInstance->EndPointRemoteHost;
+  } else {
+    HttpInstance->TlsConfigData.VerifyHost.HostName = HttpInstance->RemoteHost;
+  }
 
   //
   // EfiTlsConnectionEnd,
diff --git a/NetworkPkg/Library/DxeHttpLib/DxeHttpLib.c b/NetworkPkg/Library/DxeHttpLib/DxeHttpLib.c
index 6a5d78629b..45087a1935 100644
--- a/NetworkPkg/Library/DxeHttpLib/DxeHttpLib.c
+++ b/NetworkPkg/Library/DxeHttpLib/DxeHttpLib.c
@@ -1927,6 +1927,11 @@ HttpGenRequestMessage (
         CopyMem (RequestPtr, HTTP_METHOD_DELETE, StrLength);
         RequestPtr += StrLength;
         break;
+      case HttpMethodConnect:
+        StrLength = sizeof (HTTP_METHOD_CONNECT) - 1;
+        CopyMem (RequestPtr, HTTP_METHOD_CONNECT, StrLength);
+        RequestPtr += StrLength;
+        break;
       default:
         ASSERT (FALSE);
         Status = EFI_INVALID_PARAMETER;
-- 
2.36.1.windows.1



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#94092): https://edk2.groups.io/g/devel/message/94092
Mute This Topic: https://groups.io/mt/93837213/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-