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

@ -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)