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:
parent
0ecd77e435
commit
d70c7383de
11 changed files with 929 additions and 8 deletions
|
@ -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
102
include/net/websocket.h
Normal 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__ */
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
6
subsys/net/lib/websocket/CMakeLists.txt
Normal file
6
subsys/net/lib/websocket/CMakeLists.txt
Normal 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)
|
22
subsys/net/lib/websocket/Kconfig
Normal file
22
subsys/net/lib/websocket/Kconfig
Normal 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
|
499
subsys/net/lib/websocket/websocket.c
Normal file
499
subsys/net/lib/websocket/websocket.c
Normal 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;
|
||||
}
|
69
subsys/net/lib/websocket/websocket_internal.h
Normal file
69
subsys/net/lib/websocket/websocket_internal.h
Normal 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__ */
|
Loading…
Add table
Add a link
Reference in a new issue