net: websocket: client: Simple API for Websocket client
Implement simple API to do Websocket client requests. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
parent
a07045d8b2
commit
6af987646f
8 changed files with 1344 additions and 0 deletions
197
include/net/websocket.h
Normal file
197
include/net/websocket.h
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
/** @file
|
||||||
|
* @brief Websocket API
|
||||||
|
*
|
||||||
|
* An API for applications to setup websocket connections
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZEPHYR_INCLUDE_NET_WEBSOCKET_H_
|
||||||
|
#define ZEPHYR_INCLUDE_NET_WEBSOCKET_H_
|
||||||
|
|
||||||
|
#include <kernel.h>
|
||||||
|
|
||||||
|
#include <net/net_ip.h>
|
||||||
|
#include <net/http_parser.h>
|
||||||
|
#include <net/http_client.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Websocket API
|
||||||
|
* @defgroup websocket Websocket API
|
||||||
|
* @ingroup networking
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Message type values. Returned in websocket_recv_msg() */
|
||||||
|
#define WEBSOCKET_FLAG_FINAL 0x00000001 /**< Final frame */
|
||||||
|
#define WEBSOCKET_FLAG_TEXT 0x00000002 /**< Textual data */
|
||||||
|
#define WEBSOCKET_FLAG_BINARY 0x00000004 /**< Binary data */
|
||||||
|
#define WEBSOCKET_FLAG_CLOSE 0x00000008 /**< Closing connection */
|
||||||
|
#define WEBSOCKET_FLAG_PING 0x00000010 /**< Ping message */
|
||||||
|
#define WEBSOCKET_FLAG_PONG 0x00000011 /**< Pong message */
|
||||||
|
|
||||||
|
enum websocket_opcode {
|
||||||
|
WEBSOCKET_OPCODE_CONTINUE = 0x00,
|
||||||
|
WEBSOCKET_OPCODE_DATA_TEXT = 0x01,
|
||||||
|
WEBSOCKET_OPCODE_DATA_BINARY = 0x02,
|
||||||
|
WEBSOCKET_OPCODE_CLOSE = 0x08,
|
||||||
|
WEBSOCKET_OPCODE_PING = 0x09,
|
||||||
|
WEBSOCKET_OPCODE_PONG = 0x0A,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef websocket_connect_cb_t
|
||||||
|
* @brief Callback called after Websocket connection is established.
|
||||||
|
*
|
||||||
|
* @param sock Websocket id
|
||||||
|
* @param req HTTP handshake request
|
||||||
|
* @param user_data A valid pointer on some user data or NULL
|
||||||
|
*
|
||||||
|
* @return 0 if ok, <0 if there is an error and connection should be aborted
|
||||||
|
*/
|
||||||
|
typedef int (*websocket_connect_cb_t)(int ws_sock, struct http_request *req,
|
||||||
|
void *user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Websocket client connection request. This contains all the data that is
|
||||||
|
* needed when doing a Websocket connection request.
|
||||||
|
*/
|
||||||
|
struct websocket_request {
|
||||||
|
/** Host of the Websocket server when doing HTTP handshakes. */
|
||||||
|
const char *host;
|
||||||
|
|
||||||
|
/** URL of the Websocket. */
|
||||||
|
const char *url;
|
||||||
|
|
||||||
|
/** User supplied callback function to call when optional headers need
|
||||||
|
* to be sent. This can be NULL, in which case the optional_headers
|
||||||
|
* field in http_request is used. The idea of this optional_headers
|
||||||
|
* callback is to allow user to send more HTTP header data that is
|
||||||
|
* practical to store in allocated memory.
|
||||||
|
*/
|
||||||
|
http_header_cb_t optional_headers_cb;
|
||||||
|
|
||||||
|
/** A NULL terminated list of any optional headers that
|
||||||
|
* should be added to the HTTP request. May be NULL.
|
||||||
|
* If the optional_headers_cb is specified, then this field is ignored.
|
||||||
|
*/
|
||||||
|
const char **optional_headers;
|
||||||
|
|
||||||
|
/** User supplied callback function to call when a connection is
|
||||||
|
* established.
|
||||||
|
*/
|
||||||
|
websocket_connect_cb_t cb;
|
||||||
|
|
||||||
|
/** User supplied list of callback functions if the calling application
|
||||||
|
* wants to know the parsing status or the HTTP fields during the
|
||||||
|
* handshake. This is optional parameter and normally not needed but
|
||||||
|
* is useful if the caller wants to know something about
|
||||||
|
* the fields that the server is sending.
|
||||||
|
*/
|
||||||
|
const struct http_parser_settings *http_cb;
|
||||||
|
|
||||||
|
/** User supplied buffer where HTTP connection data is stored */
|
||||||
|
u8_t *tmp_buf;
|
||||||
|
|
||||||
|
/** Length of the user supplied temp buffer */
|
||||||
|
size_t tmp_buf_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connect to a server that provides Websocket service. The callback is
|
||||||
|
* called after connection is established. The returned value is a new socket
|
||||||
|
* descriptor that can be used to send / receive data using the BSD socket API.
|
||||||
|
*
|
||||||
|
* @param http_sock Socket id to the server. Note that this socket is used to do
|
||||||
|
* HTTP handshakes etc. The actual Websocket connectivity is done via the
|
||||||
|
* returned websocket id. Note that the http_sock must not be closed
|
||||||
|
* after this function returns as it is used to deliver the Websocket
|
||||||
|
* packets to the Websocket server.
|
||||||
|
* @param req Websocket request. User should allocate and fill the request
|
||||||
|
* data.
|
||||||
|
* @param timeout Max timeout to wait for the connection. The timeout value
|
||||||
|
* cannot be 0 as there would be no time to receive the data.
|
||||||
|
* @param user_data User specified data that is passed to the callback.
|
||||||
|
*
|
||||||
|
* @return Websocket id to be used when sending/receiving Websocket data.
|
||||||
|
*/
|
||||||
|
int websocket_connect(int http_sock, struct websocket_request *req,
|
||||||
|
s32_t timeout, void *user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send websocket msg to peer.
|
||||||
|
*
|
||||||
|
* @details The function will automatically add websocket header to the
|
||||||
|
* message.
|
||||||
|
*
|
||||||
|
* @param ws_sock Websocket id returned by websocket_connect().
|
||||||
|
* @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 WEBSOCKET_OPCODE_CONTINUE. If final == true and this
|
||||||
|
* is the only message, then opcode should have proper opcode (text or
|
||||||
|
* binary) set.
|
||||||
|
* @param timeout How long to try to send the message.
|
||||||
|
*
|
||||||
|
* @return <0 if error, >=0 amount of bytes sent
|
||||||
|
*/
|
||||||
|
int websocket_send_msg(int ws_sock, const u8_t *payload, size_t payload_len,
|
||||||
|
enum websocket_opcode opcode, bool mask, bool final,
|
||||||
|
s32_t timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Receive websocket msg from peer.
|
||||||
|
*
|
||||||
|
* @details The function will automatically remove websocket header from the
|
||||||
|
* message.
|
||||||
|
*
|
||||||
|
* @param ws_sock Websocket id returned by websocket_connect().
|
||||||
|
* @param buf Buffer where websocket data is read.
|
||||||
|
* @param buf_len Length of the data buffer.
|
||||||
|
* @param message_type Type of the message.
|
||||||
|
* @param remaining How much there is data left in the message after this read.
|
||||||
|
* @param timeout How long to try to receive the message.
|
||||||
|
*
|
||||||
|
* @return <0 if error, >=0 amount of bytes received
|
||||||
|
*/
|
||||||
|
int websocket_recv_msg(int ws_sock, u8_t *buf, size_t buf_len,
|
||||||
|
u32_t *message_type, u64_t *remaining, s32_t timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Close websocket.
|
||||||
|
*
|
||||||
|
* @details One must call websocket_connect() after this call to re-establish
|
||||||
|
* the connection.
|
||||||
|
*
|
||||||
|
* @param ws_sock Websocket id returned by websocket_connect().
|
||||||
|
*/
|
||||||
|
int websocket_disconnect(int ws_sock);
|
||||||
|
|
||||||
|
#if defined(CONFIG_WEBSOCKET_CLIENT)
|
||||||
|
void websocket_init(void);
|
||||||
|
#else
|
||||||
|
static inline void websocket_init(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endif /* ZEPHYR_INCLUDE_NET_WEBSOCKET_H_ */
|
|
@ -27,6 +27,7 @@ LOG_MODULE_REGISTER(net_core, CONFIG_NET_CORE_LOG_LEVEL);
|
||||||
#include <net/net_core.h>
|
#include <net/net_core.h>
|
||||||
#include <net/dns_resolve.h>
|
#include <net/dns_resolve.h>
|
||||||
#include <net/gptp.h>
|
#include <net/gptp.h>
|
||||||
|
#include <net/websocket.h>
|
||||||
|
|
||||||
#if defined(CONFIG_NET_LLDP)
|
#if defined(CONFIG_NET_LLDP)
|
||||||
#include <net/lldp.h>
|
#include <net/lldp.h>
|
||||||
|
@ -450,6 +451,7 @@ static inline int services_init(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
dns_init_resolver();
|
dns_init_resolver();
|
||||||
|
websocket_init();
|
||||||
|
|
||||||
net_shell_init();
|
net_shell_init();
|
||||||
|
|
||||||
|
|
|
@ -21,4 +21,5 @@ if(CONFIG_HTTP_PARSER_URL OR CONFIG_HTTP_PARSER OR CONFIG_HTTP_CLIENT)
|
||||||
add_subdirectory(http)
|
add_subdirectory(http)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory_ifdef(CONFIG_WEBSOCKET_CLIENT websocket)
|
||||||
add_subdirectory_ifdef(CONFIG_OPENTHREAD_PLAT openthread)
|
add_subdirectory_ifdef(CONFIG_OPENTHREAD_PLAT openthread)
|
||||||
|
|
|
@ -14,6 +14,8 @@ source "subsys/net/lib/mqtt/Kconfig"
|
||||||
|
|
||||||
source "subsys/net/lib/http/Kconfig"
|
source "subsys/net/lib/http/Kconfig"
|
||||||
|
|
||||||
|
source "subsys/net/lib/websocket/Kconfig"
|
||||||
|
|
||||||
source "subsys/net/lib/lwm2m/Kconfig"
|
source "subsys/net/lib/lwm2m/Kconfig"
|
||||||
|
|
||||||
source "subsys/net/lib/socks/Kconfig"
|
source "subsys/net/lib/socks/Kconfig"
|
||||||
|
|
10
subsys/net/lib/websocket/CMakeLists.txt
Normal file
10
subsys/net/lib/websocket/CMakeLists.txt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
zephyr_library_named(websocket_client)
|
||||||
|
zephyr_include_directories(${ZEPHYR_BASE}/subsys/net/lib/sockets)
|
||||||
|
|
||||||
|
zephyr_library_sources(
|
||||||
|
websocket.c
|
||||||
|
)
|
||||||
|
|
||||||
|
zephyr_library_link_libraries_ifdef(CONFIG_MBEDTLS mbedTLS)
|
31
subsys/net/lib/websocket/Kconfig
Normal file
31
subsys/net/lib/websocket/Kconfig
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Copyright (c) 2019 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
menuconfig WEBSOCKET_CLIENT
|
||||||
|
bool "Websocket client support [EXPERIMENTAL]"
|
||||||
|
select NET_SOCKETS
|
||||||
|
select HTTP_PARSER
|
||||||
|
select HTTP_PARSER_URL
|
||||||
|
select HTTP_CLIENT
|
||||||
|
select MBEDTLS
|
||||||
|
select BASE64
|
||||||
|
help
|
||||||
|
Enable Websocket client library.
|
||||||
|
|
||||||
|
if WEBSOCKET_CLIENT
|
||||||
|
|
||||||
|
config WEBSOCKET_MAX_CONTEXTS
|
||||||
|
int "Max number of websockets to allocate"
|
||||||
|
default 1
|
||||||
|
help
|
||||||
|
How many Websockets can be created in the system.
|
||||||
|
|
||||||
|
module = NET_WEBSOCKET
|
||||||
|
module-dep = NET_LOG
|
||||||
|
module-str = Log level for Websocket
|
||||||
|
module-help = Enable debug message of Websocket client library.
|
||||||
|
source "subsys/net/Kconfig.template.log_config.net"
|
||||||
|
|
||||||
|
endif
|
962
subsys/net/lib/websocket/websocket.c
Normal file
962
subsys/net/lib/websocket/websocket.c
Normal file
|
@ -0,0 +1,962 @@
|
||||||
|
/** @file
|
||||||
|
* @brief Websocket client API
|
||||||
|
*
|
||||||
|
* An API for applications to setup a websocket connections.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(net_websocket, CONFIG_NET_WEBSOCKET_LOG_LEVEL);
|
||||||
|
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <sys/fdtable.h>
|
||||||
|
#include <net/net_core.h>
|
||||||
|
#include <net/net_ip.h>
|
||||||
|
#include <net/socket.h>
|
||||||
|
#include <net/http_client.h>
|
||||||
|
#include <net/websocket.h>
|
||||||
|
|
||||||
|
#include <sys/byteorder.h>
|
||||||
|
#include <base64.h>
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
|
|
||||||
|
#include "net_private.h"
|
||||||
|
#include "sockets_internal.h"
|
||||||
|
#include "websocket_internal.h"
|
||||||
|
|
||||||
|
/* If you want to see the data that is being sent or received,
|
||||||
|
* then you can enable debugging and set the following variables to 1.
|
||||||
|
* This will print a lot of data so is not enabled by default.
|
||||||
|
*/
|
||||||
|
#define HEXDUMP_SENT_PACKETS 0
|
||||||
|
#define HEXDUMP_RECV_PACKETS 0
|
||||||
|
|
||||||
|
static struct websocket_context contexts[CONFIG_WEBSOCKET_MAX_CONTEXTS];
|
||||||
|
|
||||||
|
static struct k_sem contexts_lock;
|
||||||
|
|
||||||
|
extern const struct socket_op_vtable sock_fd_op_vtable;
|
||||||
|
static const struct socket_op_vtable websocket_fd_op_vtable;
|
||||||
|
|
||||||
|
static const char *opcode2str(enum websocket_opcode opcode)
|
||||||
|
{
|
||||||
|
switch (opcode) {
|
||||||
|
case WEBSOCKET_OPCODE_DATA_TEXT:
|
||||||
|
return "TEXT";
|
||||||
|
case WEBSOCKET_OPCODE_DATA_BINARY:
|
||||||
|
return "BIN";
|
||||||
|
case WEBSOCKET_OPCODE_CONTINUE:
|
||||||
|
return "CONT";
|
||||||
|
case WEBSOCKET_OPCODE_CLOSE:
|
||||||
|
return "CLOSE";
|
||||||
|
case WEBSOCKET_OPCODE_PING:
|
||||||
|
return "PING";
|
||||||
|
case WEBSOCKET_OPCODE_PONG:
|
||||||
|
return "PONG";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int websocket_context_ref(struct websocket_context *ctx)
|
||||||
|
{
|
||||||
|
int old_rc = atomic_inc(&ctx->refcount);
|
||||||
|
|
||||||
|
return old_rc + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int websocket_context_unref(struct websocket_context *ctx)
|
||||||
|
{
|
||||||
|
int old_rc = atomic_dec(&ctx->refcount);
|
||||||
|
|
||||||
|
if (old_rc != 1) {
|
||||||
|
return old_rc - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool websocket_context_is_used(struct websocket_context *ctx)
|
||||||
|
{
|
||||||
|
NET_ASSERT(ctx);
|
||||||
|
|
||||||
|
return !!atomic_get(&ctx->refcount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct websocket_context *websocket_get(void)
|
||||||
|
{
|
||||||
|
struct websocket_context *ctx = NULL;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
k_sem_take(&contexts_lock, K_FOREVER);
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(contexts); i++) {
|
||||||
|
if (websocket_context_is_used(&contexts[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
websocket_context_ref(&contexts[i]);
|
||||||
|
ctx = &contexts[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_sem_give(&contexts_lock);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct websocket_context *websocket_find(int real_sock)
|
||||||
|
{
|
||||||
|
struct websocket_context *ctx = NULL;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
k_sem_take(&contexts_lock, K_FOREVER);
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(contexts); i++) {
|
||||||
|
if (!websocket_context_is_used(&contexts[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contexts[i].real_sock != real_sock) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = &contexts[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_sem_give(&contexts_lock);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void response_cb(struct http_response *rsp,
|
||||||
|
enum http_final_call final_data,
|
||||||
|
void *user_data)
|
||||||
|
{
|
||||||
|
struct websocket_context *ctx = user_data;
|
||||||
|
|
||||||
|
if (final_data == HTTP_DATA_MORE) {
|
||||||
|
NET_DBG("[%p] Partial data received (%zd bytes)", ctx,
|
||||||
|
rsp->data_len);
|
||||||
|
ctx->all_received = false;
|
||||||
|
} else if (final_data == HTTP_DATA_FINAL) {
|
||||||
|
NET_DBG("[%p] All the data received (%zd bytes)", ctx,
|
||||||
|
rsp->data_len);
|
||||||
|
ctx->all_received = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_header_field(struct http_parser *parser, const char *at,
|
||||||
|
size_t length)
|
||||||
|
{
|
||||||
|
struct http_request *req = CONTAINER_OF(parser,
|
||||||
|
struct http_request,
|
||||||
|
internal.parser);
|
||||||
|
struct websocket_context *ctx = req->internal.user_data;
|
||||||
|
const char *ws_accept_str = "Sec-WebSocket-Accept";
|
||||||
|
u16_t len;
|
||||||
|
|
||||||
|
len = strlen(ws_accept_str);
|
||||||
|
if (length >= len && strncasecmp(at, ws_accept_str, len) == 0) {
|
||||||
|
ctx->sec_accept_present = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->http_cb && ctx->http_cb->on_header_field) {
|
||||||
|
ctx->http_cb->on_header_field(parser, at, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MAX_SEC_ACCEPT_LEN 32
|
||||||
|
|
||||||
|
static int on_header_value(struct http_parser *parser, const char *at,
|
||||||
|
size_t length)
|
||||||
|
{
|
||||||
|
struct http_request *req = CONTAINER_OF(parser,
|
||||||
|
struct http_request,
|
||||||
|
internal.parser);
|
||||||
|
struct websocket_context *ctx = req->internal.user_data;
|
||||||
|
char str[MAX_SEC_ACCEPT_LEN];
|
||||||
|
|
||||||
|
if (ctx->sec_accept_present) {
|
||||||
|
int ret;
|
||||||
|
size_t olen;
|
||||||
|
|
||||||
|
ctx->sec_accept_ok = false;
|
||||||
|
ctx->sec_accept_present = false;
|
||||||
|
|
||||||
|
ret = base64_encode(str, sizeof(str) - 1, &olen,
|
||||||
|
ctx->sec_accept_key,
|
||||||
|
WS_SHA1_OUTPUT_LEN);
|
||||||
|
if (ret == 0) {
|
||||||
|
if (strncmp(at, str, length)) {
|
||||||
|
NET_DBG("[%p] Security keys do not match "
|
||||||
|
"%s vs %s", ctx, str, at);
|
||||||
|
} else {
|
||||||
|
ctx->sec_accept_ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->http_cb && ctx->http_cb->on_header_value) {
|
||||||
|
ctx->http_cb->on_header_value(parser, at, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int websocket_connect(int sock, struct websocket_request *wreq,
|
||||||
|
s32_t timeout, void *user_data)
|
||||||
|
{
|
||||||
|
/* This is the expected Sec-WebSocket-Accept key. We are storing a
|
||||||
|
* pointer to this in ctx but the value is only used for the duration
|
||||||
|
* of this function call so there is no issue even if this variable
|
||||||
|
* is allocated from stack.
|
||||||
|
*/
|
||||||
|
u8_t sec_accept_key[WS_SHA1_OUTPUT_LEN];
|
||||||
|
struct http_parser_settings http_parser_settings;
|
||||||
|
struct websocket_context *ctx;
|
||||||
|
struct http_request req;
|
||||||
|
int ret, fd, key_len;
|
||||||
|
size_t olen;
|
||||||
|
char key_accept[MAX_SEC_ACCEPT_LEN + sizeof(WS_MAGIC)];
|
||||||
|
u32_t rnd_value = sys_rand32_get();
|
||||||
|
char sec_ws_key[] =
|
||||||
|
"Sec-WebSocket-Key: 0123456789012345678901==\r\n";
|
||||||
|
char *headers[] = {
|
||||||
|
sec_ws_key,
|
||||||
|
"Upgrade: websocket\r\n",
|
||||||
|
"Connection: Upgrade\r\n",
|
||||||
|
"Sec-WebSocket-Version: 13\r\n",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
fd = -1;
|
||||||
|
|
||||||
|
if (sock < 0 || wreq == NULL || wreq->host == NULL ||
|
||||||
|
wreq->url == NULL) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = websocket_find(sock);
|
||||||
|
if (ctx) {
|
||||||
|
NET_DBG("[%p] Websocket for sock %d already exists!", ctx,
|
||||||
|
sock);
|
||||||
|
return -EEXIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = websocket_get();
|
||||||
|
if (!ctx) {
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->real_sock = sock;
|
||||||
|
ctx->tmp_buf = wreq->tmp_buf;
|
||||||
|
ctx->tmp_buf_len = wreq->tmp_buf_len;
|
||||||
|
ctx->timeout = timeout;
|
||||||
|
ctx->sec_accept_key = sec_accept_key;
|
||||||
|
ctx->http_cb = wreq->http_cb;
|
||||||
|
|
||||||
|
mbedtls_sha1_ret((const unsigned char *)&rnd_value, sizeof(rnd_value),
|
||||||
|
sec_accept_key);
|
||||||
|
|
||||||
|
ret = base64_encode(sec_ws_key + sizeof("Sec-Websocket-Key: ") - 1,
|
||||||
|
sizeof(sec_ws_key) -
|
||||||
|
sizeof("Sec-Websocket-Key: "),
|
||||||
|
&olen, sec_accept_key,
|
||||||
|
/* We are only interested in 16 first bytes so
|
||||||
|
* substract 4 from the SHA-1 length
|
||||||
|
*/
|
||||||
|
sizeof(sec_accept_key) - 4);
|
||||||
|
if (ret) {
|
||||||
|
NET_DBG("[%p] Cannot encode base64 (%d)", ctx, ret);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((olen + sizeof("Sec-Websocket-Key: ") + 2) > sizeof(sec_ws_key)) {
|
||||||
|
NET_DBG("[%p] Too long message (%zd > %zd)", ctx,
|
||||||
|
olen + sizeof("Sec-Websocket-Key: ") + 2,
|
||||||
|
sizeof(sec_ws_key));
|
||||||
|
ret = -EMSGSIZE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(sec_ws_key + sizeof("Sec-Websocket-Key: ") - 1 + olen,
|
||||||
|
HTTP_CRLF, sizeof(HTTP_CRLF));
|
||||||
|
|
||||||
|
memset(&req, 0, sizeof(req));
|
||||||
|
|
||||||
|
req.method = HTTP_GET;
|
||||||
|
req.url = wreq->url;
|
||||||
|
req.host = wreq->host;
|
||||||
|
req.protocol = "HTTP/1.1";
|
||||||
|
req.header_fields = (const char **)headers;
|
||||||
|
req.optional_headers_cb = wreq->optional_headers_cb;
|
||||||
|
req.optional_headers = wreq->optional_headers;
|
||||||
|
req.response = response_cb;
|
||||||
|
req.http_cb = &http_parser_settings;
|
||||||
|
req.recv_buf = wreq->tmp_buf;
|
||||||
|
req.recv_buf_len = wreq->tmp_buf_len;
|
||||||
|
|
||||||
|
/* We need to catch the Sec-WebSocket-Accept field in order to verify
|
||||||
|
* that it contains the stuff that we sent in Sec-WebSocket-Key field
|
||||||
|
* so setup HTTP callbacks so that we will get the needed fields.
|
||||||
|
*/
|
||||||
|
if (ctx->http_cb) {
|
||||||
|
memcpy(&http_parser_settings, ctx->http_cb,
|
||||||
|
sizeof(http_parser_settings));
|
||||||
|
} else {
|
||||||
|
memset(&http_parser_settings, 0, sizeof(http_parser_settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
http_parser_settings.on_header_field = on_header_field;
|
||||||
|
http_parser_settings.on_header_value = on_header_value;
|
||||||
|
|
||||||
|
/* Pre-calculate the expected Sec-Websocket-Accept field */
|
||||||
|
key_len = MIN(sizeof(key_accept) - 1, olen);
|
||||||
|
strncpy(key_accept, sec_ws_key + sizeof("Sec-Websocket-Key: ") - 1,
|
||||||
|
key_len);
|
||||||
|
|
||||||
|
olen = MIN(sizeof(key_accept) - 1 - key_len, sizeof(WS_MAGIC) - 1);
|
||||||
|
strncpy(key_accept + key_len, WS_MAGIC, olen);
|
||||||
|
|
||||||
|
/* This SHA-1 value is then checked when we receive the response */
|
||||||
|
mbedtls_sha1_ret(key_accept, olen + key_len, sec_accept_key);
|
||||||
|
|
||||||
|
ret = http_client_req(sock, &req, timeout, ctx);
|
||||||
|
if (ret < 0) {
|
||||||
|
NET_DBG("[%p] Cannot connect to Websocket host %s", ctx,
|
||||||
|
wreq->host);
|
||||||
|
ret = -ECONNABORTED;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(ctx->all_received && ctx->sec_accept_ok)) {
|
||||||
|
NET_DBG("[%p] WS handshake failed (%d/%d)", ctx,
|
||||||
|
ctx->all_received, ctx->sec_accept_ok);
|
||||||
|
ret = -ECONNABORTED;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->user_data = user_data;
|
||||||
|
|
||||||
|
fd = z_reserve_fd();
|
||||||
|
if (fd < 0) {
|
||||||
|
ret = -ENOSPC;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->sock = fd;
|
||||||
|
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
/* Set net context object as initialized and grant access to the
|
||||||
|
* calling thread (and only the calling thread)
|
||||||
|
*/
|
||||||
|
z_object_recycle(ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
z_finalize_fd(fd, ctx,
|
||||||
|
(const struct fd_op_vtable *)&websocket_fd_op_vtable);
|
||||||
|
|
||||||
|
/* Call the user specified callback and if it accepts the connection
|
||||||
|
* then continue.
|
||||||
|
*/
|
||||||
|
if (wreq->cb) {
|
||||||
|
ret = wreq->cb(fd, &req, user_data);
|
||||||
|
if (ret < 0) {
|
||||||
|
NET_DBG("[%p] Connection aborted (%d)", ctx, ret);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NET_DBG("[%p] WS connection to peer established (fd %d)", ctx, fd);
|
||||||
|
|
||||||
|
/* We will re-use the temp buffer in receive function if needed but
|
||||||
|
* in order that to work the length must be set to 0
|
||||||
|
*/
|
||||||
|
ctx->tmp_buf_len = 0;
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (fd >= 0) {
|
||||||
|
(void)close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
websocket_context_unref(ctx);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int websocket_disconnect(int ws_sock)
|
||||||
|
{
|
||||||
|
struct websocket_context *ctx;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ctx = z_get_fd_obj(ws_sock, NULL, 0);
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
NET_DBG("[%p] Disconnecting", ctx);
|
||||||
|
|
||||||
|
(void)close(ctx->sock);
|
||||||
|
|
||||||
|
ret = close(ctx->real_sock);
|
||||||
|
|
||||||
|
websocket_context_unref(ctx);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int websocket_ioctl_vmeth(void *obj, unsigned int request, va_list args)
|
||||||
|
{
|
||||||
|
if (request == ZFD_IOCTL_CLOSE) {
|
||||||
|
struct websocket_context *ctx = obj;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = websocket_disconnect(ctx->sock);
|
||||||
|
if (ret < 0) {
|
||||||
|
NET_DBG("[%p] Cannot close (%d)", obj, ret);
|
||||||
|
|
||||||
|
errno = -ret;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock_fd_op_vtable.fd_vtable.ioctl(obj, request, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void websocket_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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int websocket_prepare_and_send(struct websocket_context *ctx,
|
||||||
|
u8_t *header, size_t header_len,
|
||||||
|
u8_t *payload, size_t payload_len,
|
||||||
|
s32_t timeout)
|
||||||
|
{
|
||||||
|
struct iovec io_vector[2];
|
||||||
|
struct msghdr msg;
|
||||||
|
|
||||||
|
io_vector[0].iov_base = header;
|
||||||
|
io_vector[0].iov_len = header_len;
|
||||||
|
io_vector[1].iov_base = payload;
|
||||||
|
io_vector[1].iov_len = payload_len;
|
||||||
|
|
||||||
|
memset(&msg, 0, sizeof(msg));
|
||||||
|
|
||||||
|
msg.msg_iov = io_vector;
|
||||||
|
msg.msg_iovlen = ARRAY_SIZE(io_vector);
|
||||||
|
|
||||||
|
if (HEXDUMP_SENT_PACKETS) {
|
||||||
|
LOG_HEXDUMP_DBG(header, header_len, "Header");
|
||||||
|
LOG_HEXDUMP_DBG(payload, payload_len, "Payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendmsg(ctx->real_sock, &msg,
|
||||||
|
timeout == K_NO_WAIT ? MSG_DONTWAIT : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int websocket_send_msg(int ws_sock, const u8_t *payload, size_t payload_len,
|
||||||
|
enum websocket_opcode opcode, bool mask, bool final,
|
||||||
|
s32_t timeout)
|
||||||
|
{
|
||||||
|
struct websocket_context *ctx;
|
||||||
|
u8_t header[MAX_HEADER_LEN], hdr_len = 2;
|
||||||
|
u8_t *data_to_send = (u8_t *)payload;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (opcode != WEBSOCKET_OPCODE_DATA_TEXT &&
|
||||||
|
opcode != WEBSOCKET_OPCODE_DATA_BINARY &&
|
||||||
|
opcode != WEBSOCKET_OPCODE_CONTINUE &&
|
||||||
|
opcode != WEBSOCKET_OPCODE_CLOSE &&
|
||||||
|
opcode != WEBSOCKET_OPCODE_PING &&
|
||||||
|
opcode != WEBSOCKET_OPCODE_PONG) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = z_get_fd_obj(ws_sock, NULL, 0);
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return -EBADF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PART_OF_ARRAY(contexts, ctx)) {
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
NET_DBG("[%p] Len %zd %s/%d/%s", ctx, payload_len, opcode2str(opcode),
|
||||||
|
mask, final ? "final" : "more");
|
||||||
|
|
||||||
|
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) {
|
||||||
|
ctx->masking_value = sys_rand32_get();
|
||||||
|
|
||||||
|
header[hdr_len++] |= ctx->masking_value >> 24;
|
||||||
|
header[hdr_len++] |= ctx->masking_value >> 16;
|
||||||
|
header[hdr_len++] |= ctx->masking_value >> 8;
|
||||||
|
header[hdr_len++] |= ctx->masking_value;
|
||||||
|
|
||||||
|
data_to_send = k_malloc(payload_len);
|
||||||
|
if (!data_to_send) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data_to_send, payload, payload_len);
|
||||||
|
|
||||||
|
websocket_mask_payload(data_to_send, payload_len,
|
||||||
|
ctx->masking_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = websocket_prepare_and_send(ctx, header, hdr_len,
|
||||||
|
data_to_send, payload_len, timeout);
|
||||||
|
if (ret < 0) {
|
||||||
|
NET_DBG("Cannot send ws msg (%d)", -errno);
|
||||||
|
goto quit;
|
||||||
|
}
|
||||||
|
|
||||||
|
quit:
|
||||||
|
if (data_to_send != payload) {
|
||||||
|
k_free(data_to_send);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret - hdr_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool websocket_parse_header(u8_t *buf, size_t buf_len, bool *masked,
|
||||||
|
u32_t *mask_value, u64_t *message_length,
|
||||||
|
u32_t *message_type_flag,
|
||||||
|
size_t *header_len)
|
||||||
|
{
|
||||||
|
u8_t len_len; /* length of the length field in header */
|
||||||
|
u8_t len; /* message length byte */
|
||||||
|
u16_t value;
|
||||||
|
|
||||||
|
value = sys_get_be16(&buf[0]);
|
||||||
|
if (value & 0x8000) {
|
||||||
|
*message_type_flag |= WEBSOCKET_FLAG_FINAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (value & 0x0f00) {
|
||||||
|
case 0x0100:
|
||||||
|
*message_type_flag |= WEBSOCKET_FLAG_TEXT;
|
||||||
|
break;
|
||||||
|
case 0x0200:
|
||||||
|
*message_type_flag |= WEBSOCKET_FLAG_BINARY;
|
||||||
|
break;
|
||||||
|
case 0x0800:
|
||||||
|
*message_type_flag |= WEBSOCKET_FLAG_CLOSE;
|
||||||
|
break;
|
||||||
|
case 0x0900:
|
||||||
|
*message_type_flag |= WEBSOCKET_FLAG_PING;
|
||||||
|
break;
|
||||||
|
case 0x0A00:
|
||||||
|
*message_type_flag |= WEBSOCKET_FLAG_PONG;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = value & 0x007f;
|
||||||
|
if (len < 126) {
|
||||||
|
len_len = 0;
|
||||||
|
*message_length = len;
|
||||||
|
} else if (len == 126) {
|
||||||
|
len_len = 2;
|
||||||
|
*message_length = sys_get_be16(&buf[2]);
|
||||||
|
} else {
|
||||||
|
len_len = 8;
|
||||||
|
*message_length = sys_get_be64(&buf[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Minimum websocket header is 2 bytes, header length might be
|
||||||
|
* bigger depending on length field len.
|
||||||
|
*/
|
||||||
|
*header_len = MIN_HEADER_LEN + len_len;
|
||||||
|
|
||||||
|
if (buf_len >= *header_len) {
|
||||||
|
if (value & 0x0080) {
|
||||||
|
*masked = true;
|
||||||
|
*mask_value = sys_get_be32(&buf[2 + len_len]);
|
||||||
|
*header_len += 4;
|
||||||
|
} else {
|
||||||
|
*masked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int websocket_recv_msg(int ws_sock, u8_t *buf, size_t buf_len,
|
||||||
|
u32_t *message_type, u64_t *remaining, s32_t timeout)
|
||||||
|
{
|
||||||
|
struct websocket_context *ctx;
|
||||||
|
size_t header_len, pos_to_write = 0;
|
||||||
|
int recv_len = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ctx = z_get_fd_obj(ws_sock, NULL, 0);
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return -EBADF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PART_OF_ARRAY(contexts, ctx)) {
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we have not received the websocket header yet, read it first */
|
||||||
|
if (!ctx->header_received) {
|
||||||
|
/* If we have the header or part of it saved in tmp buf,
|
||||||
|
* then use that
|
||||||
|
*/
|
||||||
|
if (ctx->tmp_buf_len > 0) {
|
||||||
|
int header_left, bytes_to_mv;
|
||||||
|
|
||||||
|
header_left = sizeof(ctx->header) - ctx->pos;
|
||||||
|
bytes_to_mv = MIN(header_left, ctx->tmp_buf_len);
|
||||||
|
|
||||||
|
NET_DBG("Saving %d bytes to header / data",
|
||||||
|
bytes_to_mv);
|
||||||
|
|
||||||
|
memmove(&ctx->header[ctx->pos],
|
||||||
|
ctx->tmp_buf, bytes_to_mv);
|
||||||
|
|
||||||
|
if (ctx->tmp_buf_len > bytes_to_mv) {
|
||||||
|
ctx->tmp_buf_len -= bytes_to_mv;
|
||||||
|
|
||||||
|
NET_DBG("Left %zd bytes data",
|
||||||
|
ctx->tmp_buf_len);
|
||||||
|
|
||||||
|
memmove(ctx->tmp_buf,
|
||||||
|
&ctx->tmp_buf[bytes_to_mv],
|
||||||
|
ctx->tmp_buf_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
recv_len = bytes_to_mv;
|
||||||
|
} else {
|
||||||
|
recv_len = recv(ctx->real_sock, &ctx->header[ctx->pos],
|
||||||
|
sizeof(ctx->header) - ctx->pos,
|
||||||
|
timeout == K_NO_WAIT ? MSG_DONTWAIT : 0);
|
||||||
|
if (recv_len < 0) {
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recv_len == 0) {
|
||||||
|
/* Socket closed */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->pos += recv_len;
|
||||||
|
|
||||||
|
if (ctx->pos >= MIN_HEADER_LEN) {
|
||||||
|
bool masked;
|
||||||
|
|
||||||
|
/* Now we will be able to figure out what is the
|
||||||
|
* actual size of the header.
|
||||||
|
*/
|
||||||
|
if (websocket_parse_header(ctx->header,
|
||||||
|
ctx->pos,
|
||||||
|
&masked,
|
||||||
|
&ctx->masking_value,
|
||||||
|
&ctx->message_len,
|
||||||
|
&ctx->message_type,
|
||||||
|
&header_len)) {
|
||||||
|
ctx->masked = masked;
|
||||||
|
|
||||||
|
if (message_type) {
|
||||||
|
*message_type = ctx->message_type;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->pos < sizeof(ctx->header) &&
|
||||||
|
ctx->pos < ctx->message_len) {
|
||||||
|
ctx->pos += recv_len;
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All the header is now received, we can read the payload
|
||||||
|
* data next.
|
||||||
|
*/
|
||||||
|
ctx->header_received = true;
|
||||||
|
|
||||||
|
/* If there is any data in the header, then move it to the
|
||||||
|
* data buffer.
|
||||||
|
*/
|
||||||
|
if (header_len < sizeof(ctx->header)) {
|
||||||
|
NET_ASSERT(ctx->pos <= sizeof(ctx->header));
|
||||||
|
|
||||||
|
memmove(buf, &ctx->header[header_len],
|
||||||
|
ctx->pos - header_len);
|
||||||
|
pos_to_write = ctx->pos - header_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HEXDUMP_RECV_PACKETS) {
|
||||||
|
LOG_HEXDUMP_DBG(ctx->header, header_len, "Header");
|
||||||
|
NET_DBG("[%p] masked %d mask 0x%04x hdr %zd msg %zd",
|
||||||
|
ctx, ctx->masked, ctx->masking_value, header_len,
|
||||||
|
(size_t)ctx->message_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->total_read = pos_to_write;
|
||||||
|
ctx->pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->total_read < ctx->message_len) {
|
||||||
|
if (ctx->tmp_buf_len > 0) {
|
||||||
|
NET_DBG("Using %zd bytes from tmp buf",
|
||||||
|
ctx->tmp_buf_len);
|
||||||
|
|
||||||
|
memmove(&buf[pos_to_write],
|
||||||
|
ctx->tmp_buf,
|
||||||
|
ctx->tmp_buf_len);
|
||||||
|
recv_len = ctx->tmp_buf_len;
|
||||||
|
} else {
|
||||||
|
recv_len = recv(ctx->real_sock, &buf[pos_to_write],
|
||||||
|
buf_len - pos_to_write,
|
||||||
|
timeout == K_NO_WAIT ? MSG_DONTWAIT : 0);
|
||||||
|
if (recv_len < 0) {
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recv_len == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->total_read += recv_len;
|
||||||
|
recv_len += pos_to_write;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unmask the data */
|
||||||
|
if (ctx->masked) {
|
||||||
|
websocket_mask_payload(buf, ctx->total_read,
|
||||||
|
ctx->masking_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HEXDUMP_RECV_PACKETS
|
||||||
|
LOG_HEXDUMP_DBG(buf, ctx->total_read, "Payload");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* If we receive the end of the message and there is still data left,
|
||||||
|
* move the extra data to temp buffer so that we can get it from there
|
||||||
|
* in next call.
|
||||||
|
*/
|
||||||
|
if (ctx->total_read >= ctx->message_len) {
|
||||||
|
ret = ctx->total_read - ctx->message_len;
|
||||||
|
if (ret > 0) {
|
||||||
|
if ((recv_len - ret) < 0) {
|
||||||
|
NET_DBG("Pkt failure (%d)", recv_len - ret);
|
||||||
|
} else {
|
||||||
|
memmove(ctx->tmp_buf, &buf[recv_len - ret],
|
||||||
|
ret);
|
||||||
|
ctx->tmp_buf_len = ret;
|
||||||
|
|
||||||
|
NET_DBG("[%p] Saving new header with %d "
|
||||||
|
"bytes data", ctx, ret);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NET_DBG("[%p] Received total %u bytes", ctx,
|
||||||
|
(u32_t)ctx->message_len);
|
||||||
|
ctx->tmp_buf_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
recv_len = ctx->message_len;
|
||||||
|
ctx->header_received = false;
|
||||||
|
|
||||||
|
if (remaining) {
|
||||||
|
*remaining = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (remaining) {
|
||||||
|
*remaining = ctx->message_len - ctx->total_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->tmp_buf_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return recv_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int websocket_send(struct websocket_context *ctx, const u8_t *buf,
|
||||||
|
size_t buf_len, s32_t timeout)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
NET_DBG("[%p] Sending %zd bytes", ctx, buf_len);
|
||||||
|
|
||||||
|
ret = websocket_send_msg(ctx->sock, buf, buf_len,
|
||||||
|
WEBSOCKET_OPCODE_DATA_TEXT,
|
||||||
|
true, true, timeout);
|
||||||
|
if (ret < 0) {
|
||||||
|
errno = -ret;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
NET_DBG("[%p] Sent %d bytes", ctx, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int websocket_recv(struct websocket_context *ctx, u8_t *buf,
|
||||||
|
size_t buf_len, s32_t timeout)
|
||||||
|
{
|
||||||
|
u32_t message_type;
|
||||||
|
u64_t remaining;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
NET_DBG("[%p] Waiting data, buf len %zd bytes", ctx, buf_len);
|
||||||
|
|
||||||
|
/* TODO: add support for recvmsg() so that we could return the
|
||||||
|
* websocket specific information in ancillary data.
|
||||||
|
*/
|
||||||
|
ret = websocket_recv_msg(ctx->sock, buf, buf_len, &message_type,
|
||||||
|
&remaining, timeout);
|
||||||
|
if (ret < 0) {
|
||||||
|
errno = -ret;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
NET_DBG("[%p] Received %d bytes", ctx, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t websocket_read_vmeth(void *obj, void *buffer, size_t count)
|
||||||
|
{
|
||||||
|
return (ssize_t)websocket_recv(obj, buffer, count, K_FOREVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t websocket_write_vmeth(void *obj, const void *buffer,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
return (ssize_t)websocket_send(obj, buffer, count, K_FOREVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t websocket_sendto_ctx(void *obj, const void *buf, size_t len,
|
||||||
|
int flags,
|
||||||
|
const struct sockaddr *dest_addr,
|
||||||
|
socklen_t addrlen)
|
||||||
|
{
|
||||||
|
struct websocket_context *ctx = obj;
|
||||||
|
s32_t timeout = K_FOREVER;
|
||||||
|
|
||||||
|
if (flags & ZSOCK_MSG_DONTWAIT) {
|
||||||
|
timeout = K_NO_WAIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARG_UNUSED(dest_addr);
|
||||||
|
ARG_UNUSED(addrlen);
|
||||||
|
|
||||||
|
return (ssize_t)websocket_send(ctx, buf, len, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t websocket_recvfrom_ctx(void *obj, void *buf, size_t max_len,
|
||||||
|
int flags, struct sockaddr *src_addr,
|
||||||
|
socklen_t *addrlen)
|
||||||
|
{
|
||||||
|
struct websocket_context *ctx = obj;
|
||||||
|
s32_t timeout = K_FOREVER;
|
||||||
|
|
||||||
|
if (flags & ZSOCK_MSG_DONTWAIT) {
|
||||||
|
timeout = K_NO_WAIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARG_UNUSED(src_addr);
|
||||||
|
ARG_UNUSED(addrlen);
|
||||||
|
|
||||||
|
return (ssize_t)websocket_recv(ctx, buf, max_len, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct socket_op_vtable websocket_fd_op_vtable = {
|
||||||
|
.fd_vtable = {
|
||||||
|
.read = websocket_read_vmeth,
|
||||||
|
.write = websocket_write_vmeth,
|
||||||
|
.ioctl = websocket_ioctl_vmeth,
|
||||||
|
},
|
||||||
|
.sendto = websocket_sendto_ctx,
|
||||||
|
.recvfrom = websocket_recvfrom_ctx,
|
||||||
|
};
|
||||||
|
|
||||||
|
void websocket_context_foreach(websocket_context_cb_t cb, void *user_data)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
k_sem_take(&contexts_lock, K_FOREVER);
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(contexts); i++) {
|
||||||
|
if (!websocket_context_is_used(&contexts[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_mutex_lock(&contexts[i].lock, K_FOREVER);
|
||||||
|
|
||||||
|
cb(&contexts[i], user_data);
|
||||||
|
|
||||||
|
k_mutex_unlock(&contexts[i].lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
k_sem_give(&contexts_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void websocket_init(void)
|
||||||
|
{
|
||||||
|
k_sem_init(&contexts_lock, 1, UINT_MAX);
|
||||||
|
}
|
139
subsys/net/lib/websocket/websocket_internal.h
Normal file
139
subsys/net/lib/websocket/websocket_internal.h
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/** @file
|
||||||
|
@brief Websocket private header
|
||||||
|
|
||||||
|
This is not to be included by the application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define WS_SHA1_OUTPUT_LEN 20
|
||||||
|
|
||||||
|
/* Min Websocket header length */
|
||||||
|
#define MIN_HEADER_LEN 2
|
||||||
|
|
||||||
|
/* Max Websocket header length */
|
||||||
|
#define MAX_HEADER_LEN 14
|
||||||
|
|
||||||
|
/* From RFC 6455 chapter 4.2.2 */
|
||||||
|
#define WS_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Websocket connection information
|
||||||
|
*/
|
||||||
|
struct websocket_context {
|
||||||
|
union {
|
||||||
|
/** User data.
|
||||||
|
*/
|
||||||
|
void *user_data;
|
||||||
|
|
||||||
|
/** This is used during HTTP handshake to verify that the
|
||||||
|
* peer sent proper Sec-WebSocket-Accept key.
|
||||||
|
*/
|
||||||
|
u8_t *sec_accept_key;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Reference count.
|
||||||
|
*/
|
||||||
|
atomic_t refcount;
|
||||||
|
|
||||||
|
/** Internal lock for protecting this context from multiple access.
|
||||||
|
*/
|
||||||
|
struct k_mutex lock;
|
||||||
|
|
||||||
|
/* The socket number is valid only after HTTP handshake is done
|
||||||
|
* so we can share the memory for these.
|
||||||
|
*/
|
||||||
|
union {
|
||||||
|
/** HTTP parser settings for the application usage */
|
||||||
|
const struct http_parser_settings *http_cb;
|
||||||
|
|
||||||
|
/** The Websocket socket id. If data is sent via this socket, it
|
||||||
|
* will automatically add Websocket headers etc into the data.
|
||||||
|
*/
|
||||||
|
int sock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Temporary buffers used for HTTP handshakes and Websocket protocol
|
||||||
|
* headers. User must provide the actual buffer where the headers are
|
||||||
|
* stored temporarily.
|
||||||
|
*/
|
||||||
|
u8_t *tmp_buf;
|
||||||
|
|
||||||
|
/** Temporary buffer length.
|
||||||
|
*/
|
||||||
|
size_t tmp_buf_len;
|
||||||
|
|
||||||
|
/** The real TCP socket to use when sending Websocket data to peer.
|
||||||
|
*/
|
||||||
|
int real_sock;
|
||||||
|
|
||||||
|
/** Websocket connection masking value */
|
||||||
|
u32_t masking_value;
|
||||||
|
|
||||||
|
/** Timeout for Websocket operations.
|
||||||
|
*/
|
||||||
|
s32_t timeout;
|
||||||
|
|
||||||
|
/** Internal buffer for Websocket header when reading data.
|
||||||
|
*/
|
||||||
|
struct {
|
||||||
|
u8_t header[MAX_HEADER_LEN];
|
||||||
|
u8_t pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Amount of data received. */
|
||||||
|
u64_t total_read;
|
||||||
|
|
||||||
|
/** Message length */
|
||||||
|
u64_t message_len;
|
||||||
|
|
||||||
|
/** Message type */
|
||||||
|
u32_t message_type;
|
||||||
|
|
||||||
|
/** Is the message masked */
|
||||||
|
u8_t masked : 1;
|
||||||
|
|
||||||
|
/** Did we receive Sec-WebSocket-Accept: field */
|
||||||
|
u8_t sec_accept_present : 1;
|
||||||
|
|
||||||
|
/** Is Sec-WebSocket-Accept field correct */
|
||||||
|
u8_t sec_accept_ok : 1;
|
||||||
|
|
||||||
|
/** Did we receive all from peer during HTTP handshake */
|
||||||
|
u8_t all_received : 1;
|
||||||
|
|
||||||
|
/** Header received */
|
||||||
|
u8_t header_received : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disconnect the Websocket.
|
||||||
|
*
|
||||||
|
* @param sock Websocket id returned by websocket_connect() call.
|
||||||
|
*
|
||||||
|
* @return 0 if ok, <0 if error
|
||||||
|
*/
|
||||||
|
int websocket_disconnect(int sock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef websocket_context_cb_t
|
||||||
|
* @brief Callback used while iterating over websocket contexts
|
||||||
|
*
|
||||||
|
* @param context A valid pointer on current websocket context
|
||||||
|
* @param user_data A valid pointer on some user data or NULL
|
||||||
|
*/
|
||||||
|
typedef void (*websocket_context_cb_t)(struct websocket_context *ctx,
|
||||||
|
void *user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterate over websocket context. This is mainly used by net-shell
|
||||||
|
* to show information about websockets.
|
||||||
|
*
|
||||||
|
* @param cb Websocket context callback
|
||||||
|
* @param user_data Caller specific data.
|
||||||
|
*/
|
||||||
|
void websocket_context_foreach(websocket_context_cb_t cb, void *user_data);
|
Loading…
Add table
Add a link
Reference in a new issue