Skip to content

Commit 7510e2d

Browse files
jukkarnashif
authored andcommitted
shell: backend: Create a websocket transport backend
This creates a websocket based shell backend that is used to implement a websocket console that can be connected using a browser. Signed-off-by: Jukka Rissanen <[email protected]>
1 parent 7408004 commit 7510e2d

File tree

7 files changed

+687
-1
lines changed

7 files changed

+687
-1
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright (c) 2024 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef SHELL_WEBSOCKET_H__
8+
#define SHELL_WEBSOCKET_H__
9+
10+
#include <zephyr/net/socket.h>
11+
#include <zephyr/net/http/server.h>
12+
#include <zephyr/net/http/service.h>
13+
#include <zephyr/shell/shell.h>
14+
15+
#ifdef __cplusplus
16+
extern "C" {
17+
#endif
18+
19+
#define SHELL_WEBSOCKET_SERVICE_COUNT CONFIG_SHELL_WEBSOCKET_BACKEND_COUNT
20+
21+
/** Line buffer structure. */
22+
struct shell_websocket_line_buf {
23+
/** Line buffer. */
24+
char buf[CONFIG_SHELL_WEBSOCKET_LINE_BUF_SIZE];
25+
26+
/** Current line length. */
27+
uint16_t len;
28+
};
29+
30+
/** WEBSOCKET-based shell transport. */
31+
struct shell_websocket {
32+
/** Handler function registered by shell. */
33+
shell_transport_handler_t shell_handler;
34+
35+
/** Context registered by shell. */
36+
void *shell_context;
37+
38+
/** Buffer for outgoing line. */
39+
struct shell_websocket_line_buf line_out;
40+
41+
/** Array for sockets used by the websocket service. */
42+
struct zsock_pollfd fds[1];
43+
44+
/** Input buffer. */
45+
uint8_t rx_buf[CONFIG_SHELL_CMD_BUFF_SIZE];
46+
47+
/** Number of data bytes within the input buffer. */
48+
size_t rx_len;
49+
50+
/** Mutex protecting the input buffer access. */
51+
struct k_mutex rx_lock;
52+
53+
/** The delayed work is used to send non-lf terminated output that has
54+
* been around for "too long". This will prove to be useful
55+
* to send the shell prompt for instance.
56+
*/
57+
struct k_work_delayable send_work;
58+
struct k_work_sync work_sync;
59+
60+
/** If set, no output is sent to the WEBSOCKET client. */
61+
bool output_lock;
62+
};
63+
64+
extern const struct shell_transport_api shell_websocket_transport_api;
65+
extern int shell_websocket_setup(int ws_socket, void *user_data);
66+
extern int shell_websocket_enable(const struct shell *sh);
67+
68+
#define GET_WS_NAME(_service) ws_ctx_##_service
69+
#define GET_WS_SHELL_NAME(_name) shell_websocket_##_name
70+
#define GET_WS_TRANSPORT_NAME(_service) transport_shell_ws_##_service
71+
#define GET_WS_DETAIL_NAME(_service) ws_res_detail_##_service
72+
73+
#define SHELL_WEBSOCKET_DEFINE(_service) \
74+
static struct shell_websocket GET_WS_NAME(_service); \
75+
static struct shell_transport GET_WS_TRANSPORT_NAME(_service) = { \
76+
.api = &shell_websocket_transport_api, \
77+
.ctx = &GET_WS_NAME(_service), \
78+
}
79+
80+
#define SHELL_WS_PORT_NAME(_service) http_service_##_service
81+
#define SHELL_WS_BUF_NAME(_service) ws_recv_buffer_##_service
82+
#define SHELL_WS_TEMP_RECV_BUF_SIZE 256
83+
84+
#define DEFINE_WEBSOCKET_HTTP_SERVICE(_service) \
85+
uint8_t SHELL_WS_BUF_NAME(_service)[SHELL_WS_TEMP_RECV_BUF_SIZE]; \
86+
struct http_resource_detail_websocket \
87+
GET_WS_DETAIL_NAME(_service) = { \
88+
.common = { \
89+
.type = HTTP_RESOURCE_TYPE_WEBSOCKET, \
90+
\
91+
/* We need HTTP/1.1 GET method for upgrading */ \
92+
.bitmask_of_supported_http_methods = BIT(HTTP_GET), \
93+
}, \
94+
.cb = shell_websocket_setup, \
95+
.data_buffer = SHELL_WS_BUF_NAME(_service), \
96+
.data_buffer_len = sizeof(SHELL_WS_BUF_NAME(_service)), \
97+
.user_data = &GET_WS_NAME(_service), \
98+
}; \
99+
HTTP_RESOURCE_DEFINE(ws_resource_##_service, _service, \
100+
"/" CONFIG_SHELL_WEBSOCKET_ENDPOINT_URL, \
101+
&GET_WS_DETAIL_NAME(_service))
102+
103+
#define DEFINE_WEBSOCKET_SERVICE(_service) \
104+
SHELL_WEBSOCKET_DEFINE(_service); \
105+
SHELL_DEFINE(shell_websocket_##_service, \
106+
CONFIG_SHELL_WEBSOCKET_PROMPT, \
107+
&GET_WS_TRANSPORT_NAME(_service), \
108+
CONFIG_SHELL_WEBSOCKET_LOG_MESSAGE_QUEUE_SIZE, \
109+
CONFIG_SHELL_WEBSOCKET_LOG_MESSAGE_QUEUE_TIMEOUT, \
110+
SHELL_FLAG_OLF_CRLF); \
111+
DEFINE_WEBSOCKET_HTTP_SERVICE(_service)
112+
113+
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
114+
/* Use a secure connection only for Websocket. */
115+
#define WEBSOCKET_CONSOLE_DEFINE(_service, _sec_tag_list, _sec_tag_list_size) \
116+
static uint16_t SHELL_WS_PORT_NAME(_service) = \
117+
CONFIG_SHELL_WEBSOCKET_PORT; \
118+
HTTPS_SERVICE_DEFINE(_service, \
119+
CONFIG_SHELL_WEBSOCKET_IP_ADDR, \
120+
&SHELL_WS_PORT_NAME(_service), \
121+
SHELL_WEBSOCKET_SERVICE_COUNT, \
122+
SHELL_WEBSOCKET_SERVICE_COUNT, \
123+
NULL, \
124+
_sec_tag_list, \
125+
_sec_tag_list_size); \
126+
DEFINE_WEBSOCKET_SERVICE(_service); \
127+
128+
129+
#else /* CONFIG_NET_SOCKETS_SOCKOPT_TLS */
130+
/* TLS not possible so define only normal HTTP service */
131+
#define WEBSOCKET_CONSOLE_DEFINE(_service, _sec_tag_list, _sec_tag_list_size) \
132+
static uint16_t SHELL_WS_PORT_NAME(_service) = \
133+
CONFIG_SHELL_WEBSOCKET_PORT; \
134+
HTTP_SERVICE_DEFINE(_service, \
135+
CONFIG_SHELL_WEBSOCKET_IP_ADDR, \
136+
&SHELL_WS_PORT_NAME(_service), \
137+
SHELL_WEBSOCKET_SERVICE_COUNT, \
138+
SHELL_WEBSOCKET_SERVICE_COUNT, \
139+
NULL); \
140+
DEFINE_WEBSOCKET_SERVICE(_service)
141+
142+
#endif /* CONFIG_NET_SOCKETS_SOCKOPT_TLS */
143+
144+
#define WEBSOCKET_CONSOLE_ENABLE(_service) \
145+
(void)shell_websocket_enable(&GET_WS_SHELL_NAME(_service))
146+
147+
#ifdef __cplusplus
148+
}
149+
#endif
150+
151+
#endif /* SHELL_WEBSOCKET_H__ */

