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