From 10838963a5316597e8bce00ff4f0d6727b17b563 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Thu, 12 Jan 2023 19:03:31 +0100 Subject: [PATCH] [chip-tool] Add interactive server command to allow chip-tool to execute commands received over websockets (#24304) --- examples/chip-tool/BUILD.gn | 8 +- .../chip-tool/commands/interactive/Commands.h | 1 + .../interactive/InteractiveCommands.cpp | 17 +- .../interactive/InteractiveCommands.h | 43 ++++- .../commands/interactive/WebSocketServer.cpp | 147 ++++++++++++++++++ .../commands/interactive/WebSocketServer.h | 31 ++++ .../interactive/WebSocketServerDelegate.h | 28 ++++ third_party/libwebsockets/BUILD.gn | 1 + .../libwebsockets/lws_config_private.h | 2 + 9 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 examples/chip-tool/commands/interactive/WebSocketServer.cpp create mode 100644 examples/chip-tool/commands/interactive/WebSocketServer.h create mode 100644 examples/chip-tool/commands/interactive/WebSocketServerDelegate.h diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index b24ddb5d28c427..1ca148ceeb1246 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -85,7 +85,12 @@ static_library("chip-tool-utils") { deps = [] if (config_use_interactive_mode) { - sources += [ "commands/interactive/InteractiveCommands.cpp" ] + sources += [ + "commands/interactive/InteractiveCommands.cpp", + "commands/interactive/WebSocketServer.cpp", + "commands/interactive/WebSocketServer.h", + "commands/interactive/WebSocketServerDelegate.h", + ] deps += [ "${editline_root}:editline" ] } @@ -108,6 +113,7 @@ static_library("chip-tool-utils") { "${chip_root}/src/platform", "${chip_root}/third_party/inipp", "${chip_root}/third_party/jsoncpp", + "${chip_root}/third_party/libwebsockets", ] public_configs = [ ":config" ] diff --git a/examples/chip-tool/commands/interactive/Commands.h b/examples/chip-tool/commands/interactive/Commands.h index 04249045c9caef..747c8aeaa418d9 100644 --- a/examples/chip-tool/commands/interactive/Commands.h +++ b/examples/chip-tool/commands/interactive/Commands.h @@ -29,6 +29,7 @@ void registerCommandsInteractive(Commands & commands, CredentialIssuerCommands * commands_list clusterCommands = { #if CONFIG_USE_INTERACTIVE_MODE make_unique(&commands, credsIssuerConfig), + make_unique(&commands, credsIssuerConfig), #endif // CONFIG_USE_INTERACTIVE_MODE }; diff --git a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp index de82f205803070..e887391a12031a 100644 --- a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp +++ b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp @@ -43,7 +43,6 @@ void ENFORCE_FORMAT(3, 0) LoggingCallback(const char * module, uint8_t category, chip::Logging::Platform::LogV(module, category, msg, args); ClearLine(); } -} // namespace char * GetCommand(char * command) { @@ -64,6 +63,20 @@ char * GetCommand(char * command) return command; } +} // namespace + +CHIP_ERROR InteractiveServerCommand::RunCommand() +{ + ReturnErrorOnFailure(mWebSocketServer.Run(mPort, this)); + + SetCommandExitStatus(CHIP_NO_ERROR); + return CHIP_NO_ERROR; +} + +bool InteractiveServerCommand::OnWebSocketMessageReceived(char * msg) +{ + return ParseCommand(msg); +} CHIP_ERROR InteractiveStartCommand::RunCommand() { @@ -93,7 +106,7 @@ CHIP_ERROR InteractiveStartCommand::RunCommand() return CHIP_NO_ERROR; } -bool InteractiveStartCommand::ParseCommand(char * command) +bool InteractiveCommand::ParseCommand(char * command) { if (strcmp(command, kInteractiveModeStopCommand) == 0) { diff --git a/examples/chip-tool/commands/interactive/InteractiveCommands.h b/examples/chip-tool/commands/interactive/InteractiveCommands.h index dc3582d621189b..830c9d5fd408f2 100644 --- a/examples/chip-tool/commands/interactive/InteractiveCommands.h +++ b/examples/chip-tool/commands/interactive/InteractiveCommands.h @@ -21,22 +21,53 @@ #include "../common/CHIPCommand.h" #include "../common/Commands.h" -#include "InteractiveCommands.h" +#include "WebSocketServer.h" class Commands; -class InteractiveStartCommand : public CHIPCommand +class InteractiveCommand : public CHIPCommand +{ +public: + InteractiveCommand(const char * name, Commands * commandsHandler, CredentialIssuerCommands * credsIssuerConfig) : + CHIPCommand(name, credsIssuerConfig), mHandler(commandsHandler) + {} + + /////////// CHIPCommand Interface ///////// + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(0); } + + bool ParseCommand(char * command); + +private: + Commands * mHandler = nullptr; +}; + +class InteractiveStartCommand : public InteractiveCommand { public: InteractiveStartCommand(Commands * commandsHandler, CredentialIssuerCommands * credsIssuerConfig) : - CHIPCommand("start", credsIssuerConfig), mHandler(commandsHandler) + InteractiveCommand("start", commandsHandler, credsIssuerConfig) {} + /////////// CHIPCommand Interface ///////// CHIP_ERROR RunCommand() override; +}; - chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(0); } +class InteractiveServerCommand : public InteractiveCommand, public WebSocketServerDelegate +{ +public: + InteractiveServerCommand(Commands * commandsHandler, CredentialIssuerCommands * credsIssuerConfig) : + InteractiveCommand("server", commandsHandler, credsIssuerConfig) + { + AddArgument("port", 0, UINT16_MAX, &mPort, "Port the websocket will listen to. Defaults to 9002."); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override; + + /////////// WebSocketServerDelegate Interface ///////// + bool OnWebSocketMessageReceived(char * msg) override; private: - bool ParseCommand(char * command); - Commands * mHandler = nullptr; + WebSocketServer mWebSocketServer; + chip::Optional mPort; }; diff --git a/examples/chip-tool/commands/interactive/WebSocketServer.cpp b/examples/chip-tool/commands/interactive/WebSocketServer.cpp new file mode 100644 index 00000000000000..c7269b38af538f --- /dev/null +++ b/examples/chip-tool/commands/interactive/WebSocketServer.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "WebSocketServer.h" + +constexpr uint16_t kDefaultWebSocketServerPort = 9002; +constexpr uint16_t kMaxMessageBufferLen = 8192; + +namespace { +void LogWebSocketCallbackReason(lws_callback_reasons reason) +{ +#if CHIP_DETAIL_LOGGING + switch (reason) + { + case LWS_CALLBACK_GET_THREAD_ID: + ChipLogDetail(chipTool, "LWS_CALLBACK_GET_THREAD_ID"); + break; + case LWS_CALLBACK_ADD_HEADERS: + ChipLogDetail(chipTool, "LWS_CALLBACK_ADD_HEADERS"); + break; + case LWS_CALLBACK_PROTOCOL_INIT: + ChipLogDetail(chipTool, "LWS_CALLBACK_PROTOCOL_INIT"); + break; + case LWS_CALLBACK_PROTOCOL_DESTROY: + ChipLogDetail(chipTool, "LWS_CALLBACK_PROTOCOL_DESTROY"); + break; + case LWS_CALLBACK_HTTP: + ChipLogDetail(chipTool, "LWS_CALLBACK_HTTP"); + break; + case LWS_CALLBACK_EVENT_WAIT_CANCELLED: + ChipLogDetail(chipTool, "LWS_CALLBACK_EVENT_WAIT_CANCELLED"); + break; + case LWS_CALLBACK_CLIENT_WRITEABLE: + ChipLogDetail(chipTool, "LWS_CALLBACK_CLIENT_WRITEABLE"); + break; + case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: + ChipLogDetail(chipTool, "LWS_CALLBACK_FILTER_NETWORK_CONNECTION"); + break; + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + ChipLogDetail(chipTool, "LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION"); + break; + case LWS_CALLBACK_WSI_CREATE: + ChipLogDetail(chipTool, "LWS_CALLBACK_WSI_CREATE"); + break; + case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED: + ChipLogDetail(chipTool, "LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED"); + break; + case LWS_CALLBACK_HTTP_CONFIRM_UPGRADE: + ChipLogDetail(chipTool, "LWS_CALLBACK_HTTP_CONFIRM_UPGRADE"); + break; + case LWS_CALLBACK_HTTP_BIND_PROTOCOL: + ChipLogDetail(chipTool, "LWS_CALLBACK_HTTP_BIND_PROTOCOL"); + break; + case LWS_CALLBACK_ESTABLISHED: + ChipLogDetail(chipTool, "LWS_CALLBACK_ESTABLISHED"); + break; + case LWS_CALLBACK_RECEIVE: + ChipLogDetail(chipTool, "LWS_CALLBACK_RECEIVE"); + break; + case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: + ChipLogDetail(chipTool, "LWS_CALLBACK_WS_PEER_INITIATED_CLOSE"); + break; + case LWS_CALLBACK_WSI_DESTROY: + ChipLogDetail(chipTool, "LWS_CALLBACK_WSI_DESTROY"); + break; + case LWS_CALLBACK_CLOSED: + ChipLogDetail(chipTool, "LWS_CALLBACK_CLOSED"); + break; + case LWS_CALLBACK_SERVER_WRITEABLE: + ChipLogDetail(chipTool, "LWS_CALLBACK_SERVER_WRITEABLE"); + break; + case LWS_CALLBACK_CLOSED_HTTP: + ChipLogDetail(chipTool, "LWS_CALLBACK_CLOSED_HTTP"); + break; + default: + ChipLogError(chipTool, "Unknown reason: %d ", static_cast(reason)); + } +#endif // CHIP_DETAIL_LOGGING +} + +static int OnWebSocketCallback(lws * wsi, lws_callback_reasons reason, void * user, void * in, size_t len) +{ + LogWebSocketCallbackReason(reason); + + if (LWS_CALLBACK_RECEIVE == reason) + { + char msg[kMaxMessageBufferLen + 1 /* for null byte */] = {}; + VerifyOrDie(sizeof(msg) > len); + memcpy(msg, in, len); + + auto protocol = lws_get_protocol(wsi); + auto delegate = static_cast(protocol->user); + if (nullptr == delegate) + { + ChipLogError(chipTool, "Failed to retrieve the server interactive context."); + return -1; + } + + if (!delegate->OnWebSocketMessageReceived(msg)) + { + auto context = lws_get_context(wsi); + lws_default_loop_exit(context); + } + } + else if (LWS_CALLBACK_CLIENT_ESTABLISHED == reason) + { + lws_callback_on_writable(wsi); + } + + return 0; +} +} // namespace + +CHIP_ERROR WebSocketServer::Run(chip::Optional port, WebSocketServerDelegate * delegate) +{ + VerifyOrReturnError(nullptr != delegate, CHIP_ERROR_INVALID_ARGUMENT); + + lws_protocols protocols[] = { { "ws", OnWebSocketCallback, 0, 0, 0, delegate, 0 }, LWS_PROTOCOL_LIST_TERM }; + + lws_context_creation_info info; + memset(&info, 0, sizeof(info)); + info.port = port.ValueOr(kDefaultWebSocketServerPort); + info.iface = nullptr; + info.pt_serv_buf_size = kMaxMessageBufferLen; + info.protocols = protocols; + + auto context = lws_create_context(&info); + VerifyOrReturnError(nullptr != context, CHIP_ERROR_INTERNAL); + + lws_context_default_loop_run_destroy(context); + return CHIP_NO_ERROR; +} diff --git a/examples/chip-tool/commands/interactive/WebSocketServer.h b/examples/chip-tool/commands/interactive/WebSocketServer.h new file mode 100644 index 00000000000000..d24ab07f88f217 --- /dev/null +++ b/examples/chip-tool/commands/interactive/WebSocketServer.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "WebSocketServerDelegate.h" + +#include +#include +#include + +class WebSocketServer +{ +public: + CHIP_ERROR Run(chip::Optional port, WebSocketServerDelegate * delegate); +}; diff --git a/examples/chip-tool/commands/interactive/WebSocketServerDelegate.h b/examples/chip-tool/commands/interactive/WebSocketServerDelegate.h new file mode 100644 index 00000000000000..b0c55cf226c795 --- /dev/null +++ b/examples/chip-tool/commands/interactive/WebSocketServerDelegate.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "WebSocketServerDelegate.h" + +class WebSocketServerDelegate +{ +public: + virtual ~WebSocketServerDelegate(){}; + virtual bool OnWebSocketMessageReceived(char * msg) = 0; +}; diff --git a/third_party/libwebsockets/BUILD.gn b/third_party/libwebsockets/BUILD.gn index 8e759cc4dc601f..37d7bf1acbef24 100644 --- a/third_party/libwebsockets/BUILD.gn +++ b/third_party/libwebsockets/BUILD.gn @@ -22,6 +22,7 @@ config("libwebsockets_config") { "-Wno-shadow", "-Wno-unreachable-code", "-Wno-format-nonliteral", + "-Wno-implicit-fallthrough", ] defines = [] diff --git a/third_party/libwebsockets/lws_config_private.h b/third_party/libwebsockets/lws_config_private.h index 468bba9a126d4f..3869c2d5f24ea9 100644 --- a/third_party/libwebsockets/lws_config_private.h +++ b/third_party/libwebsockets/lws_config_private.h @@ -17,3 +17,5 @@ */ #pragma once + +#define LWS_HAVE_SYS_RESOURCE_H