1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #ifndef ZEPHYR_INCLUDE_SHELL_WEBSOCKET_H_
8 #define ZEPHYR_INCLUDE_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 /* ZEPHYR_INCLUDE_SHELL_WEBSOCKET_H_ */
152