]>
Commit | Line | Data |
---|---|---|
c4545d76 FS |
1 | /** @file |
2 | Support functions implementation for UEFI HTTP boot driver. | |
3 | ||
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. | |
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 | /** | |
19 | Get the Nic handle using any child handle in the IPv4 stack. | |
20 | ||
21 | @param[in] ControllerHandle Pointer to child handle over IPv4. | |
22 | ||
23 | @return NicHandle The pointer to the Nic handle. | |
24 | @return NULL Can't find the Nic handle. | |
25 | ||
26 | **/ | |
27 | EFI_HANDLE | |
28 | HttpBootGetNicByIp4Children ( | |
29 | IN EFI_HANDLE ControllerHandle | |
30 | ) | |
31 | { | |
32 | EFI_HANDLE NicHandle; | |
33 | ||
34 | NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiHttpProtocolGuid); | |
35 | if (NicHandle == NULL) { | |
36 | NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiDhcp4ProtocolGuid); | |
37 | if (NicHandle == NULL) { | |
38 | return NULL; | |
39 | } | |
40 | } | |
41 | ||
42 | return NicHandle; | |
43 | } | |
44 | ||
45 | ||
46 | /** | |
47 | This function is to convert UINTN to ASCII string with the required formatting. | |
48 | ||
49 | @param[in] Number Numeric value to be converted. | |
50 | @param[in] Buffer The pointer to the buffer for ASCII string. | |
51 | @param[in] Length The length of the required format. | |
52 | ||
53 | **/ | |
54 | VOID | |
55 | HttpBootUintnToAscDecWithFormat ( | |
56 | IN UINTN Number, | |
57 | IN UINT8 *Buffer, | |
58 | IN INTN Length | |
59 | ) | |
60 | { | |
61 | UINTN Remainder; | |
62 | ||
63 | while (Length > 0) { | |
64 | Length--; | |
65 | Remainder = Number % 10; | |
66 | Number /= 10; | |
67 | Buffer[Length] = (UINT8) ('0' + Remainder); | |
68 | } | |
69 | } | |
70 | ||
71 | /** | |
72 | This function is to display the IPv4 address. | |
73 | ||
74 | @param[in] Ip The pointer to the IPv4 address. | |
75 | ||
76 | **/ | |
77 | VOID | |
78 | HttpBootShowIp4Addr ( | |
79 | IN EFI_IPv4_ADDRESS *Ip | |
80 | ) | |
81 | { | |
82 | UINTN Index; | |
83 | ||
84 | for (Index = 0; Index < 4; Index++) { | |
85 | AsciiPrint ("%d", Ip->Addr[Index]); | |
86 | if (Index < 3) { | |
87 | AsciiPrint ("."); | |
88 | } | |
89 | } | |
90 | } | |
91 | ||
92 | /** | |
93 | Create a HTTP_IO_HEADER to hold the HTTP header items. | |
94 | ||
95 | @param[in] MaxHeaderCount The maximun number of HTTP header in this holder. | |
96 | ||
97 | @return A pointer of the HTTP header holder or NULL if failed. | |
98 | ||
99 | **/ | |
100 | HTTP_IO_HEADER * | |
101 | HttpBootCreateHeader ( | |
102 | UINTN MaxHeaderCount | |
103 | ) | |
104 | { | |
105 | HTTP_IO_HEADER *HttpIoHeader; | |
106 | ||
107 | if (MaxHeaderCount == 0) { | |
108 | return NULL; | |
109 | } | |
110 | ||
111 | HttpIoHeader = AllocateZeroPool (sizeof (HTTP_IO_HEADER) + MaxHeaderCount * sizeof (EFI_HTTP_HEADER)); | |
112 | if (HttpIoHeader == NULL) { | |
113 | return NULL; | |
114 | } | |
115 | ||
116 | HttpIoHeader->MaxHeaderCount = MaxHeaderCount; | |
117 | HttpIoHeader->Headers = (EFI_HTTP_HEADER *) (HttpIoHeader + 1); | |
118 | ||
119 | return HttpIoHeader; | |
120 | } | |
121 | ||
122 | /** | |
123 | Destroy the HTTP_IO_HEADER and release the resouces. | |
124 | ||
125 | @param[in] HttpIoHeader Point to the HTTP header holder to be destroyed. | |
126 | ||
127 | **/ | |
128 | VOID | |
129 | HttpBootFreeHeader ( | |
130 | IN HTTP_IO_HEADER *HttpIoHeader | |
131 | ) | |
132 | { | |
133 | UINTN Index; | |
134 | ||
135 | if (HttpIoHeader != NULL) { | |
136 | if (HttpIoHeader->HeaderCount != 0) { | |
137 | for (Index = 0; Index < HttpIoHeader->HeaderCount; Index++) { | |
138 | FreePool (HttpIoHeader->Headers[Index].FieldName); | |
139 | FreePool (HttpIoHeader->Headers[Index].FieldValue); | |
140 | } | |
141 | } | |
142 | FreePool (HttpIoHeader); | |
143 | } | |
144 | } | |
145 | ||
146 | /** | |
147 | Find a specified header field according to the field name. | |
148 | ||
149 | @param[in] HeaderCount Number of HTTP header structures in Headers list. | |
150 | @param[in] Headers Array containing list of HTTP headers. | |
151 | @param[in] FieldName Null terminated string which describes a field name. | |
152 | ||
153 | @return Pointer to the found header or NULL. | |
154 | ||
155 | **/ | |
156 | EFI_HTTP_HEADER * | |
157 | HttpBootFindHeader ( | |
158 | IN UINTN HeaderCount, | |
159 | IN EFI_HTTP_HEADER *Headers, | |
160 | IN CHAR8 *FieldName | |
161 | ) | |
162 | { | |
163 | UINTN Index; | |
164 | ||
165 | if (HeaderCount == 0 || Headers == NULL || FieldName == NULL) { | |
166 | return NULL; | |
167 | } | |
168 | ||
169 | for (Index = 0; Index < HeaderCount; Index++){ | |
170 | // | |
171 | // Field names are case-insensitive (RFC 2616). | |
172 | // | |
173 | if (AsciiStriCmp (Headers[Index].FieldName, FieldName) == 0) { | |
174 | return &Headers[Index]; | |
175 | } | |
176 | } | |
177 | return NULL; | |
178 | } | |
179 | ||
180 | /** | |
181 | Set or update a HTTP header with the field name and corresponding value. | |
182 | ||
183 | @param[in] HttpIoHeader Point to the HTTP header holder. | |
184 | @param[in] FieldName Null terminated string which describes a field name. | |
185 | @param[in] FieldValue Null terminated string which describes the corresponding field value. | |
186 | ||
187 | @retval EFI_SUCCESS The HTTP header has been set or updated. | |
188 | @retval EFI_INVALID_PARAMETER Any input parameter is invalid. | |
189 | @retval EFI_OUT_OF_RESOURCES Insufficient resource to complete the operation. | |
190 | @retval Other Unexpected error happened. | |
191 | ||
192 | **/ | |
193 | EFI_STATUS | |
194 | HttpBootSetHeader ( | |
195 | IN HTTP_IO_HEADER *HttpIoHeader, | |
196 | IN CHAR8 *FieldName, | |
197 | IN CHAR8 *FieldValue | |
198 | ) | |
199 | { | |
200 | EFI_HTTP_HEADER *Header; | |
201 | UINTN StrSize; | |
202 | CHAR8 *NewFieldValue; | |
203 | ||
204 | if (HttpIoHeader == NULL || FieldName == NULL || FieldValue == NULL) { | |
205 | return EFI_INVALID_PARAMETER; | |
206 | } | |
207 | ||
208 | Header = HttpBootFindHeader (HttpIoHeader->HeaderCount, HttpIoHeader->Headers, FieldName); | |
209 | if (Header == NULL) { | |
210 | // | |
211 | // Add a new header. | |
212 | // | |
213 | if (HttpIoHeader->HeaderCount >= HttpIoHeader->MaxHeaderCount) { | |
214 | return EFI_OUT_OF_RESOURCES; | |
215 | } | |
216 | Header = &HttpIoHeader->Headers[HttpIoHeader->HeaderCount]; | |
217 | ||
218 | StrSize = AsciiStrSize (FieldName); | |
219 | Header->FieldName = AllocatePool (StrSize); | |
220 | if (Header->FieldName == NULL) { | |
221 | return EFI_OUT_OF_RESOURCES; | |
222 | } | |
223 | CopyMem (Header->FieldName, FieldName, StrSize); | |
224 | Header->FieldName[StrSize -1] = '\0'; | |
225 | ||
226 | StrSize = AsciiStrSize (FieldValue); | |
227 | Header->FieldValue = AllocatePool (StrSize); | |
228 | if (Header->FieldValue == NULL) { | |
229 | FreePool (Header->FieldName); | |
230 | return EFI_OUT_OF_RESOURCES; | |
231 | } | |
232 | CopyMem (Header->FieldValue, FieldValue, StrSize); | |
233 | Header->FieldValue[StrSize -1] = '\0'; | |
234 | ||
235 | HttpIoHeader->HeaderCount++; | |
236 | } else { | |
237 | // | |
238 | // Update an existing one. | |
239 | // | |
240 | StrSize = AsciiStrSize (FieldValue); | |
241 | NewFieldValue = AllocatePool (StrSize); | |
242 | if (NewFieldValue == NULL) { | |
243 | return EFI_OUT_OF_RESOURCES; | |
244 | } | |
245 | CopyMem (NewFieldValue, FieldValue, StrSize); | |
246 | NewFieldValue[StrSize -1] = '\0'; | |
247 | ||
248 | if (Header->FieldValue != NULL) { | |
249 | FreePool (Header->FieldValue); | |
250 | } | |
251 | Header->FieldValue = NewFieldValue; | |
252 | } | |
253 | ||
254 | return EFI_SUCCESS; | |
255 | } | |
256 | ||
257 | /** | |
258 | Notify the callback function when an event is triggered. | |
259 | ||
260 | @param[in] Event The triggered event. | |
261 | @param[in] Context The opaque parameter to the function. | |
262 | ||
263 | **/ | |
264 | VOID | |
265 | EFIAPI | |
266 | HttpIoCommonNotify ( | |
267 | IN EFI_EVENT Event, | |
268 | IN VOID *Context | |
269 | ) | |
270 | { | |
271 | *((BOOLEAN *) Context) = TRUE; | |
272 | } | |
273 | ||
274 | /** | |
275 | Create a HTTP_IO to access the HTTP service. It will create and configure | |
276 | a HTTP child handle. | |
277 | ||
278 | @param[in] Image The handle of the driver image. | |
279 | @param[in] Controller The handle of the controller. | |
280 | @param[in] IpVersion IP_VERSION_4 or IP_VERSION_6. | |
281 | @param[in] ConfigData The HTTP_IO configuration data. | |
282 | @param[out] HttpIo The HTTP_IO. | |
283 | ||
284 | @retval EFI_SUCCESS The HTTP_IO is created and configured. | |
285 | @retval EFI_INVALID_PARAMETER One or more parameters are invalid. | |
286 | @retval EFI_UNSUPPORTED One or more of the control options are not | |
287 | supported in the implementation. | |
288 | @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. | |
289 | @retval Others Failed to create the HTTP_IO or configure it. | |
290 | ||
291 | **/ | |
292 | EFI_STATUS | |
293 | HttpIoCreateIo ( | |
294 | IN EFI_HANDLE Image, | |
295 | IN EFI_HANDLE Controller, | |
296 | IN UINT8 IpVersion, | |
297 | IN HTTP_IO_CONFIG_DATA *ConfigData, | |
298 | OUT HTTP_IO *HttpIo | |
299 | ) | |
300 | { | |
301 | EFI_STATUS Status; | |
302 | EFI_HTTP_CONFIG_DATA HttpConfigData; | |
303 | EFI_HTTPv4_ACCESS_POINT Http4AccessPoint; | |
304 | EFI_HTTP_PROTOCOL *Http; | |
305 | EFI_EVENT Event; | |
306 | ||
307 | if ((Image == NULL) || (Controller == NULL) || (ConfigData == NULL) || (HttpIo == NULL)) { | |
308 | return EFI_INVALID_PARAMETER; | |
309 | } | |
310 | ||
311 | if (IpVersion != IP_VERSION_4 && IpVersion != IP_VERSION_6) { | |
312 | return EFI_UNSUPPORTED; | |
313 | } | |
314 | ||
315 | ZeroMem (HttpIo, sizeof (HTTP_IO)); | |
316 | ||
317 | // | |
318 | // Create the HTTP child instance and get the HTTP protocol. | |
319 | // | |
320 | Status = NetLibCreateServiceChild ( | |
321 | Controller, | |
322 | Image, | |
323 | &gEfiHttpServiceBindingProtocolGuid, | |
324 | &HttpIo->Handle | |
325 | ); | |
326 | if (EFI_ERROR (Status)) { | |
327 | return Status; | |
328 | } | |
329 | ||
330 | Status = gBS->OpenProtocol ( | |
331 | HttpIo->Handle, | |
332 | &gEfiHttpProtocolGuid, | |
333 | (VOID **) &Http, | |
334 | Image, | |
335 | Controller, | |
336 | EFI_OPEN_PROTOCOL_BY_DRIVER | |
337 | ); | |
338 | if (EFI_ERROR (Status) || (Http == NULL)) { | |
339 | goto ON_ERROR; | |
340 | } | |
341 | ||
342 | // | |
343 | // Init the configuration data and configure the HTTP child. | |
344 | // | |
345 | HttpIo->Image = Image; | |
346 | HttpIo->Controller = Controller; | |
347 | HttpIo->IpVersion = IpVersion; | |
348 | HttpIo->Http = Http; | |
349 | ||
350 | ZeroMem (&HttpConfigData, sizeof (EFI_HTTP_CONFIG_DATA)); | |
351 | HttpConfigData.HttpVersion = HttpVersion11; | |
352 | HttpConfigData.TimeOutMillisec = ConfigData->Config4.RequestTimeOut; | |
353 | if (HttpIo->IpVersion == IP_VERSION_4) { | |
354 | HttpConfigData.LocalAddressIsIPv6 = FALSE; | |
355 | ||
356 | Http4AccessPoint.UseDefaultAddress = ConfigData->Config4.UseDefaultAddress; | |
357 | Http4AccessPoint.LocalPort = ConfigData->Config4.LocalPort; | |
358 | IP4_COPY_ADDRESS (&Http4AccessPoint.LocalAddress, &ConfigData->Config4.LocalIp); | |
359 | IP4_COPY_ADDRESS (&Http4AccessPoint.LocalSubnet, &ConfigData->Config4.SubnetMask); | |
360 | HttpConfigData.AccessPoint.IPv4Node = &Http4AccessPoint; | |
361 | } else { | |
362 | ASSERT (FALSE); | |
363 | } | |
364 | ||
365 | Status = Http->Configure (Http, &HttpConfigData); | |
366 | if (EFI_ERROR (Status)) { | |
367 | goto ON_ERROR; | |
368 | } | |
369 | ||
370 | // | |
371 | // Create events for variuos asynchronous operations. | |
372 | // | |
373 | Status = gBS->CreateEvent ( | |
374 | EVT_NOTIFY_SIGNAL, | |
375 | TPL_NOTIFY, | |
376 | HttpIoCommonNotify, | |
377 | &HttpIo->IsTxDone, | |
378 | &Event | |
379 | ); | |
380 | if (EFI_ERROR (Status)) { | |
381 | goto ON_ERROR; | |
382 | } | |
383 | HttpIo->ReqToken.Event = Event; | |
384 | HttpIo->ReqToken.Message = &HttpIo->ReqMessage; | |
385 | ||
386 | Status = gBS->CreateEvent ( | |
387 | EVT_NOTIFY_SIGNAL, | |
388 | TPL_NOTIFY, | |
389 | HttpIoCommonNotify, | |
390 | &HttpIo->IsRxDone, | |
391 | &Event | |
392 | ); | |
393 | if (EFI_ERROR (Status)) { | |
394 | goto ON_ERROR; | |
395 | } | |
396 | HttpIo->RspToken.Event = Event; | |
397 | HttpIo->RspToken.Message = &HttpIo->RspMessage; | |
398 | ||
399 | return EFI_SUCCESS; | |
400 | ||
401 | ON_ERROR: | |
402 | HttpIoDestroyIo (HttpIo); | |
403 | ||
404 | return Status; | |
405 | } | |
406 | ||
407 | /** | |
408 | Destroy the HTTP_IO and release the resouces. | |
409 | ||
410 | @param[in] HttpIo The HTTP_IO which wraps the HTTP service to be destroyed. | |
411 | ||
412 | **/ | |
413 | VOID | |
414 | HttpIoDestroyIo ( | |
415 | IN HTTP_IO *HttpIo | |
416 | ) | |
417 | { | |
418 | EFI_HTTP_PROTOCOL *Http; | |
419 | EFI_EVENT Event; | |
420 | ||
421 | if (HttpIo == NULL) { | |
422 | return; | |
423 | } | |
424 | ||
425 | Event = HttpIo->ReqToken.Event; | |
426 | if (Event != NULL) { | |
427 | gBS->CloseEvent (Event); | |
428 | } | |
429 | ||
430 | Event = HttpIo->RspToken.Event; | |
431 | if (Event != NULL) { | |
432 | gBS->CloseEvent (Event); | |
433 | } | |
434 | ||
435 | Http = HttpIo->Http; | |
436 | if (Http != NULL) { | |
437 | Http->Configure (Http, NULL); | |
438 | gBS->CloseProtocol ( | |
439 | HttpIo->Handle, | |
440 | &gEfiHttpProtocolGuid, | |
441 | HttpIo->Image, | |
442 | HttpIo->Controller | |
443 | ); | |
444 | } | |
445 | ||
446 | NetLibDestroyServiceChild ( | |
447 | HttpIo->Controller, | |
448 | HttpIo->Image, | |
449 | &gEfiHttpServiceBindingProtocolGuid, | |
450 | HttpIo->Handle | |
451 | ); | |
452 | } | |
453 | ||
454 | /** | |
455 | Synchronously send a HTTP REQUEST message to the server. | |
456 | ||
457 | @param[in] HttpIo The HttpIo wrapping the HTTP service. | |
458 | @param[in] Request A pointer to storage such data as URL and HTTP method. | |
459 | @param[in] HeaderCount Number of HTTP header structures in Headers list. | |
460 | @param[in] Headers Array containing list of HTTP headers. | |
461 | @param[in] BodyLength Length in bytes of the HTTP body. | |
462 | @param[in] Body Body associated with the HTTP request. | |
463 | ||
464 | @retval EFI_SUCCESS The HTTP request is trasmitted. | |
465 | @retval EFI_INVALID_PARAMETER One or more parameters are invalid. | |
466 | @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. | |
467 | @retval EFI_DEVICE_ERROR An unexpected network or system error occurred. | |
468 | @retval Others Other errors as indicated. | |
469 | ||
470 | **/ | |
471 | EFI_STATUS | |
472 | HttpIoSendRequest ( | |
473 | IN HTTP_IO *HttpIo, | |
474 | IN EFI_HTTP_REQUEST_DATA *Request, | |
475 | IN UINTN HeaderCount, | |
476 | IN EFI_HTTP_HEADER *Headers, | |
477 | IN UINTN BodyLength, | |
478 | IN VOID *Body | |
479 | ) | |
480 | { | |
481 | EFI_STATUS Status; | |
482 | EFI_HTTP_PROTOCOL *Http; | |
483 | ||
484 | if (HttpIo == NULL || HttpIo->Http == NULL) { | |
485 | return EFI_INVALID_PARAMETER; | |
486 | } | |
487 | ||
488 | HttpIo->ReqToken.Status = EFI_NOT_READY; | |
489 | HttpIo->ReqToken.Message->Data.Request = Request; | |
490 | HttpIo->ReqToken.Message->HeaderCount = HeaderCount; | |
491 | HttpIo->ReqToken.Message->Headers = Headers; | |
492 | HttpIo->ReqToken.Message->BodyLength = BodyLength; | |
493 | HttpIo->ReqToken.Message->Body = Body; | |
494 | ||
495 | // | |
496 | // Queue the request token to HTTP instances. | |
497 | // | |
498 | Http = HttpIo->Http; | |
499 | HttpIo->IsTxDone = FALSE; | |
500 | Status = Http->Request ( | |
501 | Http, | |
502 | &HttpIo->ReqToken | |
503 | ); | |
504 | if (EFI_ERROR (Status)) { | |
505 | return Status; | |
506 | } | |
507 | ||
508 | // | |
509 | // Poll the network until transmit finish. | |
510 | // | |
511 | while (!HttpIo->IsTxDone) { | |
512 | Http->Poll (Http); | |
513 | } | |
514 | ||
515 | return HttpIo->ReqToken.Status; | |
516 | } | |
517 | ||
518 | /** | |
519 | Synchronously receive a HTTP RESPONSE message from the server. | |
520 | ||
521 | @param[in] HttpIo The HttpIo wrapping the HTTP service. | |
522 | @param[in] RecvMsgHeader TRUE to receive a new HTTP response (from message header). | |
523 | FALSE to continue receive the previous response message. | |
524 | @param[out] ResponseData Point to a wrapper of the received response data. | |
525 | ||
526 | @retval EFI_SUCCESS The HTTP resopnse is received. | |
527 | @retval EFI_INVALID_PARAMETER One or more parameters are invalid. | |
528 | @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. | |
529 | @retval EFI_DEVICE_ERROR An unexpected network or system error occurred. | |
530 | @retval Others Other errors as indicated. | |
531 | ||
532 | **/ | |
533 | EFI_STATUS | |
534 | HttpIoRecvResponse ( | |
535 | IN HTTP_IO *HttpIo, | |
536 | IN BOOLEAN RecvMsgHeader, | |
537 | OUT HTTP_IO_RESOPNSE_DATA *ResponseData | |
538 | ) | |
539 | { | |
540 | EFI_STATUS Status; | |
541 | EFI_HTTP_PROTOCOL *Http; | |
542 | ||
543 | if (HttpIo == NULL || HttpIo->Http == NULL || ResponseData == NULL) { | |
544 | return EFI_INVALID_PARAMETER; | |
545 | } | |
546 | ||
547 | // | |
548 | // Queue the response token to HTTP instances. | |
549 | // | |
550 | HttpIo->RspToken.Status = EFI_NOT_READY; | |
551 | if (RecvMsgHeader) { | |
552 | HttpIo->RspToken.Message->Data.Response = &ResponseData->Response; | |
553 | } else { | |
554 | HttpIo->RspToken.Message->Data.Response = NULL; | |
555 | } | |
556 | HttpIo->RspToken.Message->HeaderCount = 0; | |
557 | HttpIo->RspToken.Message->Headers = NULL; | |
558 | HttpIo->RspToken.Message->BodyLength = ResponseData->BodyLength; | |
559 | HttpIo->RspToken.Message->Body = ResponseData->Body; | |
560 | ||
561 | Http = HttpIo->Http; | |
562 | HttpIo->IsRxDone = FALSE; | |
563 | Status = Http->Response ( | |
564 | Http, | |
565 | &HttpIo->RspToken | |
566 | ); | |
567 | ||
568 | if (EFI_ERROR (Status)) { | |
569 | return Status; | |
570 | } | |
571 | ||
572 | // | |
573 | // Poll the network until transmit finish. | |
574 | // | |
575 | while (!HttpIo->IsRxDone) { | |
576 | Http->Poll (Http); | |
577 | } | |
578 | ||
579 | // | |
580 | // Store the received data into the wrapper. | |
581 | // | |
582 | Status = HttpIo->ReqToken.Status; | |
583 | if (!EFI_ERROR (Status)) { | |
584 | ResponseData->HeaderCount = HttpIo->RspToken.Message->HeaderCount; | |
585 | ResponseData->Headers = HttpIo->RspToken.Message->Headers; | |
586 | ResponseData->BodyLength = HttpIo->RspToken.Message->BodyLength; | |
587 | } | |
588 | ||
589 | return Status; | |
590 | } |