emNet Websocket printf Server (Sample)

From SEGGER Knowledge Base
Jump to navigation Jump to search
IP_WEBSOCKET_printf_Server_Sample.c
Requires modifications No
Download IP_WEBSOCKET_printf_Server_Sample.c

This Sample demonstrates a simple WebSocket terminal that can receive text messages to printf them to the standard output. The WebSocket protocol returned in the response is "debug".

Code

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

Purpose : Simple WebSocket terminal that can receive text messages
          to printf them to the standard output.
          The WebSocket protocol returned in the response is "debug".
          The protocols suggested by the client are not parsed by this
          sample for simplicity but the protocol returned can be
          configured by the define WEBSOCKET_PROTO .

Example output:
          ...
          <client connects>
          26:947 WebSocketServer - WebSocket client connected.

          27:947 WebSocketServer - Client: OS time: 4027

          29:140 WebSocketServer - Client: OS time: 5220

          30:340 WebSocketServer - Client: OS time: 6420

          31:540 WebSocketServer - Client: OS time: 7620

          32:740 WebSocketServer - Client: OS time: 8820

          33:940 WebSocketServer - Client: OS time: 10020
          ...

Additional information:
  The sample can easily be configured to start the emWeb server with different configurations and features
  such as SSL support.

  Preparations:
    When using SSL: To use this sample together with the WEBSOCKET_printf_Client sample, it is necessary to
    generate a self-signed SSL certificate for authentication.

    First, use openSSL to generate a RSA private key, and then generate a new certificate using this key.
    The 'Common Name'for the certificate must be the URL of the server, or in this case the IP address of
    the server.

    The certificate and key now need to be installed into emSSL. Convert both to a C array, using the Bin2C
    tool supplied with emSSL. The .c and .h files then need to be imported into the Shared\SSL\Sample\Certificates
    folder.

    In SSL_X_Config.c, add the certificate in _GetCertificate() and the private key in _GetPrivateKey().
    Please refer to the SSL documentation (UM15001_emSSL) chapter 3.61 'Creating certificates using OpenSSL'
    and 3.7.1 'Installing a single RSA certificate and key' for more information, including the exact commands
    necessary to generate the keys and certificate.

    Use the PrintCert tool, also supplied with emSSL, to add the certificate to SSL_X_TrustedCerts.c, as
    described in chapter 3.7.3 'Installing root certificates' in UM15001_emSSL. Finally, add this root
    certificate in SSL_X_Config() in SSL_X_Config.c.


    Enable/disable the features that you want to use. The available
    binary configuration switches are:
      - APP_USE_SSL : Enable creation of a secured IPv4 socket on port 443 (default). Requires emSSL.

*/

#include "RTOS.h"
#include "BSP.h"
#include "IP.h"
#include "IP_WEBSOCKET.h"

#include <stdio.h>
#include <string.h>

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#ifndef   WEBS_STRLEN
  #define WEBS_STRLEN          strlen
#endif

#ifndef   WEBS_MEMSET
  #define WEBS_MEMSET          memset
#endif

#define USE_RX_TASK      0                           // 0: Packets are read in ISR, 1: Packets are read in a task of its own.

#ifndef   APP_USE_SSL
  #define APP_USE_SSL   (0)                          // If enabled, creates SSL sockets as well.
#endif

#if (APP_USE_SSL != 0)
  #include "SSL.h"
#endif

//
// WebSocket sample configuration.
//
#if (APP_USE_SSL != 0)
  #define SERVER_PORT    443
#else
  #define SERVER_PORT    8181
#endif
#define WEBSOCKET_PROTO  "debug"  // WebSocket subprotocol sent back.

//
// Task priorities.
//
enum {
   TASK_PRIO_IP_WEBSOCKET = 150
  ,TASK_PRIO_IP_TASK         // Priority should be higher than all IP application tasks.
#if USE_RX_TASK
  ,TASK_PRIO_IP_RX_TASK      // Must be the highest priority of all IP related tasks.
#endif
};

//
// Task stack sizes that might not fit for some interfaces (multiples of sizeof(int)).
//
#ifndef   APP_TASK_STACK_OVERHEAD
  #define APP_TASK_STACK_OVERHEAD     0
#endif

