Webserver+3d print

Dependents:   Nucleo

cyclone_tcp/web_socket/web_socket.c

Committer:
Sergunb
Date:
2017-02-04
Revision:
0:8918a71cdbe9

File content as of revision 0:8918a71cdbe9:

/**
 * @file web_socket.c
 * @brief WebSocket API (client and server)
 *
 * @section License
 *
 * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved.
 *
 * This file is part of CycloneTCP Open.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @author Oryx Embedded SARL (www.oryx-embedded.com)
 * @version 1.7.6
 **/

//Switch to the appropriate trace level
#define TRACE_LEVEL WEB_SOCKET_TRACE_LEVEL

//Dependencies
#include <stdlib.h>
#include "core/net.h"
#include "web_socket/web_socket.h"
#include "web_socket/web_socket_auth.h"
#include "web_socket/web_socket_frame.h"
#include "web_socket/web_socket_transport.h"
#include "web_socket/web_socket_misc.h"
#include "str.h"
#include "base64.h"
#include "debug.h"

//Check TCP/IP stack configuration
#if (WEB_SOCKET_SUPPORT == ENABLED)

//WebSocket table
WebSocket webSocketTable[WEB_SOCKET_MAX_COUNT];
//Random data generation callback function
WebSocketRandCallback webSockRandCallback;


/**
 * @brief WebSocket related initialization
 * @return Error code
 **/

error_t webSocketInit(void)
{
   //Initialize WebSockets
   memset(webSocketTable, 0, sizeof(webSocketTable));

   //Successful initialization
   return NO_ERROR;
}


/**
 * @brief Register RNG callback function
 * @param[in] callback RNG callback function
 * @return Error code
 **/