subsys/net/lib/http/Kconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@ config HTTP_SERVER_RESTART_DELAY
179179
allow any existing connections to finalize to avoid binding errors
180180
during initialization.
181181

182+
config WEBSOCKET_CONSOLE
183+
bool
184+
default y if HTTP_SERVER_WEBSOCKET && SHELL_BACKEND_WEBSOCKET
185+
help
186+
Hidden option that is enabled only when all the necessary options
187+
needed by websocket console are set.
182188
endif
183189

184190
# Hidden option to avoid having multiple individual options that are ORed together

subsys/net/lib/http/http_server_ws.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ int handle_http1_to_websocket_upgrade(struct http_client_ctx *client)
110110
ret = ws_detail->cb(ws_sock, ws_detail->user_data);
111111
if (ret < 0) {
112112
NET_DBG("WS connection failed (%d)", ret);
113-
zsock_close(ws_sock);
113+
websocket_unregister(ws_sock);
114114
goto error;
115115
}
116116
}

subsys/net/lib/websocket/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ if WEBSOCKET_CLIENT
2424

2525
config WEBSOCKET_MAX_CONTEXTS
2626
int "Max number of websockets to allocate"
27+
default SHELL_WEBSOCKET_BACKEND_COUNT if SHELL_BACKEND_WEBSOCKET
2728
default 1
2829
help
2930
How many Websockets can be created in the system.

subsys/shell/backends/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ zephyr_sources_ifdef(
2525
shell_mqtt.c
2626
)
2727

