web_socket_misc.c
Go to the documentation of this file.
1 /**
2  * @file web_socket_misc.c
3  * @brief Helper functions for WebSockets
4  *
5  * @section License
6  *
7  * Copyright (C) 2010-2018 Oryx Embedded SARL. All rights reserved.
8  *
9  * This file is part of CycloneTCP Open.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  * @author Oryx Embedded SARL (www.oryx-embedded.com)
26  * @version 1.9.0
27  **/
28 
29 //Switch to the appropriate trace level
30 #define TRACE_LEVEL WEB_SOCKET_TRACE_LEVEL
31 
32 //Dependencies
33 #include <stdlib.h>
34 #include "core/net.h"
35 #include "web_socket/web_socket.h"
40 #include "encoding/base64.h"
41 #include "hash/sha1.h"
42 #include "str.h"
43 #include "debug.h"
44 
45 //Check TCP/IP stack configuration
46 #if (WEB_SOCKET_SUPPORT == ENABLED)
47 
48 //WebSocket GUID
49 const char_t webSocketGuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
50 
51 
52 /**
53  * @brief HTTP status codes
54  **/
55 
56 static const WebSocketStatusCodeDesc statusCodeList[] =
57 {
58  //Success
59  {200, "OK"},
60  {201, "Created"},
61  {202, "Accepted"},
62  {204, "No Content"},
63  //Redirection
64  {301, "Moved Permanently"},
65  {302, "Found"},
66  {304, "Not Modified"},
67  //Client error
68  {400, "Bad Request"},
69  {401, "Unauthorized"},
70  {403, "Forbidden"},
71  {404, "Not Found"},
72  //Server error
73  {500, "Internal Server Error"},
74  {501, "Not Implemented"},
75  {502, "Bad Gateway"},
76  {503, "Service Unavailable"}
77 };
78 
79 
80 /**
81  * @brief Update WebSocket state
82  * @param[in] webSocket Handle to a WebSocket
83  * @param[in] newState New state to switch to
84  **/
85 
86 void webSocketChangeState(WebSocket *webSocket, WebSocketState newState)
87 {
88  //Switch to the new state
89  webSocket->state = newState;
90  //Save current time;
91  webSocket->timestamp = osGetSystemTime();
92 
93  //Reset sub-state
94  webSocket->txContext.state = WS_SUB_STATE_INIT;
95  webSocket->rxContext.state = WS_SUB_STATE_INIT;
96 }
97 
98 
99 /**
100  * @brief Parse client or server handshake
101  * @param[in] webSocket Handle to a WebSocket
102  * @return Error code
103  **/
104 
106 {
107  error_t error;
108  size_t n;
109  WebSocketFrameContext *rxContext;
110 
111  //Point to the RX context
112  rxContext = &webSocket->rxContext;
113 
114  //Initialize status code
115  error = NO_ERROR;
116 
117  //Wait for the handshake to complete
118  while(1)
119  {
120  //Client or server operation?
121  if(webSocket->endpoint == WS_ENDPOINT_CLIENT)
122  {
123  if(webSocket->state == WS_STATE_OPEN)
124  break;
125  }
126  else
127  {
128  if(webSocket->state == WS_STATE_SERVER_HANDSHAKE)
129  break;
130  }
131 
132  //Check current sub-state
133  if(rxContext->state == WS_SUB_STATE_INIT)
134  {
135  //Initialize status code
136  webSocket->statusCode = WS_STATUS_CODE_NO_STATUS_RCVD;
137 
138  //Initialize FIN flag
139  rxContext->fin = TRUE;
140 
141  //Flush the receive buffer
142  rxContext->bufferPos = 0;
143  rxContext->bufferLen = 0;
144 
145  //Initialize variables
146  webSocket->handshakeContext.statusCode = 0;
147  webSocket->handshakeContext.upgradeWebSocket = FALSE;
148  webSocket->handshakeContext.connectionUpgrade = FALSE;
149  webSocket->handshakeContext.connectionClose = FALSE;
150  webSocket->handshakeContext.contentLength = 0;
151  webSocket->handshakeContext.closingFrameSent = FALSE;
152  webSocket->handshakeContext.closingFrameReceived = FALSE;
153 
154 #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
155  webSocket->authContext.requiredAuthMode = WS_AUTH_MODE_NONE;
156 #endif
157 
158 #if (WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
159  strcpy(webSocket->authContext.nonce, "");
160  strcpy(webSocket->authContext.opaque, "");
161  webSocket->authContext.stale = FALSE;
162 #endif
163 
164  //Client or server operation?
165  if(webSocket->endpoint == WS_ENDPOINT_CLIENT)
166  {
167  //Clear server key
168  strcpy(webSocket->handshakeContext.serverKey, "");
169 
170  //Debug message
171  TRACE_DEBUG("WebSocket: server handshake\r\n");
172  }
173  else
174  {
175  //Clear client key
176  strcpy(webSocket->handshakeContext.clientKey, "");
177 
178  //Debug message
179  TRACE_DEBUG("WebSocket: client handshake\r\n");
180  }
181 
182  //Decode the leading line
184  }
185  else if(rxContext->state == WS_SUB_STATE_HANDSHAKE_LEADING_LINE)
186  {
187  //Check whether more data is required
188  if(rxContext->bufferLen < 2)
189  {
190  //Limit the number of characters to read at a time
191  n = WEB_SOCKET_BUFFER_SIZE - 1 - rxContext->bufferLen;
192 
193  //Read data until a CLRF character is encountered
194  error = webSocketReceiveData(webSocket, rxContext->buffer +
195  rxContext->bufferLen, n, &n, SOCKET_FLAG_BREAK_CRLF);
196 
197  //Update the length of the buffer
198  rxContext->bufferLen += n;
199  }
200  else if(rxContext->bufferLen >= (WEB_SOCKET_BUFFER_SIZE - 1))
201  {
202  //Report an error
203  error = ERROR_INVALID_REQUEST;
204  }
205  else if(strncmp((char_t *) rxContext->buffer + rxContext->bufferLen - 2, "\r\n", 2))
206  {
207  //Limit the number of characters to read at a time
208  n = WEB_SOCKET_BUFFER_SIZE - 1 - rxContext->bufferLen;
209 
210  //Read data until a CLRF character is encountered
211  error = webSocketReceiveData(webSocket, rxContext->buffer +
212  rxContext->bufferLen, n, &n, SOCKET_FLAG_BREAK_CRLF);
213 
214  //Update the length of the buffer
215  rxContext->bufferLen += n;
216  }
217  else
218  {
219  //Properly terminate the string with a NULL character
220  rxContext->buffer[rxContext->bufferLen] = '\0';
221 
222  //Client or server operation?
223  if(webSocket->endpoint == WS_ENDPOINT_CLIENT)
224  {
225  //The leading line from the server follows the Status-Line format
226  error = webSocketParseStatusLine(webSocket, (char_t *) rxContext->buffer);
227  }
228  else
229  {
230  //The leading line from the client follows the Request-Line format
231  error = webSocketParseRequestLine(webSocket, (char_t *) rxContext->buffer);
232  }
233 
234  //Flush the receive buffer
235  rxContext->bufferPos = 0;
236  rxContext->bufferLen = 0;
237 
238  //Parse the header fields of the handshake
240  }
241  }
242  else if(rxContext->state == WS_SUB_STATE_HANDSHAKE_HEADER_FIELD)
243  {
244  //Check whether more data is required
245  if(rxContext->bufferLen < 2)
246  {
247  //Limit the number of characters to read at a time
248  n = WEB_SOCKET_BUFFER_SIZE - 1 - rxContext->bufferLen;
249 
250  //Read data until a CLRF character is encountered
251  error = webSocketReceiveData(webSocket, rxContext->buffer +
252  rxContext->bufferLen, n, &n, SOCKET_FLAG_BREAK_CRLF);
253 
254  //Update the length of the buffer
255  rxContext->bufferLen += n;
256  }
257  else if(rxContext->bufferLen >= (WEB_SOCKET_BUFFER_SIZE - 1))
258  {
259  //Report an error
260  error = ERROR_INVALID_REQUEST;
261  }
262  else if(strncmp((char_t *) rxContext->buffer + rxContext->bufferLen - 2, "\r\n", 2))
263  {
264  //Limit the number of characters to read at a time
265  n = WEB_SOCKET_BUFFER_SIZE - 1 - rxContext->bufferLen;
266 
267  //Read data until a CLRF character is encountered
268  error = webSocketReceiveData(webSocket, rxContext->buffer +
269  rxContext->bufferLen, n, &n, SOCKET_FLAG_BREAK_CRLF);
270 
271  //Update the length of the buffer
272  rxContext->bufferLen += n;
273  }
274  else
275  {
276  //Properly terminate the string with a NULL character
277  rxContext->buffer[rxContext->bufferLen] = '\0';
278 
279  //An empty line indicates the end of the header fields
280  if(!strcmp((char_t *) rxContext->buffer, "\r\n"))
281  {
282  //Client or server operation?
283  if(webSocket->endpoint == WS_ENDPOINT_CLIENT)
284  {
285  //Verify server's handshake
286  error = webSocketVerifyServerHandshake(webSocket);
287  }
288  else
289  {
290  //Verify client's handshake
291  error = webSocketVerifyClientHandshake(webSocket);
292  }
293  }
294  else
295  {
296  //Read the next character to detect if the CRLF is immediately
297  //followed by a LWSP character
298  rxContext->state = WS_SUB_STATE_HANDSHAKE_LWSP;
299  }
300  }
301  }
302  else if(rxContext->state == WS_SUB_STATE_HANDSHAKE_LWSP)
303  {
304  char_t nextChar;
305 
306  //Read the next character
307  error = webSocketReceiveData(webSocket, &nextChar, sizeof(char_t), &n, 0);
308 
309  //Successful read operation?
310  if(!error && n == sizeof(char_t))
311  {
312  //LWSP character found?
313  if(nextChar == ' ' || nextChar == '\t')
314  {
315  //Unfolding is accomplished by regarding CRLF immediately
316  //followed by a LWSP as equivalent to the LWSP character
317  if(rxContext->bufferLen >= 2)
318  {
319  //Remove trailing CRLF sequence
320  rxContext->bufferLen -= 2;
321  }
322 
323  //The header field spans multiple line
325  }
326  else
327  {
328  //Parse header field
329  error = webSocketParseHeaderField(webSocket, (char_t *) rxContext->buffer);
330 
331  //Restore the very first character of the header field
332  rxContext->buffer[0] = nextChar;
333  //Adjust the length of the receive buffer
334  rxContext->bufferLen = sizeof(char_t);
335 
336  //Decode the next header field
338  }
339  }
340  }
341  else
342  {
343  //Invalid state
344  error = ERROR_WRONG_STATE;
345  }
346 
347  //Any error to report?
348  if(error)
349  break;
350  }
351 
352  //Return status code
353  return error;
354 }
355 
356 
357 /**
358  * @brief Parse the Request-Line of the client's handshake
359  * @param[in] webSocket Handle to a WebSocket
360  * @param[in] line NULL-terminated string that contains the Request-Line
361  * @return Error code
362  **/
363 
365 {
366  error_t error;
367  char_t *token;
368  char_t *p;
369  char_t *s;
370 
371  //Debug message
372  TRACE_DEBUG("%s", line);
373 
374  //The Request-Line begins with a method token
375  token = strtok_r(line, " \r\n", &p);
376  //Unable to retrieve the method?
377  if(token == NULL)
378  return ERROR_INVALID_REQUEST;
379 
380  //The method of the request must be GET
381  if(strcasecmp(token, "GET"))
382  return ERROR_INVALID_REQUEST;
383 
384  //The Request-URI is following the method token
385  token = strtok_r(NULL, " \r\n", &p);
386  //Unable to retrieve the Request-URI?
387  if(token == NULL)
388  return ERROR_INVALID_REQUEST;
389 
390  //Check whether a query string is present
391  s = strchr(token, '?');
392 
393  //Query string found?
394  if(s != NULL)
395  {
396  //Split the string
397  *s = '\0';
398 
399  //Save the Request-URI
401  webSocket->uri, WEB_SOCKET_URI_MAX_LEN);
402  //Any error to report?
403  if(error)
404  return ERROR_INVALID_REQUEST;
405 
406  //Check the length of the query string
407  if(strlen(s + 1) > WEB_SOCKET_QUERY_STRING_MAX_LEN)
408  return ERROR_INVALID_REQUEST;
409 
410  //Save the query string
411  strcpy(webSocket->queryString, s + 1);
412  }
413  else
414  {
415  //Save the Request-URI
417  webSocket->uri, WEB_SOCKET_URI_MAX_LEN);
418  //Any error to report?
419  if(error)
420  return ERROR_INVALID_REQUEST;
421 
422  //No query string
423  webSocket->queryString[0] = '\0';
424  }
425 
426  //The protocol version is following the Request-URI
427  token = strtok_r(NULL, " \r\n", &p);
428 
429  //HTTP version 0.9?
430  if(token == NULL)
431  {
432  //Save version number
433  webSocket->handshakeContext.version = WS_HTTP_VERSION_0_9;
434  //Persistent connections are not supported
435  webSocket->handshakeContext.connectionClose = TRUE;
436  }
437  //HTTP version 1.0?
438  else if(!strcasecmp(token, "HTTP/1.0"))
439  {
440  //Save version number
441  webSocket->handshakeContext.version = WS_HTTP_VERSION_1_0;
442  //By default connections are not persistent
443  webSocket->handshakeContext.connectionClose = TRUE;
444  }
445  //HTTP version 1.1?
446  else if(!strcasecmp(token, "HTTP/1.1"))
447  {
448  //Save version number
449  webSocket->handshakeContext.version = WS_HTTP_VERSION_1_1;
450  //HTTP 1.1 makes persistent connections the default
451  webSocket->handshakeContext.connectionClose = FALSE;
452  }
453  //HTTP version not supported?
454  else
455  {
456  //Report an error
457  return ERROR_INVALID_REQUEST;
458  }
459 
460  //Successful processing
461  return NO_ERROR;
462 }
463 
464 
465 /**
466  * @brief Parse the Status-Line of the server's handshake
467  * @param[in] webSocket Handle to a WebSocket
468  * @param[in] line NULL-terminated string that contains the Status-Line
469  * @return Error code
470  **/
471 
473 {
474  char_t *p;
475  char_t *token;
476 
477  //Debug message
478  TRACE_DEBUG("%s", line);
479 
480  //Retrieve the HTTP-Version field
481  token = strtok_r(line, " ", &p);
482  //Any parsing error?
483  if(token == NULL)
484  return ERROR_INVALID_SYNTAX;
485 
486  //Retrieve the Status-Code field
487  token = strtok_r(NULL, " ", &p);
488  //Any parsing error?
489  if(token == NULL)
490  return ERROR_INVALID_SYNTAX;
491 
492  //Convert the status code
493  webSocket->handshakeContext.statusCode = strtoul(token, &p, 10);
494  //Any parsing error?
495  if(*p != '\0')
496  return ERROR_INVALID_SYNTAX;
497 
498  //Successful processing
499  return NO_ERROR;
500 }
501 
502 
503 /**
504  * @brief Parse a header field
505  * @param[in] webSocket Handle to a WebSocket
506  * @param[in] line NULL-terminated string that contains the header field
507  * @return Error code
508  **/
509 
511 {
512  char_t *separator;
513  char_t *name;
514  char_t *value;
515  WebSocketHandshakeContext *handshakeContext;
516 
517  //Point to the handshake context
518  handshakeContext = &webSocket->handshakeContext;
519 
520  //Debug message
521  TRACE_DEBUG("%s", line);
522 
523  //Check whether a separator is present
524  separator = strchr(line, ':');
525 
526  //Separator found?
527  if(separator != NULL)
528  {
529  //Split the line
530  *separator = '\0';
531 
532  //Get field name and value
533  name = strTrimWhitespace(line);
534  value = strTrimWhitespace(separator + 1);
535 
536  //Upgrade header field found?
537  if(!strcasecmp(name, "Upgrade"))
538  {
539  if(!strcasecmp(value, "websocket"))
540  handshakeContext->upgradeWebSocket = TRUE;
541 
542  }
543  //Connection header field found?
544  else if(!strcasecmp(name, "Connection"))
545  {
546  //Parse Connection header field
548  }
549  //Sec-WebSocket-Key header field found?
550  else if(!strcasecmp(name, "Sec-WebSocket-Key"))
551  {
552  //Server operation?
553  if(webSocket->endpoint == WS_ENDPOINT_SERVER)
554  {
555  //Save the contents of the Sec-WebSocket-Key header field
556  strSafeCopy(handshakeContext->clientKey, value,
558  }
559  }
560  //Sec-WebSocket-Accept header field found?
561  else if(!strcasecmp(name, "Sec-WebSocket-Accept"))
562  {
563  //Client operation?
564  if(webSocket->endpoint == WS_ENDPOINT_CLIENT)
565  {
566  //Save the contents of the Sec-WebSocket-Accept header field
567  strSafeCopy(handshakeContext->serverKey, value,
569  }
570  }
571 #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
572  //WWW-Authenticate header field found?
573  else if(!strcasecmp(name, "WWW-Authenticate"))
574  {
575  //Parse WWW-Authenticate header field
577  }
578 #endif
579  //Content-Length header field found?
580  else if(!strcasecmp(name, "Content-Length"))
581  {
582  handshakeContext->contentLength = strtoul(value, NULL, 10);
583  }
584  }
585 
586  //Successful processing
587  return NO_ERROR;
588 }
589 
590 
591 /**
592  * @brief Parse Connection header field
593  * @param[in] webSocket Handle to a WebSocket
594  * @param[in] value NULL-terminated string that contains the value of header field
595  **/
596 
598 {
599  char_t *p;
600  char_t *token;
601 
602  //Get the first value of the list
603  token = strtok_r(value, ",", &p);
604 
605  //Parse the comma-separated list
606  while(token != NULL)
607  {
608  //Trim whitespace characters
610 
611  //Check current value
612  if(!strcasecmp(value, "keep-alive"))
613  {
614  //The connection is persistent
615  webSocket->handshakeContext.connectionClose = FALSE;
616  }
617  else if(!strcasecmp(value, "close"))
618  {
619  //The connection will be closed after completion of the response
620  webSocket->handshakeContext.connectionClose = TRUE;
621  }
622  else if(!strcasecmp(value, "upgrade"))
623  {
624  //Upgrade the connection
625  webSocket->handshakeContext.connectionUpgrade = TRUE;
626  }
627 
628  //Get next value
629  token = strtok_r(NULL, ",", &p);
630  }
631 }
632 
633 
634 /**
635  * @brief Format client's handshake
636  * @param[in] webSocket Handle to a WebSocket
637  * @param[in] serverPort TCP port number used to establish the connection
638  * @return Error code
639  **/
640 
641 error_t webSocketFormatClientHandshake(WebSocket *webSocket, uint16_t serverPort)
642 {
643  char_t *p;
644  WebSocketFrameContext *txContext;
645 
646  //Point to the TX context
647  txContext = &webSocket->txContext;
648  //Point to the buffer where to format the client's handshake
649  p = (char_t *) txContext->buffer;
650 
651  //The Request-Line begins with a method token, followed by the
652  //Request-URI and the protocol version, and ending with CRLF
653  p += sprintf(p, "GET %s HTTP/1.1\r\n", webSocket->uri);
654 
655  //Add Host header field
656  if(webSocket->host[0] != '\0')
657  {
658  //The Host header field specifies the Internet host and port number of
659  //the resource being requested
660  p += sprintf(p, "Host: %s:%d\r\n", webSocket->host, serverPort);
661  }
662  else
663  {
664  //If the requested URI does not include a host name for the service being
665  //requested, then the Host header field must be given with an empty value
666  p += sprintf(p, "Host:\r\n");
667  }
668 
669 #if (WEB_SOCKET_BASIC_AUTH_SUPPORT == ENABLED || WEB_SOCKET_DIGEST_AUTH_SUPPORT == ENABLED)
670  //Check whether authentication is required
671  if(webSocket->authContext.selectedAuthMode != WS_AUTH_MODE_NONE)
672  {
673  //Add Authorization header field
674  p += webSocketAddAuthorizationField(webSocket, p);
675  }
676 #endif
677 
678  //Add Origin header field
679  if(webSocket->origin[0] != '\0')
680  p += sprintf(p, "Origin: %s\r\n", webSocket->origin);
681  else
682  p += sprintf(p, "Origin: null\r\n");
683 
684  //Add Upgrade header field
685  p += sprintf(p, "Upgrade: websocket\r\n");
686  //Add Connection header field
687  p += sprintf(p, "Connection: Upgrade\r\n");
688 
689  //Add Sec-WebSocket-Protocol header field
690  if(webSocket->subProtocol[0] != '\0')
691  p += sprintf(p, "Sec-WebSocket-Protocol: %s\r\n", webSocket->subProtocol);
692 
693  //Add Sec-WebSocket-Key header field
694  p += sprintf(p, "Sec-WebSocket-Key: %s\r\n",
695  webSocket->handshakeContext.clientKey);
696 
697  //Add Sec-WebSocket-Version header field
698  p += sprintf(p, "Sec-WebSocket-Version: 13\r\n");
699  //An empty line indicates the end of the header fields
700  p += sprintf(p, "\r\n");
701 
702  //Debug message
703  TRACE_DEBUG("\r\n");
704  TRACE_DEBUG("WebSocket: client handshake\r\n");
705  TRACE_DEBUG("%s", txContext->buffer);
706 
707  //Rewind to the beginning of the buffer
708  txContext->bufferPos = 0;
709  //Update the number of data buffered but not yet sent
710  txContext->bufferLen = strlen((char_t *) txContext->buffer);
711 
712  //Successful processing
713  return NO_ERROR;
714 }
715 
716 
717 /**
718  * @brief Format server's handshake
719  * @param[in] webSocket Handle to a WebSocket
720  * @return Error code
721  **/
722 
724 {
725  char_t *p;
726  WebSocketFrameContext *txContext;
727 
728  //Point to the TX context
729  txContext = &webSocket->txContext;
730  //Point to the buffer where to format the client's handshake
731  p = (char_t *) txContext->buffer;
732 
733  //The first line is an HTTP Status-Line, with the status code 101
734  p += sprintf(p, "HTTP/1.1 101 Switching Protocols\r\n");
735 
736  //Add Upgrade header field
737  p += sprintf(p, "Upgrade: websocket\r\n");
738  //Add Connection header field
739  p += sprintf(p, "Connection: Upgrade\r\n");
740 
741  //Add Sec-WebSocket-Protocol header field
742  if(webSocket->subProtocol[0] != '\0')
743  p += sprintf(p, "Sec-WebSocket-Protocol: %s\r\n", webSocket->subProtocol);
744 
745  //Add Sec-WebSocket-Accept header field
746  p += sprintf(p, "Sec-WebSocket-Accept: %s\r\n",
747  webSocket->handshakeContext.serverKey);
748 
749  //An empty line indicates the end of the header fields
750  p += sprintf(p, "\r\n");
751 
752  //Debug message
753  TRACE_DEBUG("\r\n");
754  TRACE_DEBUG("WebSocket: server handshake\r\n");
755  TRACE_DEBUG("%s", txContext->buffer);
756 
757  //Rewind to the beginning of the buffer
758  txContext->bufferPos = 0;
759  //Update the number of data buffered but not yet sent
760  txContext->bufferLen = strlen((char_t *) txContext->buffer);
761 
762  //Successful processing
763  return NO_ERROR;
764 }
765 
766 
767 /**
768  * @brief Format HTTP error response
769  * @param[in] webSocket Handle to a WebSocket
770  * @param[in] statusCode HTTP status code
771  * @param[in] message User message
772  * @return Error code
773  **/
774 
777 {
778  uint_t i;
779  size_t length;
780  char_t *p;
781  WebSocketFrameContext *txContext;
782 
783  //HTML response template
784  static const char_t template[] =
785  "<!doctype html>\r\n"
786  "<html>\r\n"
787  "<head><title>Error %03d</title></head>\r\n"
788  "<body>\r\n"
789  "<h2>Error %03d</h2>\r\n"
790  "<p>%s</p>\r\n"
791  "</body>\r\n"
792  "</html>\r\n";
793 
794  //Point to the TX context
795  txContext = &webSocket->txContext;
796  //Point to the buffer where to format the client's handshake
797  p = (char_t *) txContext->buffer;
798 
799  //The first line of a response message is the Status-Line, consisting
800  //of the protocol version followed by a numeric status code and its
801  //associated textual phrase
802  p += sprintf(p, "HTTP/%u.%u %u ", MSB(webSocket->handshakeContext.version),
803  LSB(webSocket->handshakeContext.version), statusCode);
804 
805  //Retrieve the Reason-Phrase that corresponds to the Status-Code
806  for(i = 0; i < arraysize(statusCodeList); i++)
807  {
808  //Check the status code
809  if(statusCodeList[i].value == statusCode)
810  {
811  //Append the textual phrase to the Status-Line
812  p += sprintf(p, statusCodeList[i].message);
813  //Break the loop and continue processing
814  break;
815  }
816  }
817 
818  //Properly terminate the Status-Line
819  p += sprintf(p, "\r\n");
820 
821  //Content type
822  p += sprintf(p, "Content-Type: %s\r\n", "text/html");
823 
824  //Compute the length of the response
825  length = strlen(template) + strlen(message) - 4;
826  //Set Content-Length field
827  p += sprintf(p, "Content-Length: %" PRIuSIZE "\r\n", length);
828 
829  //Terminate the header with an empty line
830  p += sprintf(p, "\r\n");
831 
832  //Format HTML response
833  p += sprintf(p, template, statusCode, statusCode, message);
834 
835  //Rewind to the beginning of the buffer
836  txContext->bufferPos = 0;
837  //Update the number of data buffered but not yet sent
838  txContext->bufferLen = strlen((char_t *) txContext->buffer);
839 
840  //Successful processing
841  return NO_ERROR;
842 }
843 
844 
845 /**
846  * @brief Verify client's handshake
847  * @param[in] webSocket Handle to a WebSocket
848  * @return Error code
849  **/
850 
852 {
853  error_t error;
854  WebSocketHandshakeContext *handshakeContext;
855 
856  //Debug message
857  TRACE_DEBUG("WebSocket: verifying client handshake\r\n");
858 
859  //Point to the handshake context
860  handshakeContext = &webSocket->handshakeContext;
861 
862  //The HTTP version must be at least 1.1
863  if(handshakeContext->version < WS_HTTP_VERSION_1_1)
864  return ERROR_INVALID_REQUEST;
865 
866  //The request must contain an Upgrade header field whose value
867  //must include the "websocket" keyword
868  if(!handshakeContext->upgradeWebSocket)
869  return ERROR_INVALID_REQUEST;
870 
871  //The request must contain a Connection header field whose value
872  //must include the "Upgrade" token
873  if(!handshakeContext->connectionUpgrade)
874  return ERROR_INVALID_REQUEST;
875 
876  //The request must include a header field with the name Sec-WebSocket-Key
877  if(handshakeContext->clientKey[0] == 0)
878  return ERROR_INVALID_REQUEST;
879 
880  //Check the Sec-WebSocket-Key header field
881  error = webSocketVerifyClientKey(webSocket);
882  //Verification failed?
883  if(error)
884  return error;
885 
886  //Generate the server part of the handshake
888 
889  //Successful processing
890  return NO_ERROR;
891 }
892 
893 
894 /**
895  * @brief Verify server's handshake
896  * @param[in] webSocket Handle to a WebSocket
897  * @return Error code
898  **/
899 
901 {
902  error_t error;
903  WebSocketHandshakeContext *handshakeContext;
904 
905  //Debug message
906  TRACE_DEBUG("WebSocket: verifying server handshake\r\n");
907 
908  //Point to the handshake context
909  handshakeContext = &webSocket->handshakeContext;
910 
911  //If the status code received from the server is not 101, the client
912  //handles the response per HTTP procedures
913  if(handshakeContext->statusCode == 401)
914  {
915  //Authorization required
916  return ERROR_AUTH_REQUIRED;
917  }
918  else if(handshakeContext->statusCode != 101)
919  {
920  //Unknown status code
921  return ERROR_INVALID_STATUS;
922  }
923 
924  //If the response lacks an Upgrade header field or the Upgrade header field
925  //contains a value that is not an ASCII case-insensitive match for the
926  //value "websocket", the client must fail the WebSocket connection
927  if(!handshakeContext->upgradeWebSocket)
928  return ERROR_INVALID_SYNTAX;
929 
930  //If the response lacks a Connection header field or the Connection header
931  //field doesn't contain a token that is an ASCII case-insensitive match for
932  //the value "Upgrade", the client must fail the WebSocket connection
933  if(!handshakeContext->connectionUpgrade)
934  return ERROR_INVALID_SYNTAX;
935 
936  //If the response lacks a Sec-WebSocket-Accept header field, the client
937  //must fail the WebSocket connection
938  if(strlen(handshakeContext->serverKey) == 0)
939  return ERROR_INVALID_SYNTAX;
940 
941  //Check the Sec-WebSocket-Accept header field
942  error = webSocketVerifyServerKey(webSocket);
943  //Verification failed?
944  if(error)
945  return error;
946 
947  //If the server's response is validated as provided for above, it is
948  //said that the WebSocket connection is established and that the
949  //WebSocket connection is in the OPEN state
951 
952  //Successful processing
953  return NO_ERROR;
954 }
955 
956 
957 /**
958  * @brief Generate client's key
959  * @param[in] webSocket Handle to a WebSocket
960  * @return Error code
961  **/
962 
964 {
965  error_t error;
966  size_t n;
967  uint8_t nonce[16];
968  WebSocketHandshakeContext *handshakeContext;
969 
970  //Debug message
971  TRACE_DEBUG("WebSocket: Generating client's key...\r\n");
972 
973  //Point to the handshake context
974  handshakeContext = &webSocket->handshakeContext;
975 
976  //Make sure that the RNG callback function has been registered
977  if(webSockRandCallback == NULL)
978  {
979  //A cryptographically strong random number generator
980  //must be used to generate the nonce
981  return ERROR_PRNG_NOT_READY;
982  }
983 
984  //A nonce must be selected randomly for each connection
985  error = webSockRandCallback(nonce, sizeof(nonce));
986  //Any error to report?
987  if(error)
988  return error;
989 
990  //Encode the client's key
991  base64Encode(nonce, sizeof(nonce), handshakeContext->clientKey, &n);
992 
993  //Debug message
994  TRACE_DEBUG(" Client key: %s\r\n", handshakeContext->clientKey);
995 
996  //Successful processing
997  return NO_ERROR;
998 }
999 
1000 
1001 /**
1002  * @brief Generate server's key
1003  * @param[in] webSocket Handle to a WebSocket
1004  * @return Error code
1005  **/
1006 
1008 {
1009  size_t n;
1010  WebSocketHandshakeContext *handshakeContext;
1011  Sha1Context sha1Context;
1012 
1013  //Debug message
1014  TRACE_DEBUG("WebSocket: Generating server's key...\r\n");
1015 
1016  //Point to the handshake context
1017  handshakeContext = &webSocket->handshakeContext;
1018 
1019  //Retrieve the length of the client key
1020  n = strlen(handshakeContext->clientKey);
1021 
1022  //Concatenate the Sec-WebSocket-Key with the GUID string and digest
1023  //the resulting string using SHA-1
1024  sha1Init(&sha1Context);
1025  sha1Update(&sha1Context, handshakeContext->clientKey, n);
1026  sha1Update(&sha1Context, webSocketGuid, strlen(webSocketGuid));
1027  sha1Final(&sha1Context, NULL);
1028 
1029  //Encode the result using Base64
1030  base64Encode(sha1Context.digest, SHA1_DIGEST_SIZE,
1031  handshakeContext->serverKey, &n);
1032 
1033  //Debug message
1034  TRACE_DEBUG(" Server key: %s\r\n", handshakeContext->serverKey);
1035 
1036  //Successful processing
1037  return NO_ERROR;
1038 }
1039 
1040 
1041 /**
1042  * @brief Verify client's key
1043  * @param[in] webSocket Handle to a WebSocket
1044  * @return Error code
1045  **/
1046 
1048 {
1049  error_t error;
1050  size_t n;
1051  char_t *buffer;
1052  WebSocketHandshakeContext *handshakeContext;
1053 
1054  //Debug message
1055  TRACE_DEBUG("WebSocket: Verifying client's key...\r\n");
1056 
1057  //Temporary buffer
1058  buffer = (char_t *) webSocket->txContext.buffer;
1059 
1060  //Point to the handshake context
1061  handshakeContext = &webSocket->handshakeContext;
1062 
1063  //Retrieve the length of the client's key
1064  n = strlen(handshakeContext->clientKey);
1065 
1066  //The value of the Sec-WebSocket-Key header field must be a 16-byte
1067  //value that has been Base64-encoded
1068  error = base64Decode(handshakeContext->clientKey, n, buffer, &n);
1069  //Decoding failed?
1070  if(error)
1071  return ERROR_INVALID_KEY;
1072 
1073  //Check the length of the resulting value
1074  if(n != 16)
1075  return ERROR_INVALID_KEY;
1076 
1077  //Successful verification
1078  return NO_ERROR;
1079 }
1080 
1081 
1082 /**
1083  * @brief Verify server's key
1084  * @param[in] webSocket Handle to a WebSocket
1085  * @return Error code
1086  **/
1087 
1089 {
1090  size_t n;
1091  char_t *buffer;
1092  WebSocketHandshakeContext *handshakeContext;
1093  Sha1Context sha1Context;
1094 
1095  //Debug message
1096  TRACE_DEBUG("WebSocket: Verifying server's key...\r\n");
1097 
1098  //Temporary buffer
1099  buffer = (char_t *) webSocket->txContext.buffer;
1100 
1101  //Point to the handshake context
1102  handshakeContext = &webSocket->handshakeContext;
1103 
1104  //Retrieve the length of the client's key
1105  n = strlen(handshakeContext->clientKey);
1106 
1107  //Concatenate the Sec-WebSocket-Key with the GUID string and digest
1108  //the resulting string using SHA-1
1109  sha1Init(&sha1Context);
1110  sha1Update(&sha1Context, handshakeContext->clientKey, n);
1111  sha1Update(&sha1Context, webSocketGuid, strlen(webSocketGuid));
1112  sha1Final(&sha1Context, NULL);
1113 
1114  //Encode the result using Base64
1115  base64Encode(sha1Context.digest, SHA1_DIGEST_SIZE, buffer, &n);
1116 
1117  //Debug message
1118  TRACE_DEBUG(" Client key: %s\r\n", handshakeContext->clientKey);
1119  TRACE_DEBUG(" Server key: %s\r\n", handshakeContext->serverKey);
1120  TRACE_DEBUG(" Calculated key: %s\r\n", webSocket->txContext.buffer);
1121 
1122  //Check whether the server's key is valid
1123  if(strcmp(handshakeContext->serverKey, buffer))
1124  return ERROR_INVALID_KEY;
1125 
1126  //Successful verification
1127  return NO_ERROR;
1128 }
1129 
1130 
1131 /**
1132  * @brief Check whether a status code is valid
1133  * @param[in] statusCode Status code
1134  * @return The function returns TRUE is the specified status code is
1135  * valid. Otherwise, FALSE is returned
1136  **/
1137 
1139 {
1140  bool_t valid;
1141 
1142  //Check status code
1152  {
1153  valid = TRUE;
1154  }
1155  else if(statusCode >= 3000)
1156  {
1157  valid = TRUE;
1158  }
1159  else
1160  {
1161  valid = FALSE;
1162  }
1163 
1164  //The function returns TRUE is the specified status code is valid
1165  return valid;
1166 }
1167 
1168 
1169 /**
1170  * @brief Decode a percent-encoded string
1171  * @param[in] input NULL-terminated string to be decoded
1172  * @param[out] output NULL-terminated string resulting from the decoding process
1173  * @param[in] outputSize Size of the output buffer in bytes
1174  * @return Error code
1175  **/
1176 
1178  char_t *output, size_t outputSize)
1179 {
1180  size_t i;
1181  char_t buffer[3];
1182 
1183  //Check parameters
1184  if(input == NULL || output == NULL)
1185  return ERROR_INVALID_PARAMETER;
1186 
1187  //Decode the percent-encoded string
1188  for(i = 0; *input != '\0' && i < outputSize; i++)
1189  {
1190  //Check current character
1191  if(*input == '+')
1192  {
1193  //Replace '+' characters with spaces
1194  output[i] = ' ';
1195  //Advance data pointer
1196  input++;
1197  }
1198  else if(input[0] == '%' && input[1] != '\0' && input[2] != '\0')
1199  {
1200  //Process percent-encoded characters
1201  buffer[0] = input[1];
1202  buffer[1] = input[2];
1203  buffer[2] = '\0';
1204  //String to integer conversion
1205  output[i] = (uint8_t) strtoul(buffer, NULL, 16);
1206  //Advance data pointer
1207  input += 3;
1208  }
1209  else
1210  {
1211  //Copy any other characters
1212  output[i] = *input;
1213  //Advance data pointer
1214  input++;
1215  }
1216  }
1217 
1218  //Check whether the output buffer runs out of space
1219  if(i >= outputSize)
1220  return ERROR_FAILURE;
1221 
1222  //Properly terminate the resulting string
1223  output[i] = '\0';
1224  //Successful processing
1225  return NO_ERROR;
1226 }
1227 
1228 
1229 /**
1230  * @brief Check whether a an UTF-8 stream is valid
1231  * @param[in] context UTF-8 decoding context
1232  * @param[in] data Pointer to the chunk of data to be processed
1233  * @param[in] length Data chunk length
1234  * @param[in] remaining number of remaining bytes in the UTF-8 stream
1235  * @return The function returns TRUE is the specified UTF-8 stream is
1236  * valid. Otherwise, FALSE is returned
1237  **/
1238 
1240  const uint8_t *data, size_t length, size_t remaining)
1241 {
1242  size_t i;
1243  bool_t valid;
1244 
1245  //Initialize flag
1246  valid = TRUE;
1247 
1248  //Interpret the byte stream as UTF-8
1249  for(i = 0; i < length && valid; i++)
1250  {
1251  //Leading or continuation byte?
1252  if(context->utf8CharIndex == 0)
1253  {
1254  //7-bit code point?
1255  if((data[i] & 0x80) == 0x00)
1256  {
1257  //The code point consist of a single byte
1258  context->utf8CharSize = 1;
1259  //Decode the first byte of the sequence
1260  context->utf8CodePoint = data[i] & 0x7F;
1261  }
1262  //11-bit code point?
1263  else if((data[i] & 0xE0) == 0xC0)
1264  {
1265  //The code point consist of a 2 bytes
1266  context->utf8CharSize = 2;
1267  //Decode the first byte of the sequence
1268  context->utf8CodePoint = (data[i] & 0x1F) << 6;
1269  }
1270  //16-bit code point?
1271  else if((data[i] & 0xF0) == 0xE0)
1272  {
1273  //The code point consist of a 3 bytes
1274  context->utf8CharSize = 3;
1275  //Decode the first byte of the sequence
1276  context->utf8CodePoint = (data[i] & 0x0F) << 12;
1277  }
1278  //21-bit code point?
1279  else if((data[i] & 0xF8) == 0xF0)
1280  {
1281  //The code point consist of a 3 bytes
1282  context->utf8CharSize = 4;
1283  //Decode the first byte of the sequence
1284  context->utf8CodePoint = (data[i] & 0x07) << 18;
1285  }
1286  else
1287  {
1288  //The UTF-8 stream is not valid
1289  valid = FALSE;
1290  }
1291 
1292  //This test only applies to frames that are not fragmented
1293  if(length <= remaining)
1294  {
1295  //Make sure the UTF-8 stream is properly terminated
1296  if((i + context->utf8CharSize) > remaining)
1297  {
1298  //The UTF-8 stream is not valid
1299  valid = FALSE;
1300  }
1301  }
1302 
1303  //Decode the next byte of the sequence
1304  context->utf8CharIndex = context->utf8CharSize - 1;
1305  }
1306  else
1307  {
1308  //Continuation bytes all have 10 in the high-order position
1309  if((data[i] & 0xC0) == 0x80)
1310  {
1311  //Decode the multi-byte sequence
1312  context->utf8CharIndex--;
1313  //All continuation bytes contain exactly 6 bits from the code point
1314  context->utf8CodePoint |= (data[i] & 0x3F) << (context->utf8CharIndex * 6);
1315 
1316  //The correct encoding of a code point use only the minimum number
1317  //of bytes required to hold the significant bits of the code point
1318  if(context->utf8CharSize == 2)
1319  {
1320  //Overlong encoding is not supported
1321  if((context->utf8CodePoint & ~0x7F) == 0)
1322  valid = FALSE;
1323  }
1324  if(context->utf8CharSize == 3 && context->utf8CharIndex < 2)
1325  {
1326  //Overlong encoding is not supported
1327  if((context->utf8CodePoint & ~0x7FF) == 0)
1328  valid = FALSE;
1329  }
1330  if(context->utf8CharSize == 4 && context->utf8CharIndex < 3)
1331  {
1332  //Overlong encoding is not supported
1333  if((context->utf8CodePoint & ~0xFFFF) == 0)
1334  valid = FALSE;
1335  }
1336 
1337  //According to the UTF-8 definition (RFC 3629) the high and low
1338  //surrogate halves used by UTF-16 (U+D800 through U+DFFF) are not
1339  //legal Unicode values, and their UTF-8 encoding should be treated
1340  //as an invalid byte sequence
1341  if(context->utf8CodePoint >= 0xD800 && context->utf8CodePoint < 0xE000)
1342  valid = FALSE;
1343 
1344  //Code points greater than U+10FFFF are not valid
1345  if(context->utf8CodePoint >= 0x110000)
1346  valid = FALSE;
1347  }
1348  else
1349  {
1350  //The start byte is not followed by enough continuation bytes
1351  valid = FALSE;
1352  }
1353  }
1354  }
1355 
1356  //The function returns TRUE is the specified UTF-8 stream is valid
1357  return valid;
1358 }
1359 
1360 #endif
#define WEB_SOCKET_CLIENT_KEY_SIZE
Definition: web_socket.h:169
error_t webSocketVerifyServerKey(WebSocket *webSocket)
Verify server&#39;s key.
error_t webSocketGenerateClientKey(WebSocket *webSocket)
Generate client&#39;s key.
#define WEB_SOCKET_SERVER_KEY_SIZE
Definition: web_socket.h:171
bool_t webSocketCheckStatusCode(uint16_t statusCode)
Check whether a status code is valid.
error_t webSocketVerifyClientKey(WebSocket *webSocket)
Verify client&#39;s key.
char char_t
Definition: compiler_port.h:41
void webSocketParseConnectionField(WebSocket *webSocket, char_t *value)
Parse Connection header field.
systime_t osGetSystemTime(void)
Retrieve system time.
#define MSB(x)
Definition: os_port.h:56
uint8_t buffer[WEB_SOCKET_BUFFER_SIZE]
Data buffer.
Definition: web_socket.h:402
error_t webSocketParseAuthenticateField(WebSocket *webSocket, char_t *value)
Parse WWW-Authenticate header field.
TCP/IP stack core.
void sha1Init(Sha1Context *context)
Initialize SHA-1 message digest context.
Definition: sha1.c:124
WebSocketState
WebSocket states.
Definition: web_socket.h:222
error_t webSocketGenerateServerKey(WebSocket *webSocket)
Generate server&#39;s key.
#define WEB_SOCKET_URI_MAX_LEN
Definition: web_socket.h:108
Debugging facilities.
Frame encoding/decoding context.
Definition: web_socket.h:392
uint8_t p
Definition: ndp.h:295
bool_t fin
Final fragment in a message.
Definition: web_socket.h:397
#define WebSocket
Definition: web_socket.h:175
#define WEB_SOCKET_QUERY_STRING_MAX_LEN
Definition: web_socket.h:115
Generic error code.
Definition: error.h:43
SHA-1 algorithm context.
Definition: sha1.h:54
uint8_t message[]
Definition: chap.h:150
Invalid parameter.
Definition: error.h:45
uint32_t utf8CodePoint
Definition: web_socket.h:416
UTF-8 decoding context.
Definition: web_socket.h:412
error_t webSocketFormatServerHandshake(WebSocket *webSocket)
Format server&#39;s handshake.
error_t webSocketFormatClientHandshake(WebSocket *webSocket, uint16_t serverPort)
Format client&#39;s handshake.
error_t base64Decode(const char_t *input, size_t inputLen, void *output, size_t *outputLen)
Base64 decoding algorithm.
Definition: base64.c:186
uint16_t statusCode
error_t webSocketVerifyServerHandshake(WebSocket *webSocket)
Verify server&#39;s handshake.
String manipulation helper functions.
HTTP status code.
size_t bufferPos
Current position.
Definition: web_socket.h:404
#define SHA1_DIGEST_SIZE
Definition: sha1.h:38
#define strtok_r(str, delim, p)
void base64Encode(const void *input, size_t inputLen, char_t *output, size_t *outputLen)
Base64 encoding algorithm.
Definition: base64.c:76
#define LSB(x)
Definition: os_port.h:52
WebSocket transport layer.
const char_t webSocketGuid[]
#define TRUE
Definition: os_port.h:48
WebSocketSubState state
Definition: web_socket.h:394
#define strcasecmp
char_t serverKey[WEB_SOCKET_SERVER_KEY_SIZE+1]
Definition: web_socket.h:382
#define arraysize(a)
Definition: os_port.h:68
HTTP authentication for WebSockets.
error_t webSocketParseHeaderField(WebSocket *webSocket, char_t *line)
Parse a header field.
void webSocketChangeState(WebSocket *webSocket, WebSocketState newState)
Update WebSocket state.
size_t webSocketAddAuthorizationField(WebSocket *webSocket, char_t *output)
Format Authorization header field.
char_t name[]
error_t webSocketDecodePercentEncodedString(const char_t *input, char_t *output, size_t outputSize)
Decode a percent-encoded string.
char_t clientKey[WEB_SOCKET_CLIENT_KEY_SIZE+1]
Definition: web_socket.h:381
WebSocket frame parsing and formatting.
error_t webSocketParseStatusLine(WebSocket *webSocket, char_t *line)
Parse the Status-Line of the server&#39;s handshake.
error_t webSocketParseHandshake(WebSocket *webSocket)
Parse client or server handshake.
uint8_t s
void sha1Final(Sha1Context *context, uint8_t *digest)
Finish the SHA-1 message digest.
Definition: sha1.c:186
Success.
Definition: error.h:42
size_t bufferLen
Length of the data buffer.
Definition: web_socket.h:403
error_t
Error codes.
Definition: error.h:40
Base64 encoding scheme.
uint8_t digest[20]
Definition: sha1.h:59
unsigned int uint_t
Definition: compiler_port.h:43
error_t strSafeCopy(char_t *dest, const char_t *src, size_t destSize)
Copy string.
Definition: str.c:157
uint8_t token[]
Definition: coap_common.h:181
uint8_t data[]
Definition: dtls_misc.h:167
#define PRIuSIZE
Definition: compiler_port.h:72
SHA-1 (Secure Hash Algorithm 1)
void sha1Update(Sha1Context *context, const void *data, size_t length)
Update the SHA-1 context with a portion of the message being hashed.
Definition: sha1.c:147
uint8_t value[]
Definition: dtls_misc.h:141
char_t * strTrimWhitespace(char_t *s)
Removes all leading and trailing whitespace from a string.
Definition: str.c:68
#define WEB_SOCKET_BUFFER_SIZE
Definition: web_socket.h:80
Helper functions for WebSockets.
WebSocketRandCallback webSockRandCallback
Definition: web_socket.c:50
error_t webSocketFormatErrorResponse(WebSocket *webSocket, uint_t statusCode, const char_t *message)
Format HTTP error response.
error_t webSocketVerifyClientHandshake(WebSocket *webSocket)
Verify client&#39;s handshake.
uint8_t length
Definition: dtls_misc.h:140
uint8_t n
#define FALSE
Definition: os_port.h:44
int bool_t
Definition: compiler_port.h:47
bool_t webSocketCheckUtf8Stream(WebSocketUtf8Context *context, const uint8_t *data, size_t length, size_t remaining)
Check whether a an UTF-8 stream is valid.
error_t webSocketReceiveData(WebSocket *webSocket, void *data, size_t size, size_t *received, uint_t flags)
Receive data using the relevant transport protocol.
error_t webSocketParseRequestLine(WebSocket *webSocket, char_t *line)
Parse the Request-Line of the client&#39;s handshake.
Handshake context.
Definition: web_socket.h:373
#define TRACE_DEBUG(...)
Definition: debug.h:98
WebSocket API (client and server)