error_t webSocketRegisterRandCallback(WebSocketRandCallback callback)
{
   //Check parameter
   if(callback == NULL)
      return ERROR_INVALID_PARAMETER;

   //Save callback function
   webSockRandCallback = callback;

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Create a WebSocket
 * @return Handle referencing the new WebSocket
 **/

WebSocket *webSocketOpen(void)
{
   uint_t i;
   WebSocket *webSocket;

   //Initialize WebSocket handle
   webSocket = NULL;

   //Get exclusive access
   osAcquireMutex(&netMutex);

   //Loop through WebSocket descriptors
   for(i = 0; i < WEB_SOCKET_MAX_COUNT; i++)
   {
      //Unused WebSocket found?
      if(webSocketTable[i].state == WS_STATE_UNUSED)
      {
         //Save socket handle
         webSocket = &webSocketTable[i];

         //Clear associated structure
         memset(webSocket, 0, sizeof(WebSocket));
         //Set the default timeout to be used
         webSocket->timeout = INFINITE_DELAY;
         //Enter the CLOSED state
         webSocket->state = WS_STATE_CLOSED;

         //We are done
         break;
      }
   }

   //Release exclusive access
   osReleaseMutex(&netMutex);

   //Return a handle to the freshly created WebSocket
   return webSocket;
}


/**
 * @brief Upgrade a socket to a WebSocket
 * @param[in] socket Handle referencing the socket
 * @return Handle referencing the new WebSocket
 **/

WebSocket *webSocketUpgradeSocket(Socket *socket)
{
   WebSocket *webSocket;

   //Valid socket handle?
   if(socket != NULL)
   {
      //Create a new WebSocket
      webSocket = webSocketOpen();

      //WebSocket successfully created?
      if(webSocket != NULL)
      {
         //Attach the socket handle
         webSocket->socket = socket;
         //Initialize state
         webSocket->state = WS_STATE_INIT;
      }
   }
   else
   {
      //The specified socket is not valid...
      webSocket = NULL;
   }

   //Return a handle to the freshly created WebSocket
   return webSocket;
}


#if (WEB_SOCKET_TLS_SUPPORT == ENABLED)

/**
 * @brief Upgrade a secure socket to a secure WebSocket
 * @param[in] socket Handle referencing the socket
 * @param[in] tlsContext Pointer to the SSL/TLS context
 * @return Handle referencing the new WebSocket
 **/

WebSocket *webSocketUpgradeSecureSocket(Socket *socket, TlsContext *tlsContext)
{
   WebSocket *webSocket;

   //Valid SSL/TLS context?
   if(tlsContext != NULL)
   {
      //Create a new WebSocket
      webSocket = webSocketOpen();

      //WebSocket successfully created?
      if(webSocket != NULL)
      {
         //Attach the socket handle
         webSocket->socket = socket;
         //Attach the SSL/TLS context
         webSocket->tlsContext = tlsContext;
         //Initialize state
         webSocket->state = WS_STATE_INIT;
      }
   }
   else
   {
      //The specified socket is not valid...
      webSocket = NULL;
   }

   //Return a handle to the freshly created WebSocket
   return webSocket;
}


/**
 * @brief Register TLS initialization callback function
 * @param[in] webSocket Handle that identifies a WebSocket
 * @param[in] callback TLS initialization callback function
 * @return Error code
 **/

error_t webSocketRegisterTlsInitCallback(WebSocket *webSocket,
   WebSocketTlsInitCallback callback)
{
   //Check parameters
   if(webSocket == NULL || callback == NULL)
      return ERROR_INVALID_PARAMETER;

   //Save callback function
   webSocket->tlsInitCallback = callback;

   //Successful processing
   return NO_ERROR;
}

#endif


/**
 * @brief Set timeout value for blocking operations
 * @param[in] webSocket Handle to a WebSocket
 * @param[in] timeout Maximum time to wait
 * @return Error code
 **/

error_t webSocketSetTimeout(WebSocket *webSocket, systime_t timeout)
{
   //Make sure the WebSocket handle is valid
   if(webSocket == NULL)
      return ERROR_INVALID_PARAMETER;

   //Save timeout value
   webSocket->timeout = timeout;

   //Valid socket?
   if(webSocket->socket != NULL)
      socketSetTimeout(webSocket->socket, timeout);

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Set the hostname of the resource being requested
 * @param[in] webSocket Handle to a WebSocket
 * @param[in] host NULL-terminated string containing the hostname
 * @return Error code
 **/

error_t webSocketSetHost(WebSocket *webSocket, const char_t *host)
{
   //Check parameters
   if(webSocket == NULL || host == NULL)
      return ERROR_INVALID_PARAMETER;

   //Save the hostname
   strSafeCopy(webSocket->host, host, WEB_SOCKET_HOST_MAX_LEN);

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Set the origin header field
 * @param[in] webSocket Handle to a WebSocket
 * @param[in] origin NULL-terminated string containing the origin
 * @return Error code
 **/

error_t webSocketSetOrigin(WebSocket *webSocket, const char_t *origin)
{
   //Check parameters
   if(webSocket == NULL || origin == NULL)
      return ERROR_INVALID_PARAMETER;

   //Save origin
   strSafeCopy(webSocket->origin, origin, WEB_SOCKET_ORIGIN_MAX_LEN);

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Set the sub-protocol header field
 * @param[in] webSocket Handle to a WebSocket
 * @param[in] subProtocol NULL-terminated string containing the sub-protocol
 * @return Error code
 **/

error_t webSocketSetSubProtocol(WebSocket *webSocket, const char_t *subProtocol)
{
   //Check parameters
   if(webSocket == NULL || subProtocol == NULL)
      return ERROR_INVALID_PARAMETER;

   //Save sub-protocol
   strSafeCopy(webSocket->subProtocol, subProtocol, WEB_SOCKET_SUB_PROTOCOL_MAX_LEN);

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Set authentication information
 * @param[in] webSocket Handle to a WebSocket
 * @param[in] username NULL-terminated string containing the user name to be used
 * @param[in] password NULL-terminated string containing the password to be used
 * @param[in] allowedAuthModes Logic OR of allowed HTTP authentication modes
 * @return Error code
 **/

error_t webSocketSetAuthInfo(WebSocket *webSocket, const char_t *username,
   const char_t *password, uint_t allowedAuthModes)
{
#if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
   WebSocketAuthContext *authContext;

   //Check parameters
   if(webSocket == NULL || username == NULL || password == NULL)
      return ERROR_INVALID_PARAMETER;

   //Point to the authentication context
   authContext = &webSocket->authContext;

   //Save user name
   strSafeCopy(authContext->username, username, WEB_SOCKET_USERNAME_MAX_LEN);
   //Save password
   strSafeCopy(authContext->password, password, WEB_SOCKET_PASSWORD_MAX_LEN);
   //Save the list of allowed HTTP authentication modes
   authContext->allowedAuthModes = allowedAuthModes;
#endif

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Bind the WebSocket to a particular network interface
 * @param[in] webSocket Handle to a WebSocket
 * @param[in] interface Network interface to be used
 * @return Error code
 **/

error_t webSocketBindToInterface(WebSocket *webSocket, NetInterface *interface)
{
   //Make sure the WebSocket handle is valid
   if(webSocket == NULL)
      return ERROR_INVALID_PARAMETER;

   //Explicitly associate the WebSocket with the specified interface
   webSocket->interface = interface;

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Establish a WebSocket connection
 * @param[in] webSocket Handle to a WebSocket
 * @param[in] serverIpAddr IP address of the WebSocket server to connect to
 * @param[in] serverPort TCP port number that will be used to establish the
 *   connection
 * @param[in] uri NULL-terminated string that contains the resource name
 * @return Error code
 **/

error_t webSocketConnect(WebSocket *webSocket, const IpAddr *serverIpAddr,
   uint16_t serverPort, const char_t *uri)
{
   error_t error;
   size_t n;
   WebSocketFrameContext *txContext;

   //Make sure the WebSocket handle is valid
   if(webSocket == NULL)
      return ERROR_INVALID_PARAMETER;

   //Point to the TX context
   txContext = &webSocket->txContext;

   //Initialize status code
   error = NO_ERROR;

   //Establish connection
   while(webSocket->state != WS_STATE_OPEN)
   {
      //Check current state
      if(webSocket->state == WS_STATE_CLOSED)
      {
         //Check parameters
         if(serverIpAddr == NULL || uri == NULL)
         {
            //Report an error
            error = ERROR_INVALID_PARAMETER;
         }
         else
         {
            //A WebSocket client is a WebSocket endpoint that initiates a
            //connection to a peer
            webSocket->endpoint = WS_ENDPOINT_CLIENT;

            //Save the URI
            strSafeCopy(webSocket->uri, uri, WEB_SOCKET_URI_MAX_LEN);
            //Reset retry counter
            webSocket->retryCount = 0;

#if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
            //HTTP authentication is not used for the first connection attempt
            webSocket->authContext.requiredAuthMode = WS_AUTH_MODE_NONE;
            webSocket->authContext.selectedAuthMode = WS_AUTH_MODE_NONE;
#endif
            //Initialize the WebSocket connection
            webSocketChangeState(webSocket, WS_STATE_INIT);
         }
      }
      else if(webSocket->state == WS_STATE_INIT)
      {
         //Increment retry counter
         webSocket->retryCount++;

         //Limit the number of connection attempts
         if(webSocket->retryCount > WEB_SOCKET_MAX_CONN_RETRIES)
         {
            //Report an error
            error = ERROR_OPEN_FAILED;
         }
         else
         {
            //Open network connection
            error = webSocketOpenConnection(webSocket);
         }

         //Check status code
         if(!error)
         {
            //Establish connection
            webSocketChangeState(webSocket, WS_STATE_CONNECTING);
         }
      }
      else if(webSocket->state == WS_STATE_CONNECTING)
      {
         //Establish connection
         error = webSocketEstablishConnection(webSocket,
            serverIpAddr, serverPort);

         //Check status code
         if(!error)
         {
            //Generate client's key
            error = webSocketGenerateClientKey(webSocket);
         }

         //Check status code
         if(!error)
         {
            //Format client handshake
            error = webSocketFormatClientHandshake(webSocket, serverPort);
         }

         //Check status code
         if(!error)
         {
            //Send client handshake
            webSocketChangeState(webSocket, WS_STATE_CLIENT_HANDSHAKE);
         }
      }
      else if(webSocket->state == WS_STATE_CLIENT_HANDSHAKE)
      {
         //Any remaining data to be sent?
         if(txContext->bufferPos < txContext->bufferLen)
         {
            //Send more data
            error = webSocketSendData(webSocket,
               txContext->buffer + txContext->bufferPos,
               txContext->bufferLen - txContext->bufferPos, &n, 0);

            //Advance data pointer
            txContext->bufferPos += n;
         }
         else
         {
            //Wait for server handshake
            webSocketChangeState(webSocket, WS_STATE_SERVER_HANDSHAKE);
         }
      }
      else if(webSocket->state == WS_STATE_SERVER_HANDSHAKE)
      {
         //Parse the server handshake
         error = webSocketParseHandshake(webSocket);
      }
      else if(webSocket->state == WS_STATE_SERVER_RESP_BODY)
      {
         //Check Connection header field
         if(webSocket->handshakeContext.connectionClose)
         {
            //Close connection
            webSocketCloseConnection(webSocket);
            //Try to connect again
            webSocketChangeState(webSocket, WS_STATE_INIT);
         }
         else
         {
            //Any remaining data to read in the response body?
            if(webSocket->handshakeContext.contentLength > 0)
            {
               //Limit the number of bytes to read at a time
               n = MIN(webSocket->handshakeContext.contentLength, WEB_SOCKET_BUFFER_SIZE);
               //Discard any received data
               error = webSocketReceiveData(webSocket, txContext->buffer, n, &n, 0);
               //Decrement byte counter
               webSocket->handshakeContext.contentLength -= n;
            }
            else
            {
               //Format client handshake
               error = webSocketFormatClientHandshake(webSocket, serverPort);
               //Try to authenticate again
               webSocketChangeState(webSocket, WS_STATE_CLIENT_HANDSHAKE);
            }
         }
      }
      else
      {
         //Invalid state
         error = ERROR_WRONG_STATE;
      }

      //Check whether authentication is required
      if(error == ERROR_AUTH_REQUIRED)
      {
#if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED)
         //Basic authentication?
         if(webSocket->authContext.requiredAuthMode == WS_AUTH_MODE_BASIC)
         {
            //Check whether the basic authentication scheme is allowed
            if(webSocket->authContext.allowedAuthModes & WS_AUTH_MODE_BASIC)
            {
               //Do not try to connect again if the credentials are not valid...
               if(webSocket->authContext.selectedAuthMode == WS_AUTH_MODE_NONE)
               {
                  //Catch exception
                  error = NO_ERROR;
                  //Force the WebSocket client to use basic authentication
                  webSocket->authContext.selectedAuthMode = WS_AUTH_MODE_BASIC;
                  //Read response body, if any
                  webSocketChangeState(webSocket, WS_STATE_SERVER_RESP_BODY);
               }
            }
         }
#endif
#if (WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
         //Digest authentication?
         if(webSocket->authContext.requiredAuthMode == WS_AUTH_MODE_DIGEST)
         {
            //Check whether the digest authentication scheme is allowed
            if(webSocket->authContext.allowedAuthModes & WS_AUTH_MODE_DIGEST)
            {
               //Do not try to connect again if the credentials are not valid...
               if(webSocket->authContext.selectedAuthMode == WS_AUTH_MODE_NONE)
               {
                  //Force the WebSocket client to use digest authentication
                  webSocket->authContext.selectedAuthMode = WS_AUTH_MODE_DIGEST;

                  //Make sure that the RNG callback function has been registered
                  if(webSockRandCallback != NULL)
                  {
                     //Generate a random cnonce
                     error = webSockRandCallback(txContext->buffer,
                        WEB_SOCKET_CNONCE_SIZE);
                  }
                  else
                  {
                     //A cryptographically strong random number generator
                     //must be used to generate the cnonce
                     error = ERROR_PRNG_NOT_READY;
                  }

                  //Convert the byte array to hex string
                  webSocketConvertArrayToHexString(txContext->buffer,
                     WEB_SOCKET_CNONCE_SIZE, webSocket->authContext.cnonce);

                  //Read response body, if any
                  webSocketChangeState(webSocket, WS_STATE_SERVER_RESP_BODY);
               }
            }
         }
#endif
      }

      //If an error occurred, then the client must fail the WebSocket
      //connection
      if(error)
      {
#if (NET_RTOS_SUPPORT == DISABLED)
         //Timeout error?
         if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
            break;
#endif
         //Close connection
         webSocketCloseConnection(webSocket);

         //Switch to the CLOSED state
         webSocketChangeState(webSocket, WS_STATE_CLOSED);
         //Exit immediately
         break;
      }
   }

   //Return status code
   return error;
}


/**
 * @brief Set client's key
 * @param[in] webSocket Handle to a WebSocket
 * @param[in] clientKey NULL-terminated string that holds the the client's key
 * @return Error code
 **/

error_t webSocketSetClientKey(WebSocket *webSocket, const char_t *clientKey)
{
   error_t error;
   size_t n;

   //Check parameters
   if(webSocket == NULL || clientKey == NULL)
      return ERROR_INVALID_PARAMETER;

   //Get the length of the client's key
   n = strlen(clientKey);

   //Check the length of the key
   if(n > WEB_SOCKET_CLIENT_KEY_SIZE)
      return ERROR_INVALID_LENGTH;

   //Copy client's key
   strcpy(webSocket->handshakeContext.clientKey, clientKey);

   //a WebSocket server is a WebSocket endpoint that awaits
   //connections from peers
   webSocket->endpoint = WS_ENDPOINT_SERVER;

   //Initialize status code
   webSocket->statusCode = WS_STATUS_CODE_NO_STATUS_RCVD;

   //Initialize handshake parameters
   webSocket->handshakeContext.version = WS_HTTP_VERSION_1_1;
   webSocket->handshakeContext.connectionUpgrade = TRUE;
   webSocket->handshakeContext.upgradeWebSocket = TRUE;

   //Initialize FIN flag
   webSocket->rxContext.fin = TRUE;

   //Verify client's key
   error = webSocketVerifyClientKey(webSocket);
   //Any error to report?
   if(error)
      return error;

   //Generate server's key
   error = webSocketGenerateServerKey(webSocket);
   //Any error to report?
   if(error)
      return error;

   //Format server handshake
   error = webSocketFormatServerHandshake(webSocket);
   //Any error to report?
   if(error)
      return error;

   //Update FSM state
   webSocket->state = WS_STATE_SERVER_HANDSHAKE;

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Parse client's handshake
 * @param[in] webSocket Handle that identifies a WebSocket
 * @return Error code
 **/

error_t webSocketParseClientHandshake(WebSocket *webSocket)
{
   error_t error;

   //Make sure the WebSocket handle is valid
   if(webSocket == NULL)
      return ERROR_INVALID_PARAMETER;

   //a WebSocket server is a WebSocket endpoint that awaits
   //connections from peers
   webSocket->endpoint = WS_ENDPOINT_SERVER;

   //Initialize status code
   error = NO_ERROR;

   //Establish connection
   while(webSocket->state != WS_STATE_SERVER_HANDSHAKE)
   {
      //Check current state
      if(webSocket->state == WS_STATE_INIT)
      {
         //Open network connection
         error = webSocketOpenConnection(webSocket);

         //Check status code
         if(!error)
         {
            //Establish connection
            webSocketChangeState(webSocket, WS_STATE_CONNECTING);
         }
      }
      else if(webSocket->state == WS_STATE_CONNECTING)
      {
#if (WEB_SOCKET_TLS_SUPPORT == ENABLED)
         //Use SSL/TLS to secure the connection?
         if(webSocket->tlsInitCallback != NULL)
         {
            //Establish a SSL/TLS connection
            error = tlsConnect(webSocket->tlsContext);
         }
#endif
         //Check status code
         if(!error)
         {
            //Parse client handshake
            webSocketChangeState(webSocket, WS_STATE_CLIENT_HANDSHAKE);
         }
      }
      else if(webSocket->state == WS_STATE_CLIENT_HANDSHAKE)
      {
         //Parse the client handshake
         error = webSocketParseHandshake(webSocket);

         //Check status code
         if(!error)
         {
            //Generate server's key
            error = webSocketGenerateServerKey(webSocket);
         }

         //Check status code
         if(!error)
         {
            //Format server handshake
            error = webSocketFormatServerHandshake(webSocket);
         }
      }
      else
      {
         //Invalid state
         error = ERROR_WRONG_STATE;
      }

      //Any error to report?
      if(error)
         break;
   }

   //Return status code
   return error;
}


/**
 * @brief Send server's handshake
 * @param[in] webSocket Handle that identifies a WebSocket
 * @return Error code
 **/

error_t webSocketSendServerHandshake(WebSocket *webSocket)
{
   error_t error;
   size_t n;
   WebSocketFrameContext *txContext;

   //Make sure the WebSocket handle is valid
   if(webSocket == NULL)
      return ERROR_INVALID_PARAMETER;

   //Point to the TX context
   txContext = &webSocket->txContext;

   //Initialize status code
   error = NO_ERROR;

   //Establish connection
   while(webSocket->state != WS_STATE_OPEN)
   {
      //Check current state
      if(webSocket->state == WS_STATE_SERVER_HANDSHAKE)
      {
         //Any remaining data to be sent?
         if(txContext->bufferPos < txContext->bufferLen)
         {
            //Send more data
            error = webSocketSendData(webSocket,
               txContext->buffer + txContext->bufferPos,
               txContext->bufferLen - txContext->bufferPos, &n, 0);

            //Advance data pointer
            txContext->bufferPos += n;
         }
         else
         {
            //The WebSocket connection is established
            webSocketChangeState(webSocket, WS_STATE_OPEN);
         }
      }
      else
      {
         //Invalid state
         error = ERROR_WRONG_STATE;
      }

      //Any error to report?
      if(error)
         break;
   }

   //Return status code
   return error;
}


/**
 * @brief Send HTTP error response to the client
 * @param[in] webSocket Handle that identifies a WebSocket
 * @param[in] statusCode HTTP status code
 * @param[in] message Text message
 * @return Error code
 **/

error_t webSocketSendErrorResponse(WebSocket *webSocket,
   uint_t statusCode, const char_t *message)
{
   error_t error;
   size_t n;
   WebSocketFrameContext *txContext;

   //Make sure the WebSocket handle is valid
   if(webSocket == NULL)
      return ERROR_INVALID_PARAMETER;

   //Point to the TX context
   txContext = &webSocket->txContext;

   //Initialize status code
   error = NO_ERROR;

   //Send HTTP error message
   while(1)
   {
      //Check current state
      if(txContext->state == WS_SUB_STATE_INIT)
      {
         //Format HTTP error response
         error = webSocketFormatErrorResponse(webSocket, statusCode, message);

         //Send the response
         txContext->state = WS_SUB_STATE_FRAME_PAYLOAD;
      }
      else if(txContext->state == WS_SUB_STATE_FRAME_PAYLOAD)
      {
         //Any remaining data to be sent?
         if(txContext->bufferPos < txContext->bufferLen)
         {
            //Send more data
            error = webSocketSendData(webSocket,
               txContext->buffer + txContext->bufferPos,
               txContext->bufferLen - txContext->bufferPos, &n, 0);

            //Advance data pointer
            txContext->bufferPos += n;
         }
         else
         {
            //We are done
            webSocketChangeState(webSocket, WS_STATE_SHUTDOWN);
            break;
         }
      }
      else
      {
         //Invalid state
         error = ERROR_WRONG_STATE;
      }

      //Any error to report?
      if(error)
         break;
   }

   //Return status code
   return error;
}


/**
 * @brief Transmit data over the WebSocket connection
 * @param[in] webSocket Handle that identifies a WebSocket
 * @param[in] data Pointer to a buffer containing the data to be transmitted
 * @param[in] length Number of data bytes to send
 * @param[in] type Frame type
 * @param[out] written Actual number of bytes written (optional parameter)
 * @return Error code
 **/

error_t webSocketSend(WebSocket *webSocket, const void *data,
   size_t length, WebSocketFrameType type, size_t *written)
{
   //An unfragmented message consists of a single frame with the FIN bit
   //set and an opcode other than 0
   return webSocketSendEx(webSocket, data, length,
      type, written, TRUE, TRUE);
}


/**
 * @brief Transmit data over the WebSocket connection
 * @param[in] webSocket Handle that identifies a WebSocket
 * @param[in] data Pointer to a buffer containing the data to be transmitted
 * @param[in] length Number of data bytes to send
 * @param[in] type Frame type
 * @param[out] written Actual number of bytes written (optional parameter)
 * @param[in] firstFrag First fragment of the message
 * @param[in] lastFrag Last fragment of the message
 **/

error_t webSocketSendEx(WebSocket *webSocket, const void *data, size_t length,
   WebSocketFrameType type, size_t *written, bool_t firstFrag, bool_t lastFrag)
{
   error_t error;
   size_t i;
   size_t j;
   size_t k;
   size_t n;
   const uint8_t *p;
   WebSocketFrameContext *txContext;

   //Check parameters
   if(webSocket == NULL || data == NULL)
      return ERROR_INVALID_PARAMETER;

   //A data frame may be transmitted by either the client or the server at
   //any time after opening handshake completion and before that endpoint
   //has sent a Close frame
   if(webSocket->state != WS_STATE_OPEN)
      return ERROR_NOT_CONNECTED;

   //Point to the TX context
   txContext = &webSocket->txContext;

   //Initialize status code
   error = NO_ERROR;

   //Point to the application data to be written
   p = (const uint8_t *) data;
   //No data has been transmitted yet
   i = 0;

   //Send as much data as possible
   while(1)
   {
      //Check current sub-state
      if(txContext->state == WS_SUB_STATE_INIT)
      {
         //A fragmented message consists of a single frame with the FIN bit
         //clear and an opcode other than 0, followed by zero or more frames
         //with the FIN bit clear and the opcode set to 0, and terminated by
         //a single frame with the FIN bit set and an opcode of 0
         if(!firstFrag)
            type = WS_FRAME_TYPE_CONTINUATION;

         //Format WebSocket frame header
         error = webSocketFormatFrameHeader(webSocket, lastFrag, type, length - i);

         //Send the frame header
         txContext->state = WS_SUB_STATE_FRAME_HEADER;
      }
      else if(txContext->state == WS_SUB_STATE_FRAME_HEADER)
      {
         //Any remaining data to be sent?
         if(txContext->bufferPos < txContext->bufferLen)
         {
            //Send more data
            error = webSocketSendData(webSocket,
               txContext->buffer + txContext->bufferPos,
               txContext->bufferLen - txContext->bufferPos, &n, 0);

            //Advance data pointer
            txContext->bufferPos += n;
         }
         else
         {
            //Flush the transmit buffer
            txContext->payloadPos = 0;
            txContext->bufferPos = 0;
            txContext->bufferLen = 0;

            //Send the payload of the WebSocket frame
            txContext->state = WS_SUB_STATE_FRAME_PAYLOAD;
         }
      }
      else if(txContext->state == WS_SUB_STATE_FRAME_PAYLOAD)
      {
         //Any remaining data to be sent?
         if(txContext->bufferPos < txContext->bufferLen)
         {
            //Send more data
            error = webSocketSendData(webSocket,
               txContext->buffer + txContext->bufferPos,
               txContext->bufferLen - txContext->bufferPos, &n, 0);

            //Advance data pointer
            txContext->payloadPos += n;
            txContext->bufferPos += n;

            //Total number of data that have been written
            i += n;
         }
         else
         {
            //Send as much data as possible
            if(txContext->payloadPos < txContext->payloadLen)
            {
               //Calculate the number of bytes that are pending
               n = MIN(length - i, txContext->payloadLen - txContext->payloadPos);
               //Limit the number of bytes to be copied at a time
               n = MIN(n, WEB_SOCKET_BUFFER_SIZE);

               //Copy application data to the transmit buffer
               memcpy(txContext->buffer, p, n);

               //All frames sent from the client to the server are masked
               if(webSocket->endpoint == WS_ENDPOINT_CLIENT)
               {
                  //Apply masking
                  for(j = 0; j < n; j++)
                  {
                     //Index of the masking key to be applied
                     k = (txContext->payloadPos + j) % 4;
                     //Convert unmasked data into masked data
                     txContext->buffer[j] = p[i + j] ^ txContext->maskingKey[k];
                  }
               }

               //Rewind to the beginning of the buffer
               txContext->bufferPos = 0;
               //Update the number of data buffered but not yet sent
               txContext->bufferLen = n;
            }
            else
            {
               //Prepare to send a new WebSocket frame
               txContext->state = WS_SUB_STATE_INIT;

               //Write operation complete?
               if(i >= length)
                  break;
            }
         }
      }
      else
      {
         //Invalid state
         error = ERROR_WRONG_STATE;
      }

      //Any error to report?
      if(error)
         break;
   }

   //Total number of data that have been written
   if(written != NULL)
      *written = i;

   //Return status code
   return error;
}


/**
 * @brief Receive data from a WebSocket connection
 * @param[in] webSocket Handle that identifies a WebSocket
 * @param[out] data Buffer where to store the incoming data
 * @param[in] size Maximum number of bytes that can be received
 * @param[out] type Frame type
 * @param[out] received Number of bytes that have been received
 * @return Error code
 **/

error_t webSocketReceive(WebSocket *webSocket, void *data,
   size_t size, WebSocketFrameType *type, size_t *received)
{
   bool_t firstFrag;
   bool_t lastFrag;

   return webSocketReceiveEx(webSocket, data, size,
      type, received, &firstFrag, &lastFrag);
}


/**
 * @brief Receive data from a WebSocket connection
 * @param[in] webSocket Handle that identifies a WebSocket
 * @param[out] data Buffer where to store the incoming data
 * @param[in] size Maximum number of bytes that can be received
 * @param[out] type Frame type
 * @param[out] received Number of bytes that have been received
 * @param[out] firstFrag First fragment of the message
 * @param[out] lastFrag Last fragment of the message
 * @return Error code
 **/

error_t webSocketReceiveEx(WebSocket *webSocket, void *data, size_t size,
   WebSocketFrameType *type, size_t *received, bool_t *firstFrag, bool_t *lastFrag)
{
   error_t error;
   size_t i;
   size_t j;
   size_t k;
   size_t n;
   WebSocketFrame *frame;
   WebSocketFrameContext *rxContext;

   //Make sure the WebSocket handle is valid
   if(webSocket == NULL)
      return ERROR_INVALID_PARAMETER;

   //Check the state of the WebSocket connection
   if(webSocket->state != WS_STATE_OPEN &&
      webSocket->state != WS_STATE_CLOSING_RX)
      return ERROR_NOT_CONNECTED;

   //Point to the RX context
   rxContext = &webSocket->rxContext;
   //Point to the WebSocket frame header
   frame = (WebSocketFrame *) rxContext->buffer;

   //Initialize status code
   error = NO_ERROR;

   //Initialize flags
   if(type != NULL)
      *type = WS_FRAME_TYPE_CONTINUATION;
   if(firstFrag != NULL)
      *firstFrag = FALSE;
   if(lastFrag != NULL)
      *lastFrag = FALSE;

   //No data has been read yet
   i = 0;

   //Read as much data as possible
   while(i < size)
   {
      //Check current sub-state
      if(rxContext->state == WS_SUB_STATE_INIT)
      {
         //Flush the receive buffer
         rxContext->bufferPos = 0;
         rxContext->bufferLen = sizeof(WebSocketFrame);

         //Decode the frame header
         rxContext->state = WS_SUB_STATE_FRAME_HEADER;
      }
      else if(rxContext->state == WS_SUB_STATE_FRAME_HEADER)
      {
         //Incomplete frame header?
         if(rxContext->bufferPos < rxContext->bufferLen)
         {
            //Read more data
            error = webSocketReceiveData(webSocket,
               rxContext->buffer + rxContext->bufferPos,
               rxContext->bufferLen - rxContext->bufferPos, &n, 0);

            //Advance data pointer
            rxContext->bufferPos += n;
         }
         else
         {
            //Check the Payload Length field
            if(frame->payloadLen == 126)
               rxContext->bufferLen += sizeof(uint16_t);
            else if(frame->payloadLen == 127)
               rxContext->bufferLen += sizeof(uint64_t);

            //Check whether the masking key is present
            if(frame->mask)
               rxContext->bufferLen += sizeof(uint32_t);

            //The Opcode field defines the interpretation of the payload data
            if(frame->opcode == WS_FRAME_TYPE_CLOSE)
            {
               //All control frames must have a payload length of 125 bytes or less
               if(frame->payloadLen <= 125)
               {
                  //Retrieve the length of the WebSocket frame
                  rxContext->bufferLen += frame->payloadLen;
               }
               else
               {
                  //Report a protocol error
                  webSocket->statusCode = WS_STATUS_CODE_PROTOCOL_ERROR;
                  //Terminate the WebSocket connection
                  error = ERROR_INVALID_FRAME;
               }
            }

            //Decode the extended payload length and the masking key, if any
            rxContext->state = WS_SUB_STATE_FRAME_EXT_HEADER;
         }
      }
      else if(rxContext->state == WS_SUB_STATE_FRAME_EXT_HEADER)
      {
         //Incomplete frame header?
         if(rxContext->bufferPos < rxContext->bufferLen)
         {
            //Read more data
            error = webSocketReceiveData(webSocket,
               rxContext->buffer + rxContext->bufferPos,
               rxContext->bufferLen - rxContext->bufferPos, &n, 0);

            //Advance data pointer
            rxContext->bufferPos += n;
         }
         else
         {
            //Parse the header of the WebSocket frame
            error = webSocketParseFrameHeader(webSocket, frame, type);

            //Check status code
            if(error == ERROR_UNEXPECTED_MESSAGE)
            {
               error = NO_ERROR;
               break;
            }
            else if(error == NO_ERROR)
            {
               if(firstFrag != NULL)
                  *firstFrag = TRUE;
            }

            //Flush the receive buffer
            rxContext->payloadPos = 0;
            rxContext->bufferPos = 0;
            rxContext->bufferLen = 0;

            //Decode the payload of the WebSocket frame
            rxContext->state = WS_SUB_STATE_FRAME_PAYLOAD;
         }
      }
      else if(rxContext->state == WS_SUB_STATE_FRAME_PAYLOAD)
      {
         if(rxContext->payloadPos < rxContext->payloadLen)
         {
            //Limit the number of bytes to read at a time
            n = MIN(size - i, rxContext->payloadLen - rxContext->payloadPos);
            //Limit the number of bytes to be copied at a time
            n = MIN(n, WEB_SOCKET_BUFFER_SIZE);

            //Read more data
            error = webSocketReceiveData(webSocket, rxContext->buffer, n, &n, 0);

            //All frames sent from the client to the server are masked
            if(rxContext->mask)
            {
               //Unmask the data
               for(j = 0; j < n; j++)
               {
                  //Index of the masking key to be applied
                  k = (rxContext->payloadPos + j) % 4;
                  //Convert masked data into unmasked data
                  rxContext->buffer[j] ^= rxContext->maskingKey[k];
               }
            }

            //Text frame?
            if(rxContext->dataFrameType == WS_FRAME_TYPE_TEXT &&
               rxContext->controlFrameType == WS_FRAME_TYPE_CONTINUATION)
            {
               //Compute the number of remaining data bytes in the UTF-8 stream
               if(rxContext->fin)
                  k = rxContext->payloadLen - rxContext->payloadPos;
               else
                  k = 0;

               //Invalid UTF-8 sequence?
               if(!webSocketCheckUtf8Stream(&webSocket->utf8Context,
                  rxContext->buffer, n, k))
               {
                  //The received data is not consistent with the type of the message
                  webSocket->statusCode = WS_STATUS_CODE_INVALID_PAYLOAD_DATA;
                  //The endpoint must fail the WebSocket connection
                  error = ERROR_INVALID_FRAME;
               }
            }

            //Sanity check
            if(data != NULL)
            {
               //Copy application data
               memcpy((uint8_t *) data + i, rxContext->buffer, n);
            }

            //Advance data pointer
            rxContext->payloadPos += n;

            //Total number of data that have been read
            i += n;
         }

         if(rxContext->payloadPos == rxContext->payloadLen)
         {
            //Decode the next WebSocket frame
            rxContext->state = WS_SUB_STATE_INIT;

            //Last fragment of the message?
            if(rxContext->fin || rxContext->controlFrameType != WS_FRAME_TYPE_CONTINUATION)
            {
               if(lastFrag != NULL)
                  *lastFrag = TRUE;

               //Exit immediately
               break;
            }
         }
      }
      else
      {
         //Invalid state
         error = ERROR_WRONG_STATE;
      }

      //Any error to report?
      if(error)
         break;
   }

   //Check status code
   if(!error)
   {
      //Return the frame type
      if(type != NULL)
      {
         //Control or data frame?
         if(rxContext->controlFrameType != WS_FRAME_TYPE_CONTINUATION)
            *type = rxContext->controlFrameType;
         else
            *type = rxContext->dataFrameType;
      }
   }

   //Return the total number of data that have been read
   if(received != NULL)
      *received = i;

   //Return status code
   return error;
}


/**
 * @brief Check whether some data is available in the receive buffer
 * @param[in] webSocket Handle to a WebSocket
 * @return The function returns TRUE if some data is pending and can be read
 *   immediately without blocking. Otherwise, FALSE is returned
 **/

bool_t webSocketIsRxReady(WebSocket *webSocket)
{
   bool_t available = FALSE;

#if (WEB_SOCKET_TLS_SUPPORT == ENABLED)
   //Check whether a secure connection is being used
   if(webSocket->tlsContext != NULL)
   {
      //Check whether some data is pending in the receive buffer
      if(webSocket->tlsContext->rxBufferLen > 0)
         available = TRUE;
   }
#endif

   //The function returns TRUE if some data can be read immediately
   //without blocking
   return available;
}


/**
 * @brief Gracefully close a WebSocket connection
 * @param[in] webSocket Handle to a WebSocket
 **/

error_t webSocketShutdown(WebSocket *webSocket)
{
   error_t error;
   size_t n;
   WebSocketFrameContext *txContext;

   //Make sure the WebSocket handle is valid
   if(webSocket == NULL)
      return ERROR_INVALID_PARAMETER;

   //Point to the TX context
   txContext = &webSocket->txContext;

   //Initialize status code
   error = NO_ERROR;

   //Closing handshake
   while(webSocket->state != WS_STATE_CLOSED)
   {
      //Check current state
      if(webSocket->state == WS_STATE_OPEN)
      {
         //Check whether the latest frame has been completely transmitted
         if(txContext->payloadPos == txContext->payloadLen)
         {
            //Format Close frame
            error = webSocketFormatCloseFrame(webSocket);
            //Send Close frame
            webSocket->state = WS_STATE_CLOSING_TX;
         }
         else
         {
            //The WebSocket connection cannot be closed until the
            //transmission of the frame is complete...
            error = ERROR_FAILURE;
         }
      }
      else if(webSocket->state == WS_STATE_CLOSING_TX)
      {
         //Any remaining data to be sent?
         if(txContext->bufferPos < txContext->bufferLen)
         {
            //Send more data
            error = webSocketSendData(webSocket,
               txContext->buffer + txContext->bufferPos,
               txContext->bufferLen - txContext->bufferPos, &n, 0);

            //Advance data pointer
            txContext->bufferPos += n;
         }
         else
         {
            //Check whether a Close frame has been received from the peer
            if(webSocket->handshakeContext.closingFrameReceived)
               webSocket->state = WS_STATE_SHUTDOWN;
            else
               webSocket->state = WS_STATE_CLOSING_RX;
         }
      }
      else if(webSocket->state == WS_STATE_CLOSING_RX)
      {
         //After receiving a control frame indicating the connection should
         //be closed, a peer discards any further data received
         error = webSocketReceive(webSocket, NULL, WEB_SOCKET_BUFFER_SIZE, NULL, 0);

         //Check status code
         if(error == NO_ERROR)
         {
            //Close frame received?
            if(webSocket->handshakeContext.closingFrameReceived)
            {
               //Properly shutdown the network connection
               webSocket->state = WS_STATE_SHUTDOWN;
            }
         }
         else if(error == ERROR_INVALID_FRAME || error == ERROR_END_OF_STREAM)
         {
            //Catch exception
            error = NO_ERROR;
            //Properly shutdown the network connection
            webSocket->state = WS_STATE_SHUTDOWN;
         }
      }
      else if(webSocket->state == WS_STATE_SHUTDOWN)
      {
         //Properly dispose the network connection
         error = webSocketShutdownConnection(webSocket);

         //Check status code
         if(!error)
         {
            //The connection has been properly closed
            webSocket->state = WS_STATE_CLOSED;
         }
      }
      else
      {
         //Invalid state
         error = ERROR_WRONG_STATE;
      }

      //Any error to report?
      if(error)
         break;
   }

   //Return status code
   return error;
}


/**
 * @brief Close a WebSocket connection
 * @param[in] webSocket Handle identifying the WebSocket to close
 **/

void webSocketClose(WebSocket *webSocket)
{
   //Make sure the WebSocket handle is valid
   if(webSocket != NULL)
   {
      //Close connection
      webSocketCloseConnection(webSocket);
      //Release the WebSocket
      webSocketChangeState(webSocket, WS_STATE_UNUSED);
   }
}

#endif