Merged socket development branch:
[mirror_edk2.git] / AppPkg / Applications / Sockets / WebServer / WebServer.c
CommitLineData
4684b66f 1/*++
2 This file contains an 'Intel UEFI Application' and is
3 licensed for Intel CPUs and chipsets under the terms of your
4 license agreement with Intel or your vendor. This file may
5 be modified by the user, subject to additional terms of the
6 license agreement
7--*/
8/*++
9
10Copyright (c) 2011 Intel Corporation. All rights reserved
11This software and associated documentation (if any) is furnished
12under a license and may only be used or copied in accordance
13with the terms of the license. Except as permitted by such
14license, no part of this software or documentation may be
15reproduced, stored in a retrieval system, or transmitted in any
16form or by any means without the express written consent of
17Intel Corporation.
18
19--*/
20
21/** @file
22 This is a simple shell application
23
24 This should be executed with "/Param2 Val1" and "/Param1" as the 2 command line options!
25
26**/
27
28#include <WebServer.h>
29
30DT_WEB_SERVER mWebServer; ///< Web server's control structure
31
32
33/**
34 Add a port to the list of ports to be polled.
35
36 @param [in] pWebServer The web server control structure address.
37
38 @param [in] SocketFD The socket's file descriptor to add to the list.
39
40 @retval EFI_SUCCESS The port was successfully added
41 @retval EFI_NO_RESOURCES Insufficient memory to add the port
42
43**/
44EFI_STATUS
45PortAdd (
46 IN DT_WEB_SERVER * pWebServer,
47 IN int SocketFD
48 )
49{
50 nfds_t Index;
51 size_t LengthInBytes;
52 nfds_t MaxEntries;
53 nfds_t MaxEntriesNew;
54 struct pollfd * pFdList;
55 struct pollfd * pFdListNew;
56 WSDT_PORT ** ppPortListNew;
57 WSDT_PORT * pPort;
58 EFI_STATUS Status;
59
60 DBG_ENTER ( );
61
62 //
63 // Use for/break instead of goto
64 //
65 for ( ; ; ) {
66 //
67 // Assume success
68 //
69 Status = EFI_SUCCESS;
70
71 //
72 // Create a new list if necessary
73 //
74 pFdList = pWebServer->pFdList;
75 MaxEntries = pWebServer->MaxEntries;
76 if ( pWebServer->Entries >= MaxEntries ) {
77 MaxEntriesNew = 16 + MaxEntries;
78
79 //
80 // The current FD list is full
81 // Allocate a new FD list
82 //
83 LengthInBytes = sizeof ( *pFdList ) * MaxEntriesNew;
84 Status = gBS->AllocatePool ( EfiRuntimeServicesData,
85 LengthInBytes,
86 (VOID **)&pFdListNew );
87 if ( EFI_ERROR ( Status )) {
88 DEBUG (( DEBUG_ERROR | DEBUG_POOL,
89 "ERROR - Failed to allocate the FD list, Status: %r\r\n",
90 Status ));
91 break;
92 }
93
94 //
95 // Allocate a new port list
96 //
97 LengthInBytes = sizeof ( *ppPortListNew ) * MaxEntriesNew;
98 Status = gBS->AllocatePool ( EfiRuntimeServicesData,
99 LengthInBytes,
100 (VOID **) &ppPortListNew );
101 if ( EFI_ERROR ( Status )) {
102 DEBUG (( DEBUG_ERROR | DEBUG_POOL,
103 "ERROR - Failed to allocate the port list, Status: %r\r\n",
104 Status ));
105
106 //
107 // Free the new FD list
108 //
109 gBS->FreePool ( pFdListNew );
110 break;
111 }
112
113 //
114 // Duplicate the FD list
115 //
116 Index = MaxEntries;
117 if ( NULL != pFdList ) {
118 CopyMem ( pFdListNew,
119 pFdList,
120 Index * sizeof ( *pFdList ));
121 }
122
123 //
124 // Initialize the new entries in the FD list
125 //
126 for ( ; MaxEntriesNew > Index; Index++ ) {
59bc0593 127 pFdListNew[ Index ].fd = -1;
128 pFdListNew[ Index ].events = 0;
129 pFdListNew[ Index ].revents = 0;
4684b66f 130 }
131
132 //
133 // Free the old FD list
134 //
135 if ( NULL != pFdList ) {
136 gBS->FreePool ( pFdList );
137 }
138
139 //
140 // Switch to the new FD list
141 //
142 pWebServer->pFdList = pFdListNew;
143 pFdList = pWebServer->pFdList;
144
145 //
146 // Duplicate the port list
147 //
148 Index = MaxEntries;
149 if ( NULL != pWebServer->ppPortList ) {
150 CopyMem ( ppPortListNew,
151 pWebServer->ppPortList,
152 Index * sizeof ( *ppPortListNew ));
153 }
154
155 //
156 // Initialize the new entries in the port list
157 //
158 for ( ; MaxEntriesNew > Index; Index++ ) {
59bc0593 159 ppPortListNew[ Index ] = NULL;
4684b66f 160 }
161
162 //
163 // Free the old port list
164 //
165 if ( NULL != pWebServer->ppPortList ) {
166 gBS->FreePool ( pWebServer->ppPortList );
167 }
168
169 //
170 // Switch to the new port list
171 //
172 pWebServer->ppPortList = ppPortListNew;
173
174 //
175 // Update the list size
176 //
177 pWebServer->MaxEntries = MaxEntriesNew;
178 }
179
180 //
181 // Allocate a new port
182 //
183 LengthInBytes = sizeof ( *pPort );
184 Status = gBS->AllocatePool ( EfiRuntimeServicesData,
185 LengthInBytes,
186 (VOID **)&pPort );
187 if ( EFI_ERROR ( Status )) {
188 DEBUG (( DEBUG_ERROR | DEBUG_POOL,
189 "ERROR - Failed to allocate the port, Status: %r\r\n",
190 Status ));
191 break;
192 }
193
194 //
195 // Initialize the port
196 //
197 pPort->RequestLength = 0;
198 pPort->TxBytes = 0;
199
200 //
201 // Add the socket to the FD list
202 //
59bc0593 203 pFdList[ pWebServer->Entries ].fd = SocketFD;
204 pFdList[ pWebServer->Entries ].events = POLLRDNORM
4684b66f 205 | POLLHUP;
59bc0593 206 pFdList[ pWebServer->Entries ].revents = 0;
4684b66f 207
208 //
209 // Add the port to the port list
210 //
59bc0593 211 pWebServer->ppPortList[ pWebServer->Entries ] = pPort;
4684b66f 212
213 //
214 // Account for the new entry
215 //
216 pWebServer->Entries += 1;
217 DEBUG (( DEBUG_PORT_WORK | DEBUG_INFO,
218 "WebServer handling %d ports\r\n",
219 pWebServer->Entries ));
220
221 //
222 // All done
223 //
224 break;
225 }
226
227 //
228 // Return the operation status
229 //
230 DBG_EXIT_STATUS ( Status );
231 return Status;
232}
233
234
235/**
236 Remove a port from the list of ports to be polled.
237
238 @param [in] pWebServer The web server control structure address.
239
240 @param [in] SocketFD The socket's file descriptor to add to the list.
241
242**/
243VOID
244PortRemove (
245 IN DT_WEB_SERVER * pWebServer,
246 IN int SocketFD
247 )
248{
249 nfds_t Entries;
250 nfds_t Index;
251 struct pollfd * pFdList;
252 WSDT_PORT ** ppPortList;
253
254 DBG_ENTER ( );
255
256 //
257 // Attempt to remove the entry from the list
258 //
259 Entries = pWebServer->Entries;
260 pFdList = pWebServer->pFdList;
261 ppPortList = pWebServer->ppPortList;
262 for ( Index = 0; Entries > Index; Index++ ) {
263 //
264 // Locate the specified socket file descriptor
265 //
59bc0593 266 if ( SocketFD == pFdList[ Index ].fd ) {
4684b66f 267 //
268 // Determine if this is the listen port
269 //
270 if ( SocketFD == pWebServer->HttpListenPort ) {
271 pWebServer->HttpListenPort = -1;
272 }
273
274 //
275 // Close the socket
276 //
277 close ( SocketFD );
278
279 //
280 // Free the port structure
281 //
59bc0593 282 gBS->FreePool ( ppPortList[ Index ]);
4684b66f 283
284 //
285 // Remove this port from the list by copying
286 // the rest of the list down one entry
287 //
288 Entries -= 1;
289 for ( ; Entries > Index; Index++ ) {
59bc0593 290 pFdList[ Index ] = pFdList[ Index + 1 ];
291 ppPortList[ Index ] = ppPortList[ Index + 1 ];
4684b66f 292 }
59bc0593 293 pFdList[ Index ].fd = -1;
294 pFdList[ Index ].events = 0;
295 pFdList[ Index ].revents = 0;
296 ppPortList[ Index ] = NULL;
4684b66f 297
298 //
299 // Update the number of entries in the list
300 //
301 pWebServer->Entries = Entries;
302 DEBUG (( DEBUG_PORT_WORK | DEBUG_INFO,
303 "WebServer handling %d ports\r\n",
304 pWebServer->Entries ));
305 break;
306 }
307 }
308
309 DBG_EXIT ( );
310}
311
312
313/**
314 Process the work for the sockets.
315
316 @param [in] pWebServer The web server control structure address.
317
318 @param [in] SocketFD The socket's file descriptor to add to the list.
319
320 @param [in] events everts is a bitmask of the work to be done
321
322 @param [in] pPort The address of a WSDT_PORT structure
323
324 @retval EFI_SUCCESS The operation was successful
325 @retval EFI_DEVICE_ERROR Error, close the port
326
327**/
328EFI_STATUS
329PortWork (
330 IN DT_WEB_SERVER * pWebServer,
331 IN int SocketFD,
332 IN INTN events,
333 IN WSDT_PORT * pPort
334 )
335{
336 BOOLEAN bDone;
337 size_t LengthInBytes;
338 int NewSocket;
339 EFI_STATUS OpStatus;
f6e5cdd5 340 struct sockaddr_in6 RemoteAddress;
4684b66f 341 socklen_t RemoteAddressLength;
342 EFI_STATUS Status;
343
344 DEBUG (( DEBUG_PORT_WORK, "Entering PortWork\r\n" ));
345
346 //
347 // Assume success
348 //
349 OpStatus = EFI_SUCCESS;
350
351 //
352 // Handle input events
353 //
354 if ( 0 != ( events & POLLRDNORM )) {
355 //
356 // Determine if this is a connection attempt
357 //
f6e5cdd5 358 if (( SocketFD == pWebServer->HttpListenPort )
359 || ( SocketFD == pWebServer->HttpListenPort6 )) {
4684b66f 360 //
361 // Handle connection attempts
362 // Accepts arrive as read events
363 //
364 RemoteAddressLength = sizeof ( RemoteAddress );
365 NewSocket = accept ( SocketFD,
f6e5cdd5 366 (struct sockaddr *)&RemoteAddress,
4684b66f 367 &RemoteAddressLength );
368 if ( -1 != NewSocket ) {
369 if ( 0 != NewSocket ) {
370 //
371 // Add this port to the list monitored by the web server
372 //
373 Status = PortAdd ( pWebServer, NewSocket );
374 if ( EFI_ERROR ( Status )) {
375 DEBUG (( DEBUG_ERROR,
376 "ERROR - Failed to add the port 0x%08x, Status: %r\r\n",
377 NewSocket,
378 Status ));
379
380 //
381 // Done with the new socket
382 //
383 close ( NewSocket );
384 }
385 }
386 else {
387 DEBUG (( DEBUG_ERROR,
388 "ERROR - Socket not available!\r\n" ));
389 }
390
391 //
392 // Leave the listen port open
393 //
394 }
395 else {
396 //
397 // Listen port error
398 // Close the listen port by returning error status
399 //
400 OpStatus = EFI_DEVICE_ERROR;
401 DEBUG (( DEBUG_ERROR,
402 "ERROR - Failed to accept new connection, errno: 0x%08x\r\n",
403 errno ));
404 }
405 }
406 else {
407 //
408 // Handle the data received event
409 //
410 if ( 0 == pPort->RequestLength ) {
411 //
412 // Receive the page request
413 //
414 pPort->RequestLength = recv ( SocketFD,
415 &pPort->Request[0],
416 DIM ( pPort->Request ),
417 0 );
418 if ( -1 == pPort->RequestLength ) {
419 //
420 // Receive error detected
421 // Close the port
422 //
423 OpStatus = EFI_DEVICE_ERROR;
424 }
425 else {
426 DEBUG (( DEBUG_REQUEST,
427 "0x%08x: Socket - Received %d bytes of HTTP request\r\n",
428 SocketFD,
429 pPort->RequestLength ));
430
431 //
432 // Process the request
433 //
434 OpStatus = HttpRequest ( SocketFD, pPort, &bDone );
435 if ( bDone ) {
436 //
437 // Notify the upper layer to close the socket
438 //
439 OpStatus = EFI_DEVICE_ERROR;
440 }
441 }
442 }
59bc0593 443 else {
4684b66f 444 //
445 // Receive the file data
446 //
447 LengthInBytes = recv ( SocketFD,
448 &pPort->RxBuffer[0],
449 DIM ( pPort->RxBuffer ),
450 0 );
451 if ( -1 == LengthInBytes ) {
452 //
453 // Receive error detected
454 // Close the port
455 //
456 OpStatus = EFI_DEVICE_ERROR;
457 }
458 else {
459 DEBUG (( DEBUG_REQUEST,
460 "0x%08x: Socket - Received %d bytes of file data\r\n",
461 SocketFD,
462 LengthInBytes ));
463
464 //
465 // TODO: Process the file data
466 //
467 }
468 }
469 }
470 }
471
472 //
473 // Handle the close event
474 //
475 if ( 0 != ( events & POLLHUP )) {
476 //
477 // Close the port
478 //
479 OpStatus = EFI_DEVICE_ERROR;
480 }
481
482 //
483 // Return the operation status
484 //
485 DEBUG (( DEBUG_PORT_WORK,
486 "Exiting PortWork, Status: %r\r\n",
487 OpStatus ));
488 return OpStatus;
489}
490
491
492/**
493 Scan the list of sockets and process any pending work
494
495 @param [in] pWebServer The web server control structure address.
496
497**/
498VOID
499SocketPoll (
500 IN DT_WEB_SERVER * pWebServer
501 )
502{
503 int FDCount;
504 struct pollfd * pPoll;
505 WSDT_PORT ** ppPort;
506 EFI_STATUS Status;
507
508 DEBUG (( DEBUG_SOCKET_POLL, "Entering SocketPoll\r\n" ));
509
510 //
511 // Determine if any ports are active
512 //
513 FDCount = poll ( pWebServer->pFdList,
514 pWebServer->Entries,
515 CLIENT_POLL_DELAY );
516 if ( -1 == FDCount ) {
517 DEBUG (( DEBUG_ERROR | DEBUG_SOCKET_POLL,
518 "ERROR - errno: %d\r\n",
519 errno ));
520 }
521
522 pPoll = pWebServer->pFdList;
523 ppPort = pWebServer->ppPortList;
524 while ( 0 < FDCount ) {
525 //
526 // Walk the list of ports to determine what work needs to be done
527 //
528 if ( 0 != pPoll->revents ) {
529 //
530 // Process this port
531 //
532 Status = PortWork ( pWebServer,
533 pPoll->fd,
534 pPoll->revents,
535 *ppPort );
536 pPoll->revents = 0;
537
538 //
539 // Close the port if necessary
540 //
541 if ( EFI_ERROR ( Status )) {
542 PortRemove ( pWebServer, pPoll->fd );
543 pPoll -= 1;
544 ppPort -= 1;
545 }
546
547 //
548 // Account for this file descriptor
549 //
550 FDCount -= 1;
551 }
552
553 //
554 // Set the next port
555 //
556 pPoll += 1;
557 ppPort += 1;
558 }
559
560 DEBUG (( DEBUG_SOCKET_POLL, "Exiting SocketPoll\r\n" ));
561}
562
563
564/**
f6e5cdd5 565 Create an HTTP port for the web server
4684b66f 566
f6e5cdd5 567 This routine polls the network layer to create an HTTP port for the
4684b66f 568 web server. More than one attempt may be necessary since it may take
569 some time to get the IP address and initialize the upper layers of
570 the network stack.
571
572 After the HTTP port is created, the socket layer will manage the
573 coming and going of the network connections until the last network
574 connection is broken.
575
f6e5cdd5 576 @param [in] pWebServer The web server control structure address.
577 @param [in] AddressFamily Address family for the network connection
578 @param [in] Protocol Protocol to use for the network connection
579 @param [in] HttpPort Port number for the HTTP connection
580 @param [out] pPort Address of the port
4684b66f 581
582**/
583VOID
f6e5cdd5 584WebServerListen (
585 IN DT_WEB_SERVER * pWebServer,
586 IN sa_family_t AddressFamily,
587 IN int Protocol,
588 IN UINT16 HttpPort,
589 OUT int * pPort
4684b66f 590 )
591{
f6e5cdd5 592 union {
593 struct sockaddr_in v4;
594 struct sockaddr_in6 v6;
595 } WebServerAddress;
4684b66f 596 int SocketStatus;
597 EFI_STATUS Status;
598
f6e5cdd5 599 DEBUG (( DEBUG_SERVER_LISTEN, "Entering WebServerListen\r\n" ));
4684b66f 600
601 //
f6e5cdd5 602 // Attempt to create the socket for the web server
4684b66f 603 //
f6e5cdd5 604 * pPort = socket ( AddressFamily, SOCK_STREAM, Protocol );
605 if ( -1 != *pPort ) {
4684b66f 606 //
f6e5cdd5 607 // Build the socket address
4684b66f 608 //
f6e5cdd5 609 ZeroMem ( &WebServerAddress, sizeof ( WebServerAddress ));
610 if ( AF_INET == AddressFamily ) {
611 WebServerAddress.v4.sin_len = sizeof ( WebServerAddress.v4 );
612 WebServerAddress.v4.sin_family = AddressFamily;
613 WebServerAddress.v4.sin_port = htons ( HttpPort );
614 }
615 else {
616 WebServerAddress.v6.sin6_len = sizeof ( WebServerAddress.v6 );
617 WebServerAddress.v6.sin6_family = AddressFamily;
618 WebServerAddress.v6.sin6_port = htons ( HttpPort );
619 WebServerAddress.v6.sin6_scope_id = __IPV6_ADDR_SCOPE_GLOBAL;
620 }
4684b66f 621
f6e5cdd5 622 //
623 // Bind the socket to the HTTP port
624 //
625 SocketStatus = bind ( *pPort,
626 (struct sockaddr *) &WebServerAddress,
627 WebServerAddress.v4.sin_len );
628 if ( -1 != SocketStatus ) {
4684b66f 629 //
f6e5cdd5 630 // Enable connections to the HTTP port
4684b66f 631 //
f6e5cdd5 632 SocketStatus = listen ( *pPort, SOMAXCONN );
4684b66f 633 if ( -1 != SocketStatus ) {
634 //
f6e5cdd5 635 // Add the HTTP port to the list of ports to poll
4684b66f 636 //
f6e5cdd5 637 Status = PortAdd ( pWebServer, *pPort );
638 if ( EFI_ERROR ( Status )) {
639 SocketStatus = -1;
640 }
641 else {
642 DEBUG (( DEBUG_PORT_WORK,
643 "Listening on Tcp%d:%d\r\n",
644 ( AF_INET == AddressFamily ) ? 4 : 6,
645 HttpPort ));
646 }
4684b66f 647 }
648 }
649
650 //
f6e5cdd5 651 // Release the socket if necessary
4684b66f 652 //
f6e5cdd5 653 if ( -1 == SocketStatus ) {
654 close ( *pPort );
655 *pPort = -1;
4684b66f 656 }
657 }
658
f6e5cdd5 659 DEBUG (( DEBUG_SERVER_LISTEN, "Exiting WebServerListen\r\n" ));
4684b66f 660}
661
662
4684b66f 663/**
664 Entry point for the web server application.
665
666 @param [in] Argc The number of arguments
667 @param [in] Argv The argument value array
668
669 @retval 0 The application exited normally.
670 @retval Other An error occurred.
671**/
672int
673main (
674 IN int Argc,
675 IN char **Argv
676 )
677{
f6e5cdd5 678 UINT16 HttpPort;
679 UINTN Index;
4684b66f 680 DT_WEB_SERVER * pWebServer;
681 EFI_STATUS Status;
f6e5cdd5 682 UINT64 TriggerTime;
683
684 //
685 // Get the HTTP port
686 //
687 HttpPort = PcdGet16 ( WebServer_HttpPort );
688 DEBUG (( DEBUG_HTTP_PORT,
689 "HTTP Port: %d\r\n",
690 HttpPort ));
4684b66f 691
692 //
693 // Create a timer event to start HTTP port
694 //
695 pWebServer = &mWebServer;
696 Status = gBS->CreateEvent ( EVT_TIMER,
697 TPL_WEB_SERVER,
698 NULL,
699 NULL,
700 &pWebServer->TimerEvent );
701 if ( !EFI_ERROR ( Status )) {
f6e5cdd5 702 TriggerTime = HTTP_PORT_POLL_DELAY * ( 1000 * 10 );
703 Status = gBS->SetTimer ( pWebServer->TimerEvent,
704 TimerPeriodic,
705 TriggerTime );
4684b66f 706 if ( !EFI_ERROR ( Status )) {
707 //
708 // Run the web server forever
709 //
f6e5cdd5 710 pWebServer->HttpListenPort = -1;
711 pWebServer->HttpListenPort6 = -1;
712 pWebServer->bRunning = TRUE;
713 do {
4684b66f 714 //
715 // Poll the network layer to create the HTTP port
716 // for the web server. More than one attempt may
717 // be necessary since it may take some time to get
718 // the IP address and initialize the upper layers
719 // of the network stack.
720 //
f6e5cdd5 721 if (( -1 == pWebServer->HttpListenPort )
722 || ( -1 == pWebServer->HttpListenPort6 )) {
4684b66f 723 do {
f6e5cdd5 724 //
725 // Wait a while before polling for a connection
726 //
727 if ( EFI_SUCCESS != gBS->CheckEvent ( pWebServer->TimerEvent )) {
728 if ( 0 != pWebServer->Entries ) {
729 break;
730 }
731 gBS->WaitForEvent ( 1, &pWebServer->TimerEvent, &Index );
732 }
4684b66f 733
f6e5cdd5 734 //
735 // Poll for a network connection
736 //
737 if ( -1 == pWebServer->HttpListenPort ) {
738 WebServerListen ( pWebServer,
739 AF_INET,
740 IPPROTO_TCP,
741 HttpPort,
742 &pWebServer->HttpListenPort );
743 }
744 if ( -1 == pWebServer->HttpListenPort6 ) {
745 WebServerListen ( pWebServer,
746 AF_INET6,
747 IPPROTO_TCP,
748 HttpPort,
749 &pWebServer->HttpListenPort6 );
750 }
751
752 //
753 // Continue polling while both network connections are
754 // not present
755 //
756 } while ( 0 == pWebServer->Entries );
4684b66f 757 }
758
759 //
f6e5cdd5 760 // Poll the sockets for activity while both network
761 // connections are connected
4684b66f 762 //
f6e5cdd5 763 do {
764 SocketPoll ( pWebServer );
765 } while ( pWebServer->bRunning
766 && ( -1 != pWebServer->HttpListenPort )
767 && ( -1 != pWebServer->HttpListenPort6 ));
768
769 //
770 // Continue polling the network connections until both
771 // TCP4 and TCP6 are connected
772 //
773 } while ( pWebServer->bRunning );
4684b66f 774
775 //
f6e5cdd5 776 // Stop the timer
4684b66f 777 //
f6e5cdd5 778 gBS->SetTimer ( pWebServer->TimerEvent,
779 TimerCancel,
780 0 );
4684b66f 781 }
f6e5cdd5 782
783 //
784 // Done with the timer event
785 //
786 gBS->CloseEvent ( pWebServer->TimerEvent );
4684b66f 787 }
788
789 //
790 // Return the final status
791 //
792 DBG_EXIT_STATUS ( Status );
793 return Status;
794}