#ifndef     APP_MAIN_STACK_SIZE
  #if (APP_USE_SSL != 0)
    #define   APP_MAIN_STACK_SIZE      (5120)
  #else
    #define   APP_MAIN_STACK_SIZE      (1280)
  #endif
#endif

/*********************************************************************
*
*       Defines, fixed
*
**********************************************************************
*/

#define OPEN_RESPONSE  "HTTP/1.1 101 Switching Protocols\r\n" \
                       "Upgrade: websocket\r\n"               \
                       "Connection: Upgrade\r\n"              \
                       "Sec-WebSocket-Protocol: %s\r\n"       \
                       "Sec-WebSocket-Accept: %s\r\n"         \
                       "\r\n"

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

#ifdef __cplusplus
extern "C" {     /* Make sure we have C-declarations in C++ programs */
#endif
void MainTask(void);
#ifdef __cplusplus
}
#endif

#if (APP_USE_SSL != 0)
static const SSL_TRANSPORT_API _IP_Transport;
#endif

static int _cbWebSocket_Recv(IP_WEBSOCKET_CONTEXT* pContext, IP_WEBSOCKET_CONNECTION* pConnection,       void* pData, unsigned NumBytes);
static int _cbWebSocket_Send(IP_WEBSOCKET_CONTEXT* pContext, IP_WEBSOCKET_CONNECTION* pConnection, const void* pData, unsigned NumBytes);

#if (APP_USE_SSL != 0)
  static int _cbWebSocket_SSL_Recv(IP_WEBSOCKET_CONTEXT* pContext, IP_WEBSOCKET_CONNECTION* pConnection,       void* pData, unsigned NumBytes);
  static int _cbWebSocket_SSL_Send(IP_WEBSOCKET_CONTEXT* pContext, IP_WEBSOCKET_CONNECTION* pConnection, const void* pData, unsigned NumBytes);
#endif

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static int _IFaceId;

//
// Task stacks and Task-Control-Blocks.
//
static OS_STACKPTR int _IPStack[TASK_STACK_SIZE_IP_TASK/sizeof(int)];                 // Stack of the IP_Task.
static OS_TASK         _IPTCB;                                                        // Task-Control-Block of the IP_Task.

static OS_STACKPTR int APP_MainStack[APP_MAIN_STACK_SIZE / sizeof(int)];    // Stack of the starting point of this sample.
static OS_TASK         APP_MainTCB;                                         // Task-Control-Block of the IP_Task.

#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[TASK_STACK_SIZE_IP_RX_TASK/sizeof(int)];            // Stack of the IP_RxTask.
static OS_TASK         _IPRxTCB;                                                      // Task-Control-Block of the IP_RxTask.
#endif

#if (APP_USE_SSL != 0)
//
// SSL
//
static SSL_SESSION _SSLSession;
#endif

//
// WebSocket
//
static char _acBuffer[1024];

static const IP_WEBSOCKET_TRANSPORT_API _WebSocketTransportAPI = {
#if (APP_USE_SSL != 0)
  _cbWebSocket_SSL_Recv,  // pfReceive
  _cbWebSocket_SSL_Send,  // pfSend
  NULL                    // pfGenMaskKey, client only.
#else
  _cbWebSocket_Recv,  // pfReceive
  _cbWebSocket_Send,  // pfSend
  NULL                // pfGenMaskKey, client only.
#endif
};

/*********************************************************************
*
*       Local functions
*
**********************************************************************
*/

/*********************************************************************
*
*       _cbWebSocket_Send()
*
*  Function description
*    WebSocket callback that for sending data using the underlying
*    network communication API (typically BSD socket API).
*
*  Parameters
*    pContext   : WebSocket context.
*    pConnection: Network connection handle.
*    pData      : Data to send.
*    NumBytes   : Amount of data to send.
*
*  Return value
*    Amount of data sent: >  0
*    Connection closed  : == 0
*    Error              : <  0
*/
static int _cbWebSocket_Send(IP_WEBSOCKET_CONTEXT* pContext, IP_WEBSOCKET_CONNECTION* pConnection, const void* pData, unsigned NumBytes) {
  IP_WEBSOCKET_USE_PARA(pContext);

  return send((long)pConnection, (const char*)pData, NumBytes, 0);
}

