diff --git a/include/net/http.h b/include/net/http.h index 373caf75cd7..13d451a13f1 100644 --- a/include/net/http.h +++ b/include/net/http.h @@ -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 */ diff --git a/include/net/websocket.h b/include/net/websocket.h new file mode 100644 index 00000000000..880ac178c10 --- /dev/null +++ b/include/net/websocket.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __WEBSOCKET_H__ +#define __WEBSOCKET_H__ + +#include + +#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__ */ diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 48990f18565..5296b3aa08e 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -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 diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index ebe3fe5920c..8bd37062d9a 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -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" diff --git a/subsys/net/lib/http/Kconfig b/subsys/net/lib/http/Kconfig index c6b2ff44738..b408233d448 100644 --- a/subsys/net/lib/http/Kconfig +++ b/subsys/net/lib/http/Kconfig @@ -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" diff --git a/subsys/net/lib/http/http.c b/subsys/net/lib/http/http.c index e63cf259ae3..a73ac5f5ea6 100644 --- a/subsys/net/lib/http/http.c +++ b/subsys/net/lib/http/http.c @@ -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); } diff --git a/subsys/net/lib/http/http_server.c b/subsys/net/lib/http/http_server.c index e167bba1423..a175d66491b 100644 --- a/subsys/net/lib/http/http_server.c +++ b/subsys/net/lib/http/http_server.c @@ -25,6 +25,11 @@ #include #include +#if defined(CONFIG_WEBSOCKET) +#include +#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) diff --git a/subsys/net/lib/websocket/CMakeLists.txt b/subsys/net/lib/websocket/CMakeLists.txt new file mode 100644 index 00000000000..64d9cf14df3 --- /dev/null +++ b/subsys/net/lib/websocket/CMakeLists.txt @@ -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) diff --git a/subsys/net/lib/websocket/Kconfig b/subsys/net/lib/websocket/Kconfig new file mode 100644 index 00000000000..18b48170d25 --- /dev/null +++ b/subsys/net/lib/websocket/Kconfig @@ -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 diff --git a/subsys/net/lib/websocket/websocket.c b/subsys/net/lib/websocket/websocket.c new file mode 100644 index 00000000000..ab8c76deaf1 --- /dev/null +++ b/subsys/net/lib/websocket/websocket.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#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; +} diff --git a/subsys/net/lib/websocket/websocket_internal.h b/subsys/net/lib/websocket/websocket_internal.h new file mode 100644 index 00000000000..b4ecb069f71 --- /dev/null +++ b/subsys/net/lib/websocket/websocket_internal.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __WEBSOCKET_INTERNAL_H__ +#define __WEBSOCKET_INTERNAL_H__ + +#include +#include + +#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__ */