net: websocket: Initial support for server websocket

This commit creates a websocket library that can be used by
applications. The websocket library implements currently only
server role and it uses services provided by net-app API.
The library supports TLS if enabled in configuration file.

This also adds websocket calls to HTTP app server if websocket
connection is established.

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
Jukka Rissanen 2017-08-20 23:15:07 +03:00
commit d70c7383de
11 changed files with 929 additions and 8 deletions

View file

@ -435,6 +435,27 @@ struct http_ctx {
u16_t url_len;
} http;
#if defined(CONFIG_WEBSOCKET)
struct {
/** Pending data that is not yet ready for processing */
struct net_pkt *pending;
/** Amount of data that needs to be read still */
u32_t data_waiting;
/** Websocket connection masking value */
u32_t masking_value;
/** How many bytes we have read */
u32_t data_read;
/** Message type flag. Value is one of WS_FLAG_XXX flag values
* defined in weboscket.h
*/
u32_t msg_type_flag;
} websocket;
#endif /* CONFIG_WEBSOCKET */
#if defined(CONFIG_NET_DEBUG_HTTP_CONN)
sys_snode_t node;
#endif /* CONFIG_HTTP_DEBUG_HTTP_CONN */

102
include/net/websocket.h Normal file
View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2017 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __WEBSOCKET_H__
#define __WEBSOCKET_H__
#include <net/http.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Websocket library
* @defgroup websocket Websocket Library
* @{
*/
/** Values for flag variable in HTTP receive callback */
#define WS_FLAG_FINAL 0x00000001
#define WS_FLAG_TEXT 0x00000002
#define WS_FLAG_BINARY 0x00000004
#define WS_FLAG_CLOSE 0x00000008
#define WS_FLAG_PING 0x00000010
#define WS_FLAG_PONG 0x00000011
enum ws_opcode {
WS_OPCODE_CONTINUE = 0x00,
WS_OPCODE_DATA_TEXT = 0x01,
WS_OPCODE_DATA_BINARY = 0x02,
WS_OPCODE_CLOSE = 0x08,
WS_OPCODE_PING = 0x09,
WS_OPCODE_PONG = 0x0A,
};
/**
* @brief Send websocket msg to peer.
*
* @details The function will automatically add websocket header to the
* message.
*
* @param ctx Websocket context.
* @param payload Websocket data to send.
* @param payload_len Length of the data to be sent.
* @param opcode Operation code (text, binary, ping, pong, close)
* @param mask Mask the data, see RFC 6455 for details
* @param final Is this final message for this message send. If final == false,
* then the first message must have valid opcode and subsequent messages must
* have opcode WS_OPCODE_CONTINUE. If final == true and this is the only
* message, then opcode should have proper opcode (text or binary) set.
* @param dst Remote socket address
* @param user_send_data User specific data to this connection. This is passed
* as a parameter to sent cb after the packet has been sent.
*
* @return 0 if ok, <0 if error.
*/
int ws_send_msg(struct http_ctx *ctx, u8_t *payload, size_t payload_len,
enum ws_opcode opcode, bool mask, bool final,
const struct sockaddr *dst,
void *user_send_data);
/**
* @brief Send message to client.
*
* @details The function will automatically add websocket header to the
* message.
*
* @param ctx Websocket context.
* @param payload Websocket data to send.
* @param payload_len Length of the data to be sent.
* @param opcode Operation code (text, binary, ping ,pong ,close)
* @param final Is this final message for this data send
* @param dst Remote socket address
* @param user_send_data User specific data to this connection. This is passed
* as a parameter to sent cb after the packet has been sent.
*
* @return 0 if ok, <0 if error.
*/
static inline int ws_send_msg_to_client(struct http_ctx *ctx,
u8_t *payload,
size_t payload_len,
enum ws_opcode opcode,
bool final,
const struct sockaddr *dst,
void *user_send_data)
{
return ws_send_msg(ctx, payload, payload_len, opcode, false, final,
dst, user_send_data);
}
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* __WS_H__ */

View file

@ -5,6 +5,7 @@ add_subdirectory_ifdef(CONFIG_DNS_RESOLVER dns)
add_subdirectory_ifdef(CONFIG_MQTT_LIB mqtt)
add_subdirectory_ifdef(CONFIG_NET_APP_SETTINGS app)
add_subdirectory_ifdef(CONFIG_NET_SOCKETS sockets)
add_subdirectory_ifdef(CONFIG_WEBSOCKET websocket)
if(CONFIG_HTTP_PARSER_URL
OR CONFIG_HTTP_PARSER

View file

@ -18,6 +18,8 @@ source "subsys/net/lib/lwm2m/Kconfig"
source "subsys/net/lib/sntp/Kconfig"
source "subsys/net/lib/websocket/Kconfig"
endmenu
menu "Network Application Support"

View file

@ -33,9 +33,11 @@ config HTTP_HEADERS
int "HTTP header field max number of items"
depends on HTTP_SERVER
default 8
default 20 if WEBSOCKET
help
Number of HTTP header field items that an HTTP server
application will handle
application will handle. If websocket is enabled, then the
default needs to be much bigger.
config HTTPS
bool "HTTPS support"

View file

@ -66,6 +66,15 @@ int http_close(struct http_ctx *ctx)
}
#endif
#if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_WEBSOCKET)
if (ctx->websocket.pending) {
net_pkt_unref(ctx->websocket.pending);
ctx->websocket.pending = NULL;
}
ctx->websocket.data_waiting = 0;
#endif
return net_app_close(&ctx->app_ctx);
}