/*********************************************************************
*
*       _cbWebSocket_Recv()
*
*  Function description
*    WebSocket callback that for sending data using the underlying
*    network communication API (typically BSD socket API).
*
*  Parameters
*    pContext   : WebSocket context.
*    pConnection: Network connection handle.
*    pData      : Where to store the received data.
*    NumBytes   : Maximum amount of data to receive.
*
*  Return value
*    Amount of data received: >  0
*    Connection closed      : == 0
*    Error                  : <  0
*/
static int _cbWebSocket_Recv(IP_WEBSOCKET_CONTEXT* pContext, IP_WEBSOCKET_CONNECTION* pConnection, void* pData, unsigned NumBytes) {
  IP_WEBSOCKET_USE_PARA(pContext);

  return recv((long)pConnection, (char*)pData, NumBytes, 0);
}

#if (APP_USE_SSL != 0)

/*********************************************************************
*
*       _cbWebSocket_Send_SSL()
*/
static int _cbWebSocket_SSL_Send(IP_WEBSOCKET_CONTEXT* pContext, IP_WEBSOCKET_CONNECTION* pConnection, const void* pData, unsigned NumBytes) {
  IP_WEBSOCKET_USE_PARA(pContext);

  SSL_SESSION* pSession;
  int          r;

  pSession = (SSL_SESSION*)pConnection;
  r = SSL_SESSION_Send(pSession, pData, NumBytes);
  return r;
}

/*********************************************************************
*
*       _SSL_Send()
*
*  Function description
*    SSL transport API wrapper for send()
*/
static int _SSL_Send(int Socket, const char *pData, int Len, int Flags) {
  return send(Socket, pData, Len, Flags);
}

/*********************************************************************
*
*       _cbWebSocket_Recv_SSL()
*/
static int _cbWebSocket_SSL_Recv(IP_WEBSOCKET_CONTEXT* pContext, IP_WEBSOCKET_CONNECTION* pConnection, void* pData, unsigned NumBytes) {
  IP_WEBSOCKET_USE_PARA(pContext);

  SSL_SESSION* pSession;
  int          r;

  pSession = (SSL_SESSION*)pConnection;
    do {
      r = SSL_SESSION_Receive(pSession, pData, NumBytes);
    } while (r == 0);
    //
    // Translate EOF into "connection closed",
    // the Websocket expects same return values as with recv().
    //
    if (r == SSL_ERROR_EOF) {
      r = 0;
    }
  return r;
}

/*********************************************************************
*
*       _SSL_Recv()
*
*  Function description
*    SSL transport API wrapper for recv()
*/
static int _SSL_Recv(int Socket, char *pData, int Len, int Flags) {
  return recv(Socket, pData, Len, Flags);
}

/*********************************************************************
*
*       SSL transport layer
*
*  Description
*    SSL transport API.
*/
static const SSL_TRANSPORT_API _IP_Transport = {
  _SSL_Send,
  _SSL_Recv,
  NULL,
  NULL
};

#endif

/*********************************************************************
*
*       _Panic()
*
*  Function description
*    Error/panic collection routine.
*
*  Additional information
*    This is an error routine that collects all cases that are dead
*    ends in the program flow. With blocking sockets we should never
*    end in here as the sample will handle the return values that are
*    returned by a blocking socket I/O.
*
*    When using this sample with non-blocking socket I/O and ending up
*    in here, this is an indicator that you are not handling all
*    non-blocking return values correctly. Typically the case missing
*    is to check for a WOULDBLOCK socket error when an API has
*    returned with SOCKET_ERROR (-1).
*/
static void _Panic(void) {
  IP_Logf_Application("Error, program halted!\n");
  for (;;);
}

