Merged socket development branch:
[mirror_edk2.git] / AppPkg / Applications / Sockets / WebServer / WebServer.c
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
10 Copyright (c) 2011 Intel Corporation. All rights reserved
11 This software and associated documentation (if any) is furnished
12 under a license and may only be used or copied in accordance
13 with the terms of the license. Except as permitted by such
14 license, no part of this software or documentation may be
15 reproduced, stored in a retrieval system, or transmitted in any
16 form or by any means without the express written consent of
17 Intel 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
30 DT_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 **/
44 EFI_STATUS
45 PortAdd (
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++ ) {
127 pFdListNew[ Index ].fd = -1;
128 pFdListNew[ Index ].events = 0;
129 pFdListNew[ Index ].revents = 0;
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++ ) {
159 ppPortListNew[ Index ] = NULL;
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 //
203 pFdList[ pWebServer->Entries ].fd = SocketFD;
204 pFdList[ pWebServer->Entries ].events = POLLRDNORM
205 | POLLHUP;
206 pFdList[ pWebServer->Entries ].revents = 0;
207
208 //
209 // Add the port to the port list
210 //
211 pWebServer->ppPortList[ pWebServer->Entries ] = pPort;
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 **/
243 VOID
244 PortRemove (
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 //
266 if ( SocketFD == pFdList[ Index ].fd ) {
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 //
282 gBS->FreePool ( ppPortList[ Index ]);
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++ ) {
290 pFdList[ Index ] = pFdList[ Index + 1 ];
291 ppPortList[ Index ] = ppPortList[ Index + 1 ];
292 }
293 pFdList[ Index ].fd = -1;
294 pFdList[ Index ].events = 0;
295 pFdList[ Index ].revents = 0;
296 ppPortList[ Index ] = NULL;
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 **/
328 EFI_STATUS
329 PortWork (
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;
340 struct sockaddr_in6 RemoteAddress;
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 //
358 if (( SocketFD == pWebServer->HttpListenPort )
359 || ( SocketFD == pWebServer->HttpListenPort6 )) {
360 //
361 // Handle connection attempts
362 // Accepts arrive as read events
363 //
364 RemoteAddressLength = sizeof ( RemoteAddress );
365 NewSocket = accept ( SocketFD,
366 (struct sockaddr *)&RemoteAddress,
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 }
443 else {
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 **/
498 VOID
499 SocketPoll (
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 /**
565 Create an HTTP port for the web server
566
567 This routine polls the network layer to create an HTTP port for the
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
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
581
582 **/
583 VOID
584 WebServerListen (
585 IN DT_WEB_SERVER * pWebServer,
586 IN sa_family_t AddressFamily,
587 IN int Protocol,
588 IN UINT16 HttpPort,
589 OUT int * pPort
590 )
591 {
592 union {
593 struct sockaddr_in v4;
594 struct sockaddr_in6 v6;
595 } WebServerAddress;
596 int SocketStatus;
597 EFI_STATUS Status;
598
599 DEBUG (( DEBUG_SERVER_LISTEN, "Entering WebServerListen\r\n" ));
600
601 //
602 // Attempt to create the socket for the web server
603 //
604 * pPort = socket ( AddressFamily, SOCK_STREAM, Protocol );
605 if ( -1 != *pPort ) {
606 //
607 // Build the socket address
608 //
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 }
621
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 ) {
629 //
630 // Enable connections to the HTTP port
631 //
632 SocketStatus = listen ( *pPort, SOMAXCONN );
633 if ( -1 != SocketStatus ) {
634 //
635 // Add the HTTP port to the list of ports to poll
636 //
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 }
647 }
648 }
649
650 //
651 // Release the socket if necessary
652 //
653 if ( -1 == SocketStatus ) {
654 close ( *pPort );
655 *pPort = -1;
656 }
657 }
658
659 DEBUG (( DEBUG_SERVER_LISTEN, "Exiting WebServerListen\r\n" ));
660 }
661
662
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 **/
672 int
673 main (
674 IN int Argc,
675 IN char **Argv
676 )
677 {
678 UINT16 HttpPort;
679 UINTN Index;
680 DT_WEB_SERVER * pWebServer;
681 EFI_STATUS Status;
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 ));
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 )) {
702 TriggerTime = HTTP_PORT_POLL_DELAY * ( 1000 * 10 );
703 Status = gBS->SetTimer ( pWebServer->TimerEvent,
704 TimerPeriodic,
705 TriggerTime );
706 if ( !EFI_ERROR ( Status )) {
707 //
708 // Run the web server forever
709 //
710 pWebServer->HttpListenPort = -1;
711 pWebServer->HttpListenPort6 = -1;
712 pWebServer->bRunning = TRUE;
713 do {
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 //
721 if (( -1 == pWebServer->HttpListenPort )
722 || ( -1 == pWebServer->HttpListenPort6 )) {
723 do {
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 }
733
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 );
757 }
758
759 //
760 // Poll the sockets for activity while both network
761 // connections are connected
762 //
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 );
774
775 //
776 // Stop the timer
777 //
778 gBS->SetTimer ( pWebServer->TimerEvent,
779 TimerCancel,
780 0 );
781 }
782
783 //
784 // Done with the timer event
785 //
786 gBS->CloseEvent ( pWebServer->TimerEvent );
787 }
788
789 //
790 // Return the final status
791 //
792 DBG_EXIT_STATUS ( Status );
793 return Status;
794 }