View file

@ -25,6 +25,11 @@
#include <net/net_ip.h>
#include <net/http.h>
#if defined(CONFIG_WEBSOCKET)
#include <net/websocket.h>
#include "../websocket/websocket_internal.h"
#endif
#define BUF_ALLOC_TIMEOUT 100
#define HTTP_DEFAULT_PORT 80
@ -314,6 +319,10 @@ static inline void new_client(struct http_ctx *ctx,
struct net_context *net_ctx;
const char *type_str = "HTTP";
if (type == WS_CONNECTION) {
type_str = "WS";
}
net_ctx = get_server_ctx(app_ctx, dst);
if (net_ctx) {
NET_INFO("[%p] %s connection from %s (%p)", ctx, type_str,
@ -534,6 +543,17 @@ static void http_closed(struct net_app_ctx *app_ctx,
if (ctx->cb.close) {
ctx->cb.close(ctx, 0, ctx->user_data);
}
#if defined(CONFIG_WEBSOCKET)
if (ctx->websocket.pending) {
net_pkt_unref(ctx->websocket.pending);
ctx->websocket.pending = NULL;
}
ctx->websocket.data_waiting = 0;
#endif
ctx->http.field_values_ctr = 0;
}
static void http_received(struct net_app_ctx *app_ctx,
@ -581,9 +601,9 @@ static void http_received(struct net_app_ctx *app_ctx,
if (ctx->state == HTTP_STATE_OPEN) {
/* We have active websocket session and there is no longer
* any HTTP traffic in the connection. Give the data to
* application to send.
* application.
*/
goto http_only;
goto ws_only;
}
while (frag) {
@ -595,7 +615,7 @@ static void http_received(struct net_app_ctx *app_ctx,
ctx->http.request_buf_len) {
if (ctx->state == HTTP_STATE_HEADER_RECEIVED) {
goto http_ready;
goto ws_ready;
}
/* If the caller has not supplied a callback, then
@ -650,7 +670,7 @@ fail:
http_send_error(ctx, 400, NULL, 0, dst);
} else {
if (ctx->state == HTTP_STATE_HEADER_RECEIVED) {
goto http_ready;
goto ws_ready;
}
http_process_recv(ctx, dst);
@ -664,17 +684,181 @@ quit:
return;
http_only:
ws_only:
if (ctx->cb.recv) {
#if defined(CONFIG_WEBSOCKET)
u32_t msg_len, header_len = 0;
bool masked = true;
int ret;
if (ctx->websocket.data_waiting == 0) {
ctx->websocket.masking_value = 0;
ctx->websocket.data_read = 0;
ctx->websocket.msg_type_flag = 0;
if (ctx->websocket.pending) {
/* Append the pending data to current buffer */
int orig_len;
orig_len = net_pkt_appdatalen(
ctx->websocket.pending);
net_pkt_set_appdatalen(
ctx->websocket.pending,
orig_len + net_pkt_appdatalen(pkt));
net_pkt_frag_add(ctx->websocket.pending,
pkt->frags);
pkt->frags = NULL;
net_pkt_unref(pkt);
net_pkt_compact(ctx->websocket.pending);
} else {
ctx->websocket.pending = pkt;
}
ret = ws_strip_header(ctx->websocket.pending, &masked,
&ctx->websocket.masking_value,
&msg_len,
&ctx->websocket.msg_type_flag,
&header_len);
if (ret < 0) {
/* Not enough bytes for a complete websocket
* header, continue reading data.
*/
NET_DBG("[%p] pending %zd bytes, waiting more",
ctx,
net_pkt_get_len(ctx->websocket.pending));
return;
}
if (ctx->websocket.msg_type_flag & WS_FLAG_CLOSE) {
NET_DBG("[%p] Close request from peer", ctx);
http_close(ctx);
return;
}
if (ctx->websocket.msg_type_flag & WS_FLAG_PING) {
NET_DBG("[%p] Ping request from peer", ctx);
ws_send_msg(ctx, NULL, 0, WS_OPCODE_PONG,
false, true, dst, NULL);
ctx->websocket.data_waiting = 0;
return;
}
/* We have now received some data. It might not yet be
* the full data that is told by msg_len but we can
* already pass this data to caller.
*/
ctx->websocket.data_waiting = msg_len;
if (net_pkt_get_len(ctx->websocket.pending) ==
header_len) {
NET_DBG("[%p] waiting more data", ctx);
/* We do not need the websocket header
* any more, so discard it.
*/
net_pkt_unref(ctx->websocket.pending);
ctx->websocket.pending = NULL;
return;
}
/* If we have more data pending than the header len,
* then discard the header as we do not need that.
*/
net_buf_pull(ctx->websocket.pending->frags,
header_len);
pkt = ctx->websocket.pending;
ctx->websocket.pending = NULL;
net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt));
net_pkt_set_appdata(pkt, pkt->frags->data);
}
if (net_pkt_appdatalen(pkt) > ctx->websocket.data_waiting) {
/* Now we received more data which in practice means
* that we got the next websocket header.
*/
struct net_buf *hdr, *payload;
struct net_pkt *cloned;
ret = net_pkt_split(pkt, pkt->frags,
ctx->websocket.data_waiting,
&payload, &hdr,
ctx->timeout);
if (ret < 0) {
net_pkt_unref(pkt);
return;
}
net_pkt_frag_unref(pkt->frags);
pkt->frags = NULL;
cloned = net_pkt_clone(pkt, ctx->timeout);
if (!cloned) {
net_pkt_unref(pkt);
return;
}
pkt->frags = payload;
payload->frags = NULL;
net_pkt_compact(pkt);
cloned->frags = hdr;
ctx->websocket.pending = cloned;
ctx->websocket.data_waiting = 0;
net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt));
net_pkt_set_appdata(pkt, pkt->frags->data);
net_pkt_set_appdatalen(cloned, net_pkt_get_len(cloned));
net_pkt_set_appdata(cloned, cloned->frags->data);
NET_DBG("More data (%d bytes) received, pending it",
net_pkt_appdatalen(cloned));
} else {
ctx->websocket.data_waiting -= net_pkt_appdatalen(pkt);
}
if (ctx->websocket.data_waiting) {
NET_DBG("[%p] waiting still %u bytes", ctx,
ctx->websocket.data_waiting);
} else {
NET_DBG("[%p] All bytes received", ctx);
}
NET_DBG("[%p] Pass data (%d) to application for processing",
ctx, net_pkt_appdatalen(pkt));
NET_DBG("[%p] Masked %s mask 0x%04x", ctx,
masked ? "yes" : "no",
ctx->websocket.masking_value);
if (masked) {
/* Always deliver unmasked data to the application */
ws_mask_pkt(pkt,
ctx->websocket.masking_value,
&ctx->websocket.data_read);
}
ctx->cb.recv(ctx, pkt, 0, ctx->websocket.msg_type_flag,
dst, ctx->user_data);
#else
ctx->cb.recv(ctx, pkt, 0, 0, dst, ctx->user_data);
#endif
}
return;
http_ready:
ws_ready:
http_change_state(ctx, HTTP_STATE_OPEN);
url_connected(ctx, HTTP_CONNECT, dst);
url_connected(ctx, WS_CONNECTION, dst);
net_pkt_unref(pkt);
ctx->http.field_values_ctr = 0;
}
#if defined(CONFIG_HTTPS)
@ -786,7 +970,11 @@ static int on_headers_complete(struct http_parser *parser)
{
ARG_UNUSED(parser);
#if defined(CONFIG_WEBSOCKET)
return ws_headers_complete(parser);
#else
return 0;
#endif
}
static int init_http_parser(struct http_ctx *ctx)