/*********************************************************************
*
*       _WebSocketTask()
*/
static void _WebSocketTask(void) {
  char*                sKey;
  char*                s;
  IP_WEBSOCKET_CONTEXT WebSocketContext;
  struct sockaddr      Addr;
  struct sockaddr_in   InAddr;
  long hSockListen;
  long hSock;
  int  AddrLen;
  int  r;
  int  NumBytesRead;
  U8   MessageType;
  char ac[64];

  //
  // Get a socket into listening state.
  //
  hSockListen = socket(AF_INET, SOCK_STREAM, 0);
  if (hSockListen == SOCKET_ERROR) {
    while(1); // This should never happen!
  }
  WEBS_MEMSET(&InAddr, 0, sizeof(InAddr));
  InAddr.sin_family      = AF_INET;
  InAddr.sin_port        = htons(SERVER_PORT);
  InAddr.sin_addr.s_addr = INADDR_ANY;
  bind(hSockListen, (struct sockaddr *)&InAddr, sizeof(InAddr));
  listen(hSockListen, 1);
  do {
    //
    // Wait for an incoming connection
    //
    hSock = 0;
    AddrLen = sizeof(Addr);
    if ((hSock = accept(hSockListen, &Addr, &AddrLen)) == SOCKET_ERROR) {
      continue;      // Error.
    }

#if (APP_USE_SSL != 0)
    SSL_SESSION_Prepare(&_SSLSession, hSock, &_IP_Transport);
    //
    // Upgrade the connection to secure by negotiating a session using SSL.
    //
    r = SSL_SESSION_Accept(&_SSLSession);
    if (r < 0) {
      goto OnError;
    }
#endif

    //
    // Read HTTP header.
    //
#if (APP_USE_SSL != 0)
    r = SSL_SESSION_Receive(&_SSLSession, _acBuffer, sizeof(_acBuffer));
    //
    // Translate EOF into "connection closed",
    // the Websocket expects same return values as with recv().
    //
    if (r == SSL_ERROR_EOF) {
      r = 0;
    }
#else
    r = recv(hSock, _acBuffer, sizeof(_acBuffer), 0);
#endif
    if (r == sizeof(_acBuffer)) {
      IP_Logf_Application("_acBuffer is too small to read the complete HTTP header.\n");
      goto OnError;  // Error.
    }
    if (r <= 0) {
      goto OnError;  // Error.
    }
    _acBuffer[r] = '\0';  // Treat the HTTP header in buffer as one big string.
    //
    // Find Sec-WebSocket-Key field value in HTTP header.
    //
    sKey = strstr(&_acBuffer[0], "Sec-WebSocket-Key: ");
    if (sKey == NULL) {
      IP_Logf_Application("Sec-WebSocket-Key field not found.\n");
      goto OnError;  // Error.
    }
    sKey += sizeof("Sec-WebSocket-Key: ") - 1;
    s = strstr(sKey, "\r\n");
    if (s == NULL) {
      IP_Logf_Application("End of Sec-WebSocket-Key field not found.\n");
      goto OnError;  // Error.
    }
    //
    // Generate the accept response key.
    //
    r = IP_WEBSOCKET_GenerateAcceptKey(sKey, (unsigned)(s - sKey), &ac[0], sizeof(ac) - 1);
    if (r == 0) {
      IP_Logf_Application("Buffer for accept key is not big enough.\n");
      goto OnError;  // Error.
    }
    ac[r] = '\0';
    //
    // Create the accept message and send it to the client.
    //
    r = SEGGER_snprintf(_acBuffer, sizeof(_acBuffer), OPEN_RESPONSE, WEBSOCKET_PROTO, ac);
    if (r == sizeof(_acBuffer)) {
      IP_Logf_Application("_acBuffer is too small for the response.\n");
      goto OnError;  // Error.
    }
#if (APP_USE_SSL != 0)
  SSL_SESSION_Send(&_SSLSession, (const char*)_acBuffer, WEBS_STRLEN(_acBuffer));
#else
    send(hSock, (const char*)_acBuffer, WEBS_STRLEN(_acBuffer), 0);
#endif
    IP_Logf_Application("WebSocket client connected.\n");
    //
    // Initialize the WebSocket context for the server.
    //
    IP_WEBSOCKET_InitServer(&WebSocketContext, &_WebSocketTransportAPI, (IP_WEBSOCKET_CONNECTION*)hSock);
    //
    // Process WebSocket messages.
    //

#if (APP_USE_SSL != 0)
    //
    // add SSL Session to Connection Context
    //
    WebSocketContext.pConnection = (IP_WEBSOCKET_CONNECTION*)&_SSLSession;
#endif

    while (1) {
      //
      // Wait/read the next message type.
      //
      do {
        r = IP_WEBSOCKET_WaitForNextMessage(&WebSocketContext, &MessageType);
        if (r == 0) {  // Connection closed.
          goto OnDisconnect;
        }
        if ((r < 0) && (r != IP_WEBSOCKET_ERR_AGAIN)) {
          _Panic();
        }
      } while (r == IP_WEBSOCKET_ERR_AGAIN);
      //
      // Evaluate the message type.
      //
      if (MessageType == IP_WEBSOCKET_FRAME_TYPE_CLOSE) {
        //
        // Send a close frame with a goodbye message.
        //
        r = SEGGER_snprintf(ac, sizeof(ac), "Bye, bye");
        if (r == sizeof(ac)) {
          IP_Logf_Application("ac buffer is too small for close payload.\n");
          goto OnError;  // Error.
        }
        do {
          //
          // In this special case do not check if this is an error
          // other than AGAIN as WebSockets are explicitly allowed
          // to be closed on socket level. The next call might return
          // with a real error as the other side has already closed
          // the socket, this is fine for this single case. Simply
          // close the socket in this case.
          //
          r = IP_WEBSOCKET_Close(&WebSocketContext, &ac[0], IP_WEBSOCKET_CLOSE_CODE_NORMAL_CLOSURE);
        } while (r == IP_WEBSOCKET_ERR_AGAIN);
        break;
      } else {
        if (MessageType == IP_WEBSOCKET_FRAME_TYPE_TEXT) {
          //
          // Receive a text message and printf it.
          //
          NumBytesRead = 0;
          s            = &ac[0];
          for (;;) {
            r = IP_WEBSOCKET_Recv(&WebSocketContext, (void*)s, (sizeof(ac) - 1 - NumBytesRead));
            if        (r > 0) {
              s            += r;
              NumBytesRead += r;
            } else if (r == IP_WEBSOCKET_ERR_ALL_DATA_READ) {
              *s = '\0';
              IP_Logf_Application("Client: %s\n", ac);
              break;
            } else if (r == 0) {
              goto OnDisconnect;
            } else {  // r < 0 ?
              if (r != IP_WEBSOCKET_ERR_AGAIN) {
                _Panic();  // Error, any other unhandled return value that should not occur.
              }
            }
          }
        } else {
          //
          // Discard other (binary) messages.
          //
          do {
            r = IP_WEBSOCKET_DiscardMessage(&WebSocketContext);
            if (r == 0) {
              goto OnDisconnect;
            }
            if ((r < 0) && (r != IP_WEBSOCKET_ERR_AGAIN)) {
              _Panic();
            }
          } while (r == IP_WEBSOCKET_ERR_AGAIN);
        }
      }
    }
OnDisconnect:
    IP_Logf_Application("WebSocket client disconnected.\n");
OnError:
    closesocket(hSock);
  }  while(1);
}

