From 913980c1be51b1b4d0d3a5aa53659f6f90020bf4 Mon Sep 17 00:00:00 2001 From: Lukasz Maciejonczyk Date: Thu, 23 Jan 2025 13:39:15 +0100 Subject: [PATCH] nrf_rpc: Add functionality for invoking remote shell cmd via rpc The functionality allows the nrf_rpc client to invoke nrf_rpc server shell command and obtain the command's output. This commit adds the new nrf_rpc client shell subcommand: 'remote_shell' Example of use: rpc remote_shell device list devices: - clock@10e000 (READY) - gpio@10a000 (READY) - gpio@d8200 (READY) - gpio@50400 (READY) - psa-rng (READY) - uart@c7000 (READY) - uart@c6000 (READY) - spi@4a000 (READY) - rram-controller@5004b000 (READY) - mx25r6435f@0 (READY) - ieee802154 (READY) - temp@d7000 (READY) Signed-off-by: Lukasz Maciejonczyk --- include/nrf_rpc/nrf_rpc_dev_info.h | 12 ++- .../client/src/dev_info_shell.c | 27 +++++- .../protocols_serialization/server/Kconfig | 3 + subsys/nrf_rpc/dev_info/Kconfig | 12 +++ .../nrf_rpc/dev_info/client/dev_info_client.c | 95 +++++++++++++++++-- .../dev_info/common/dev_info_rpc_ids.h | 3 +- .../nrf_rpc/dev_info/server/dev_info_server.c | 69 ++++++++++++++ 7 files changed, 208 insertions(+), 13 deletions(-) diff --git a/include/nrf_rpc/nrf_rpc_dev_info.h b/include/nrf_rpc/nrf_rpc_dev_info.h index 45c85336a4dd..bf2d5546b516 100644 --- a/include/nrf_rpc/nrf_rpc_dev_info.h +++ b/include/nrf_rpc/nrf_rpc_dev_info.h @@ -19,7 +19,17 @@ * @retval version of the remote on success. * @retval NULL on failure. */ -const char *nrf_rpc_get_ncs_commit_sha(void); +char *nrf_rpc_get_ncs_commit_sha(void); + +/** @brief Invoke the remote server shell command. + * + * @param[in] argc Number of arguments. + * @param[in] argv Array of arguments. + * + * @retval Command output on success. + * @retval NULL on failure. + */ +char *nrf_rpc_invoke_remote_shell_cmd(size_t argc, char *argv[]); /** * @} diff --git a/samples/nrf_rpc/protocols_serialization/client/src/dev_info_shell.c b/samples/nrf_rpc/protocols_serialization/client/src/dev_info_shell.c index c2ae0b2b1f61..5dca9cf0900f 100644 --- a/samples/nrf_rpc/protocols_serialization/client/src/dev_info_shell.c +++ b/samples/nrf_rpc/protocols_serialization/client/src/dev_info_shell.c @@ -9,16 +9,41 @@ static int remote_version_cmd(const struct shell *sh, size_t argc, char *argv[]) { - const char *version = nrf_rpc_get_ncs_commit_sha(); + char *version = nrf_rpc_get_ncs_commit_sha(); + + if (!version) { + shell_error(sh, "Failed to get remote version"); + return -ENOEXEC; + } shell_print(sh, "Remote version: %s", version); + k_free(version); + + return 0; +} + +static int remote_shell_cmd(const struct shell *sh, size_t argc, char *argv[]) +{ + char *output = nrf_rpc_invoke_remote_shell_cmd(argc - 1, argv + 1); + + if (!output) { + shell_error(sh, "Failed to invoke remote shell command"); + return -ENOEXEC; + } + + shell_print(sh, "%s", output); + + k_free(output); + return 0; } SHELL_STATIC_SUBCMD_SET_CREATE(util_cmds, SHELL_CMD_ARG(remote_version, NULL, "Get server version", remote_version_cmd, 1, 0), + SHELL_CMD_ARG(remote_shell, NULL, "Call remote shell command", + remote_shell_cmd, 2, 255), SHELL_SUBCMD_SET_END); SHELL_CMD_ARG_REGISTER(rpc, &util_cmds, "nRF RPC utility commands", NULL, 1, 0); diff --git a/samples/nrf_rpc/protocols_serialization/server/Kconfig b/samples/nrf_rpc/protocols_serialization/server/Kconfig index 75df033f0a66..53733b7a4f0e 100644 --- a/samples/nrf_rpc/protocols_serialization/server/Kconfig +++ b/samples/nrf_rpc/protocols_serialization/server/Kconfig @@ -22,6 +22,9 @@ config LOG_BACKEND_RTT config LOG_BACKEND_UART default n +config SHELL_BACKEND_SERIAL + default n if NRF_RPC_REMOTE_SHELL + if SOC_NRF54L15 config BT_CTLR_ECDH diff --git a/subsys/nrf_rpc/dev_info/Kconfig b/subsys/nrf_rpc/dev_info/Kconfig index b5efb9b7f12b..803d04b22e37 100644 --- a/subsys/nrf_rpc/dev_info/Kconfig +++ b/subsys/nrf_rpc/dev_info/Kconfig @@ -37,4 +37,16 @@ config DEV_INFO_RPC_INITIALIZE_NRF_RPC Initialize nRF RPC library during the system startup. Disabling this option allow user to initialize it in a different way. +config NRF_RPC_REMOTE_SHELL + bool "Remote shell" + depends on NRF_RPC_DEV_INFO_SERVER + select SHELL + select SHELL_BACKEND_DUMMY + default y + help + Enable access to remote shell via NRF_RPC. + +config SHELL_BACKEND_DUMMY_BUF_SIZE + default 1024 if NRF_RPC_REMOTE_SHELL + endif # NRF_RPC_DEV_INFO diff --git a/subsys/nrf_rpc/dev_info/client/dev_info_client.c b/subsys/nrf_rpc/dev_info/client/dev_info_client.c index ac4ca744b5c7..6cfac58fbde4 100644 --- a/subsys/nrf_rpc/dev_info/client/dev_info_client.c +++ b/subsys/nrf_rpc/dev_info/client/dev_info_client.c @@ -13,26 +13,101 @@ NRF_RPC_GROUP_DECLARE(dev_info_group); -static char version[16]; +static char *allocate_buffer_and_decode_str(struct nrf_rpc_cbor_ctx *ctx, + enum def_info_rpc_cmd_server cmd) +{ + const void *ptr; + size_t len; + char *output = NULL; + + ptr = nrf_rpc_decode_str_ptr_and_len(ctx, &len); + + if (ptr) { + output = k_malloc(len + 1); + if (output) { + memcpy(output, ptr, len); + output[len] = '\0'; + } + } + + if (!nrf_rpc_decoding_done_and_check(&dev_info_group, ctx)) { + nrf_rpc_err(-EBADMSG, NRF_RPC_ERR_SRC_RECV, &dev_info_group, cmd, + NRF_RPC_PACKET_TYPE_RSP); + k_free(output); + return NULL; + } + + return output; +} -const char *nrf_rpc_get_ncs_commit_sha(void) +char *nrf_rpc_get_ncs_commit_sha(void) { struct nrf_rpc_cbor_ctx ctx; - size_t size = ARRAY_SIZE(version); - - memset(version, 0, ARRAY_SIZE(version)); NRF_RPC_CBOR_ALLOC(&dev_info_group, ctx, 0); nrf_rpc_cbor_cmd_rsp_no_err(&dev_info_group, DEV_INFO_RPC_GET_VERSION, &ctx); - nrf_rpc_decode_str(&ctx, version, size); + return allocate_buffer_and_decode_str(&ctx, DEV_INFO_RPC_GET_VERSION); +} - if (!nrf_rpc_decoding_done_and_check(&dev_info_group, &ctx)) { - nrf_rpc_err(-EBADMSG, NRF_RPC_ERR_SRC_RECV, &dev_info_group, - DEV_INFO_RPC_GET_VERSION, NRF_RPC_PACKET_TYPE_RSP); +static size_t get_cmd_len(size_t argc, char *argv[]) +{ + size_t len = 0; + + for (size_t i = 0; i < argc; i++) { + len += strlen(argv[i]) + 1; + } + + return len; +} + +static size_t create_cmd_line(char *buffer, size_t cmd_buffer_size, size_t argc, char *argv[]) +{ + size_t len = 0; + + for (size_t i = 0; i < argc; i++) { + size_t arg_len = strlen(argv[i]); + + if (len + arg_len + 1 > cmd_buffer_size) { + return 0; + } + memcpy(buffer + len, argv[i], arg_len); + len += arg_len; + buffer[len++] = ' '; + } + + buffer[len - 1] = '\0'; + + return len; +} + +char *nrf_rpc_invoke_remote_shell_cmd(size_t argc, char *argv[]) +{ + struct nrf_rpc_cbor_ctx ctx; + size_t cmd_buffer_size = get_cmd_len(argc, argv); + char *cmd_buffer = k_malloc(cmd_buffer_size); + + if (!cmd_buffer) { + nrf_rpc_err(-ENOMEM, NRF_RPC_ERR_SRC_SEND, &dev_info_group, + DEV_INFO_RPC_INVOKE_SHELL_CMD, NRF_RPC_PACKET_TYPE_CMD); + return NULL; + } + + size_t cmd_len = create_cmd_line(cmd_buffer, cmd_buffer_size, argc, argv); + + if (!cmd_len) { + nrf_rpc_err(-ENOMEM, NRF_RPC_ERR_SRC_SEND, &dev_info_group, + DEV_INFO_RPC_INVOKE_SHELL_CMD, NRF_RPC_PACKET_TYPE_CMD); + k_free(cmd_buffer); return NULL; } - return version; + NRF_RPC_CBOR_ALLOC(&dev_info_group, ctx, 3 + cmd_len); + + nrf_rpc_encode_str(&ctx, cmd_buffer, cmd_len); + + nrf_rpc_cbor_cmd_rsp_no_err(&dev_info_group, DEV_INFO_RPC_INVOKE_SHELL_CMD, &ctx); + + return allocate_buffer_and_decode_str(&ctx, DEV_INFO_RPC_INVOKE_SHELL_CMD); } diff --git a/subsys/nrf_rpc/dev_info/common/dev_info_rpc_ids.h b/subsys/nrf_rpc/dev_info/common/dev_info_rpc_ids.h index 7f7bdf3867d6..a5001398b728 100644 --- a/subsys/nrf_rpc/dev_info/common/dev_info_rpc_ids.h +++ b/subsys/nrf_rpc/dev_info/common/dev_info_rpc_ids.h @@ -9,8 +9,9 @@ /** @brief Command IDs accepted by the device information over RPC server. */ -enum ot_rpc_cmd_server { +enum def_info_rpc_cmd_server { DEV_INFO_RPC_GET_VERSION = 0, + DEV_INFO_RPC_INVOKE_SHELL_CMD = 1, }; #endif /* DEV_INFO_RPC_IDS_H_ */ diff --git a/subsys/nrf_rpc/dev_info/server/dev_info_server.c b/subsys/nrf_rpc/dev_info/server/dev_info_server.c index 916eadb213f0..579f024d1051 100644 --- a/subsys/nrf_rpc/dev_info/server/dev_info_server.c +++ b/subsys/nrf_rpc/dev_info/server/dev_info_server.c @@ -10,6 +10,10 @@ #include #include +#if defined(CONFIG_NRF_RPC_REMOTE_SHELL) +#include +#endif + NRF_RPC_GROUP_DECLARE(dev_info_group); static void get_server_version(const struct nrf_rpc_group *group, struct nrf_rpc_cbor_ctx *ctx, @@ -34,3 +38,68 @@ static void get_server_version(const struct nrf_rpc_group *group, struct nrf_rpc NRF_RPC_CBOR_CMD_DECODER(dev_info_group, get_server_version, DEV_INFO_RPC_GET_VERSION, get_server_version, NULL); + +#if defined(CONFIG_NRF_RPC_REMOTE_SHELL) +static int shell_exec(const char *line) +{ + const struct shell *sh = shell_backend_dummy_get_ptr(); + + shell_backend_dummy_clear_output(sh); + return shell_execute_cmd(sh, line); +} + +const char *shell_get_output(size_t *len) +{ + return shell_backend_dummy_get_output(shell_backend_dummy_get_ptr(), len); +} +#endif /* CONFIG_NRF_RPC_REMOTE_SHELL */ + +static void remote_shell_cmd(const struct nrf_rpc_group *group, struct nrf_rpc_cbor_ctx *ctx, + void *handler_data) +{ + struct nrf_rpc_cbor_ctx rsp_ctx; + char *cmd_buffer = NULL; + const void *ptr; + size_t len = 0; + const char *output = NULL; + + ptr = nrf_rpc_decode_str_ptr_and_len(ctx, &len); + + if (ptr) { + cmd_buffer = k_malloc(len + 1); + if (cmd_buffer) { + memcpy(cmd_buffer, ptr, len); + cmd_buffer[len] = '\0'; + } + } + + if (!nrf_rpc_decoding_done_and_check(group, ctx)) { + nrf_rpc_err(-EBADMSG, NRF_RPC_ERR_SRC_RECV, group, DEV_INFO_RPC_INVOKE_SHELL_CMD, + NRF_RPC_PACKET_TYPE_CMD); + goto exit; + } + + if (!ptr || !cmd_buffer) { + goto exit; + } + +#if defined(CONFIG_NRF_RPC_REMOTE_SHELL) + shell_exec(cmd_buffer); + output = shell_get_output(&len); +#else + output = "Remote shell is disabled on the server. " + "Enable CONFIG_NRF_RPC_REMOTE_SHELL to use it."; + len = strlen(output); +#endif /* CONFIG_NRF_RPC_REMOTE_SHELL */ + + NRF_RPC_CBOR_ALLOC(group, rsp_ctx, 3 + len); + + nrf_rpc_encode_str(&rsp_ctx, output, len); + nrf_rpc_cbor_rsp_no_err(group, &rsp_ctx); + +exit: + k_free(cmd_buffer); +} + +NRF_RPC_CBOR_CMD_DECODER(dev_info_group, remote_shell_cmd, DEV_INFO_RPC_INVOKE_SHELL_CMD, + remote_shell_cmd, NULL);