View file

@ -0,0 +1,6 @@
# base64 support is need from mbedtls
zephyr_include_directories(. $ENV{ZEPHYR_BASE}/ext/lib/crypto/mbedtls/include/)
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_WEBSOCKET websocket.c)

View file

@ -0,0 +1,22 @@
# Copyright (c) 2017 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
menuconfig WEBSOCKET
bool "Websocket support [EXPERIMENTAL]"
default n
depends on HTTP
select NET_TCP
help
This option enables the websocket library.
if WEBSOCKET
config NET_DEBUG_WEBSOCKET
bool "Debug websocket library"
default n
help
Enables websocket library to output debug messages
endif # WEBSOCKET

View file

@ -0,0 +1,499 @@
/*
* Copyright (c) 2017 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_NET_DEBUG_WEBSOCKET)
#define SYS_LOG_DOMAIN "ws"
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#define NET_LOG_ENABLED 1
#endif
#include <zephyr.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
#include <version.h>
#include <net/net_ip.h>
#include <net/websocket.h>
#include <mbedtls/base64.h>
#include <mbedtls/sha1.h>
#define BUF_ALLOC_TIMEOUT 100
#define HTTP_CRLF "\r\n"
/* From RFC 6455 chapter 4.2.2 */
#define WS_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
static void ws_mask_payload(u8_t *payload, size_t payload_len,
u32_t masking_value)
{
int i;
for (i = 0; i < payload_len; i++) {
payload[i] ^= masking_value >> (8 * (3 - i % 4));
}
}
void ws_mask_pkt(struct net_pkt *pkt, u32_t masking_value, u32_t *data_read)
{
struct net_buf *frag;
u16_t pos;
int i;
frag = net_frag_get_pos(pkt,
net_pkt_get_len(pkt) - net_pkt_appdatalen(pkt),
&pos);
if (!frag) {
return;
}
NET_ASSERT(net_pkt_appdata(pkt) == frag->data + pos);
while (frag) {
for (i = pos; i < frag->len; i++, (*data_read)++) {
frag->data[i] ^=
masking_value >> (8 * (3 - (*data_read) % 4));
}
pos = 0;
frag = frag->frags;
}
}
int ws_send_msg(struct http_ctx *ctx, u8_t *payload, size_t payload_len,
enum ws_opcode opcode, bool mask, bool final,
const struct sockaddr *dst,
void *user_send_data)
{
u8_t header[14], hdr_len = 2;
int ret;
if (ctx->state != HTTP_STATE_OPEN) {
return -ENOTCONN;
}
if (opcode != WS_OPCODE_DATA_TEXT && opcode != WS_OPCODE_DATA_BINARY &&
opcode != WS_OPCODE_CONTINUE && opcode != WS_OPCODE_CLOSE &&
opcode != WS_OPCODE_PING && opcode != WS_OPCODE_PONG) {
return -EINVAL;
}
memset(header, 0, sizeof(header));
/* Is this the last packet? */
header[0] = final ? BIT(7) : 0;
/* Text, binary, ping, pong or close ? */
header[0] |= opcode;
/* Masking */
header[1] = mask ? BIT(7) : 0;
if (payload_len < 126) {
header[1] |= payload_len;
} else if (payload_len < 65536) {
header[1] |= 126;
header[2] = payload_len >> 8;
header[3] = payload_len;
hdr_len += 2;
} else {
header[1] |= 127;
header[2] = 0;
header[3] = 0;
header[4] = 0;
header[5] = 0;
header[6] = payload_len >> 24;
header[7] = payload_len >> 16;
header[8] = payload_len >> 8;
header[9] = payload_len;
hdr_len += 8;
}
/* Add masking value if needed */
if (mask) {
u32_t masking_value;
masking_value = sys_rand32_get();
header[hdr_len++] |= masking_value >> 24;
header[hdr_len++] |= masking_value >> 16;
header[hdr_len++] |= masking_value >> 8;
header[hdr_len++] |= masking_value;
ws_mask_payload(payload, payload_len, masking_value);
}
ret = http_prepare_and_send(ctx, header, hdr_len, dst, user_send_data);
if (ret < 0) {
NET_DBG("Cannot add ws header (%d)", ret);
goto quit;
}
if (payload) {
ret = http_prepare_and_send(ctx, payload, payload_len,
dst, user_send_data);
if (ret < 0) {
NET_DBG("Cannot send %zd bytes message (%d)",
payload_len, ret);
goto quit;
}
}
ret = http_send_flush(ctx, user_send_data);
quit:
return ret;
}
int ws_strip_header(struct net_pkt *pkt, bool *masked, u32_t *mask_value,
u32_t *message_length, u32_t *message_type_flag,
u32_t *header_len)
{
struct net_buf *frag;
u16_t value, pos, appdata_pos;
u8_t len; /* message length byte */
u8_t len_len; /* length of the length field in header */
frag = net_frag_read_be16(pkt->frags, 0, &pos, &value);
if (!frag && pos == 0xffff) {
return -ENOMSG;
}
if (value & 0x8000) {
*message_type_flag |= WS_FLAG_FINAL;
}
switch (value & 0x0f00) {
case 0x0100:
*message_type_flag |= WS_FLAG_TEXT;
break;
case 0x0200:
*message_type_flag |= WS_FLAG_BINARY;
break;
case 0x0800:
*message_type_flag |= WS_FLAG_CLOSE;
break;
case 0x0900:
*message_type_flag |= WS_FLAG_PING;
break;
case 0x0A00:
*message_type_flag |= WS_FLAG_PONG;
break;
}
len = value & 0x007f;
if (len < 126) {
len_len = 0;
*message_length = len;
} else if (len == 126) {
u16_t msg_len;
len_len = 2;
frag = net_frag_read_be16(frag, pos, &pos, &msg_len);
if (!frag && pos == 0xffff) {
return -ENOMSG;
}
*message_length = msg_len;
} else {
len_len = 4;
frag = net_frag_read_be32(frag, pos, &pos, message_length);
if (!frag && pos == 0xffff) {
return -ENOMSG;
}
}
if (value & 0x0080) {
*masked = true;
appdata_pos = 0;
frag = net_frag_read_be32(frag, pos, &pos, mask_value);
if (!frag && pos == 0xffff) {
return -ENOMSG;
}
} else {
*masked = false;
appdata_pos = len_len;
}
frag = net_frag_get_pos(pkt, pos + appdata_pos, &pos);
if (!frag && pos == 0xffff) {
return -ENOMSG;
}
/* Minimum websocket header is 6 bytes, header length might be
* bigger depending on length field len.
*/
*header_len = 6 + len_len;
return 0;
}
static bool field_contains(const char *field, int field_len,
const char *str, int str_len)
{
bool found = false;
char c, skip;
c = *str++;
if (c == '\0') {
return false;
}
str_len--;
do {
do {
skip = *field++;
field_len--;
if (skip == '\0' || field_len == 0) {
return false;
}
} while (skip != c);
if (field_len < str_len) {
return false;
}
if (strncasecmp(field, str, str_len) == 0) {
found = true;
break;
}
} while (field_len >= str_len);
return found;
}
static bool check_ws_headers(struct http_ctx *ctx, struct http_parser *parser,
int *ws_sec_key, int *host, int *subprotocol)
{
int i, count, connection = -1;
int ws_sec_version = -1;
if (!parser->upgrade || parser->method != HTTP_GET ||
parser->http_major != 1 || parser->http_minor != 1) {
return false;
}
for (i = 0, count = 0; i < ctx->http.field_values_ctr; i++) {
if (ctx->http.field_values[i].key_len == 0) {
continue;
}
if (strncasecmp(ctx->http.field_values[i].key,
"Sec-WebSocket-Key",
sizeof("Sec-WebSocket-Key") - 1) == 0) {
*ws_sec_key = i;
continue;
}
if (strncasecmp(ctx->http.field_values[i].key,
"Sec-WebSocket-Version",
sizeof("Sec-WebSocket-Version") - 1) == 0) {
if (strncmp(ctx->http.field_values[i].value,
"13", sizeof("13") - 1) == 0) {
ws_sec_version = i;
}
continue;
}
if (strncasecmp(ctx->http.field_values[i].key,
"Connection", sizeof("Connection") - 1) == 0) {
if (field_contains(
ctx->http.field_values[i].value,
ctx->http.field_values[i].value_len,
"Upgrade", sizeof("Upgrade") - 1)) {
connection = i;
}
continue;
}
if (strncasecmp(ctx->http.field_values[i].key, "Host",
sizeof("Host") - 1) == 0) {
*host = i;
continue;
}
if (strncasecmp(ctx->http.field_values[i].key,
"Sec-WebSocket-Protocol",
sizeof("Sec-WebSocket-Protocol") - 1) == 0) {
*subprotocol = i;
continue;
}
}
if (connection >= 0 && *ws_sec_key >= 0 && ws_sec_version >= 0 &&
*host >= 0) {
return true;
}
return false;
}
static struct net_pkt *prepare_reply(struct http_ctx *ctx,
int ws_sec_key, int host, int subprotocol)
{
char key_accept[32 + sizeof(WS_MAGIC) - 1];
char accept[20];
struct net_pkt *pkt;
char tmp[64];
int ret;
size_t olen;
pkt = net_app_get_net_pkt_with_dst(&ctx->app_ctx,
ctx->http.parser.addr,
ctx->timeout);
if (!pkt) {
return NULL;
}
snprintk(tmp, sizeof(tmp), "HTTP/1.1 101 OK\r\n");
if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) {
goto fail;
}
snprintk(tmp, sizeof(tmp), "User-Agent: %s\r\n", ZEPHYR_USER_AGENT);
if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) {
goto fail;
}
snprintk(tmp, sizeof(tmp), "Upgrade: websocket\r\n");
if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) {
goto fail;
}
snprintk(tmp, sizeof(tmp), "Connection: Upgrade\r\n");
if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) {
goto fail;
}
olen = min(sizeof(key_accept),
ctx->http.field_values[ws_sec_key].value_len);
strncpy(key_accept, ctx->http.field_values[ws_sec_key].value, olen);
olen = min(sizeof(key_accept) -
ctx->http.field_values[ws_sec_key].value_len,
sizeof(WS_MAGIC) - 1);
strncpy(key_accept + ctx->http.field_values[ws_sec_key].value_len,
WS_MAGIC, olen);
olen = ctx->http.field_values[ws_sec_key].value_len +
sizeof(WS_MAGIC) - 1;
mbedtls_sha1(key_accept, olen, accept);
snprintk(tmp, sizeof(tmp), "Sec-WebSocket-Accept: ");
ret = mbedtls_base64_encode(tmp + sizeof("Sec-WebSocket-Accept: ") - 1,
sizeof(tmp) -
(sizeof("Sec-WebSocket-Accept: ") - 1),
&olen, accept, sizeof(accept));
if (ret) {
if (ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
NET_DBG("[%p] Too short buffer olen %zd", ctx, olen);
}
goto fail;
}
snprintk(tmp + sizeof("Sec-WebSocket-Accept: ") - 1 + olen,
sizeof(tmp) - (sizeof("Sec-WebSocket-Accept: ") - 1) - olen,
"\r\n\r\n");
if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) {
goto fail;
}
return pkt;
fail:
net_pkt_unref(pkt);
return NULL;
}
int ws_headers_complete(struct http_parser *parser)
{
struct http_ctx *ctx = parser->data;
int ws_sec_key = -1, host = -1, subprotocol = -1;
if (check_ws_headers(ctx, parser, &ws_sec_key, &host,
&subprotocol)) {
struct net_pkt *pkt;
struct http_root_url *url;
int ret;
url = http_url_find(ctx, HTTP_URL_WEBSOCKET);
if (!url) {
url = http_url_find(ctx, HTTP_URL_STANDARD);
if (url) {
/* Normal HTTP URL was found */
return 0;
}
/* If there is no URL to serve this websocket
* request, then just bail out.
*/
if (!ctx->http.urls) {
NET_DBG("[%p] No URL handlers found", ctx);
return 0;
}
url = &ctx->http.urls->default_url;
if (url && url->is_used &&
ctx->http.urls->default_cb) {
ret = ctx->http.urls->default_cb(ctx,
WS_CONNECTION,
ctx->http.parser.addr);
if (ret == HTTP_VERDICT_ACCEPT) {
goto accept;
}
}
if (url->flags == HTTP_URL_WEBSOCKET) {
goto fail;
}
}
if (url->flags != HTTP_URL_WEBSOCKET) {
return 0;
}
accept:
NET_DBG("[%p] ws header %d fields found", ctx,
ctx->http.field_values_ctr + 1);
pkt = prepare_reply(ctx, ws_sec_key, host, subprotocol);
if (!pkt) {
goto fail;
}
net_pkt_set_appdatalen(pkt, net_buf_frags_len(pkt->frags));
ret = net_app_send_pkt(&ctx->app_ctx, pkt, NULL, 0, 0,
INT_TO_POINTER(K_FOREVER));
if (ret) {
goto fail;
}
http_change_state(ctx, HTTP_STATE_HEADER_RECEIVED);
/* We do not expect any HTTP data after this */
return 2;
fail:
http_change_state(ctx, HTTP_STATE_CLOSED);
}
return 0;
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2017 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __WEBSOCKET_INTERNAL_H__
#define __WEBSOCKET_INTERNAL_H__
#include <net/http.h>
#include <net/http_parser.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Strip websocket header from the packet.
*
* @details The function will remove websocket header from the network packet.
*
* @param pkt Received network packet
* @param masked The mask status of the message is returned.
* @param mask_value The mask value of the message is returned.
* @param message_length Total length of the message from websocket header.
* @param message_type_flag Type of the websocket message (WS_FLAG_xxx value)
* @param header_len Length of the websocket header is returned to caller.
*
* @return 0 if ok, <0 if error
*/
int ws_strip_header(struct net_pkt *pkt, bool *masked, u32_t *mask_value,
u32_t *message_length, u32_t *message_type_flag,
u32_t *header_len);
/**
* @brief Mask or unmask a websocket message if needed
*
* @details The function will either add or remove the masking from the data.
*
* @param pkt Network packet to process
* @param masking_value The mask value to use.
* @param data_read How many bytes we have read. This is modified by this
* function.
*/
void ws_mask_pkt(struct net_pkt *pkt, u32_t masking_value, u32_t *data_read);
/**
* @brief This is called by HTTP server after all the HTTP headers have been
* received.
*
* @details The function will check if this is a valid websocket connection
* or not.
*
* @param parser HTTP parser instance
*
* @return 0 if ok, 1 if there is no body, 2 if HTTP connection is to be
* upgraded to websocket one
*/
int ws_headers_complete(struct http_parser *parser);
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* __WS_H__ */