/*********************************************************************
*
*       APP_MainTask()
*
*  Function description
*    Sample starting point.
*/
static void APP_MainTask(void) {
  OS_SetPriority(OS_GetTaskID(), TASK_PRIO_IP_WEBSOCKET);
  OS_SetTaskName(OS_GetTaskID(), "WebSocketServer");
#if ((APP_USE_SSL != 0))
  //
  // Initialize SSL.
  //
  SSL_Init();
#endif
  _WebSocketTask();
  OS_TASK_Terminate(NULL);
}

/*********************************************************************
*
*       Global functions
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask
*/
void MainTask(void) {
  IP_Init();
  _IFaceId = IP_INFO_GetNumInterfaces() - 1;                                           // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
  //
  // Start TCP/IP task
  //
  OS_CREATETASK(&_IPTCB,   "IP_Task",   IP_Task,   TASK_PRIO_IP_TASK,    _IPStack);
#if USE_RX_TASK
  OS_CREATETASK(&_IPRxTCB, "IP_RxTask", IP_RxTask, TASK_PRIO_IP_RX_TASK, _IPRxStack);  // Start the IP_RxTask, optional.
#endif
  IP_Connect(_IFaceId);                                                                // Connect the interface if necessary.
  //
  // IPv4 address configured ?
  //
  while (IP_IFaceIsReadyEx(_IFaceId) == 0) {
    BSP_ToggleLED(0);
    OS_Delay(200);
  }
  BSP_ClrLED(0);
  //
  // Start the sample itself.
  //
  OS_CREATETASK(&APP_MainTCB, "APP_MainTask", APP_MainTask, TASK_PRIO_IP_TASK - 1, APP_MainStack);
  OS_TASK_Terminate(NULL);
}

/*************************** End of file ****************************/