28+
zephyr_sources_ifdef(
29+
CONFIG_SHELL_BACKEND_WEBSOCKET
30+
shell_websocket.c
31+
)
32+
2833
zephyr_sources_ifdef(
2934
CONFIG_SHELL_BACKEND_RPMSG
3035
shell_rpmsg.c

subsys/shell/backends/Kconfig.backends

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,125 @@ source "subsys/logging/Kconfig.template.log_config"
572572

573573
endif # SHELL_TELNET_BACKEND
574574

575+
config SHELL_BACKEND_WEBSOCKET
576+
bool "Websocket backend."
577+
depends on HTTP_SERVER_WEBSOCKET
578+
depends on NET_NATIVE_IP
579+
select NET_SOCKETS_SERVICE
580+
select NET_SOCKETS
581+
help
582+
Enable Websocket backend.
583+
584+
if SHELL_BACKEND_WEBSOCKET
585+
586+
config SHELL_WEBSOCKET_BACKEND_COUNT
587+
int "How many Webconsole sessions are supported"
588+
default 2
589+
range 1 8
590+
help
591+
Each connection consumes memory so select the value according to your
592+
needs. Also note that each console session needs unique HTTP endpoint
593+
configured.
594+
Note that if you have only one HTTP endpoint for the websocket console,
595+
setting the this value to 2, allows latter console session to kick out
596+
the previous one. If set this value to 1, then the latter connecting
597+
webconsole session will fail. If you have multiple HTTP endpoints, then
598+
This value should be increased accordingly.
599+
600+
config SHELL_WEBSOCKET_PROMPT
601+
string "Displayed prompt name"
602+
default ""
603+
help
604+
Displayed prompt name for Websocket backend. If prompt is set, the shell will
605+
send two newlines during initialization.
606+
607+
config SHELL_WEBSOCKET_ENDPOINT_URL
608+
string "Websocket endpoint URL"
609+
default "console"
610+
help
611+
What is the HTTP endpoint URL where the client should connect to.
612+
613+
config SHELL_WEBSOCKET_IP_ADDR
614+
string "Websocket IP listen address"
615+
default ""
616+
help
617+
This option is used to configure on which IP address and network interface
618+
the HTTP server is listening. If left empty, then all network interfaces are
619+
listened.
620+
621+
config SHELL_WEBSOCKET_PORT
622+
int "Websocket port number"
623+
default 443 if NET_SOCKETS_SOCKOPT_TLS
624+
default 80
625+
help
626+
This option is used to configure on which port websocket is going
627+
to be bound.
628+
629+
config SHELL_WEBSOCKET_LINE_BUF_SIZE
630+
int "Websocket line buffer size"
631+
default 100
632+
help
633+
This option can be used to modify the size of the buffer storing
634+
shell output line, prior to sending it through the network.
635+
Of course an output line can be longer than such size, it just
636+
means sending it will start as soon as it reaches this size.
637+
It really depends on what type of output is expected.
638+
A lot of short lines: better reduce this value. On the contrary,
639+
raise it.
640+
641+
config SHELL_WEBSOCKET_SEND_TIMEOUT
642+
int "Websocket line send timeout"
643+
default 100
644+
help
645+
This option can be used to modify the duration of the timer that kick
646+
in when a line buffer is not empty but did not yet meet the line feed.
647+
648+
module = SHELL_WEBSOCKET
649+
default-timeout = 100
650+
source "subsys/shell/Kconfig.template.shell_log_queue_timeout"
651+
652+
default-size = 512
653+
source "subsys/shell/Kconfig.template.shell_log_queue_size"
654+
655+
choice
656+
prompt "Initial log level limit"
657+
default SHELL_WEBSOCKET_INIT_LOG_LEVEL_DEFAULT
658+
659+
config SHELL_WEBSOCKET_INIT_LOG_LEVEL_DEFAULT
660+
bool "System limit (LOG_MAX_LEVEL)"
661+
662+
config SHELL_WEBSOCKET_INIT_LOG_LEVEL_DBG
663+
bool "Debug"
664+
665+
config SHELL_WEBSOCKET_INIT_LOG_LEVEL_INF
666+
bool "Info"
667+
668+
config SHELL_WEBSOCKET_INIT_LOG_LEVEL_WRN
669+
bool "Warning"
670+
671+
config SHELL_WEBSOCKET_INIT_LOG_LEVEL_ERR
672+
bool "Error"
673+
674+
config SHELL_WEBSOCKET_INIT_LOG_LEVEL_NONE
675+
bool "None"
676+
677+
endchoice
678+
679+
config SHELL_WEBSOCKET_INIT_LOG_LEVEL
680+
int
681+
default 0 if SHELL_WEBSOCKET_INIT_LOG_LEVEL_NONE
682+
default 1 if SHELL_WEBSOCKET_INIT_LOG_LEVEL_ERR
683+
default 2 if SHELL_WEBSOCKET_INIT_LOG_LEVEL_WRN
684+
default 3 if SHELL_WEBSOCKET_INIT_LOG_LEVEL_INF
685+
default 4 if SHELL_WEBSOCKET_INIT_LOG_LEVEL_DBG
686+
default 5 if SHELL_WEBSOCKET_INIT_LOG_LEVEL_DEFAULT
687+
688+
module = SHELL_WEBSOCKET
689+
module-str = Websocket shell backend
690+
source "subsys/logging/Kconfig.template.log_config"
691+
692+
endif # SHELL_WEBSOCKET_BACKEND
693+
575694
config SHELL_BACKEND_DUMMY
576695
bool "Dummy backend."
577696
help

0 commit comments

Comments
 (0)