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