796 lines
17 KiB
C
796 lines
17 KiB
C
/*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
* Copyright (c) 2019 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/init.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/net/socket.h>
|
|
#include <zephyr/net/socket_service.h>
|
|
#include <zephyr/shell/shell_telnet.h>
|
|
|
|
#include "shell_telnet_protocol.h"
|
|
|
|
SHELL_TELNET_DEFINE(shell_transport_telnet);
|
|
SHELL_DEFINE(shell_telnet, CONFIG_SHELL_PROMPT_TELNET, &shell_transport_telnet,
|
|
CONFIG_SHELL_TELNET_LOG_MESSAGE_QUEUE_SIZE,
|
|
CONFIG_SHELL_TELNET_LOG_MESSAGE_QUEUE_TIMEOUT,
|
|
SHELL_FLAG_OLF_CRLF);
|
|
|
|
LOG_MODULE_REGISTER(shell_telnet, CONFIG_SHELL_TELNET_LOG_LEVEL);
|
|
|
|
struct shell_telnet *sh_telnet;
|
|
|
|
/* Various definitions mapping the TELNET service configuration options */
|
|
#define TELNET_PORT CONFIG_SHELL_TELNET_PORT
|
|
#define TELNET_LINE_SIZE CONFIG_SHELL_TELNET_LINE_BUF_SIZE
|
|
#define TELNET_TIMEOUT CONFIG_SHELL_TELNET_SEND_TIMEOUT
|
|
|
|
#define TELNET_MIN_COMMAND_LEN 2
|
|
#define TELNET_WILL_DO_COMMAND_LEN 3
|
|
|
|
#define SOCK_ID_IPV4_LISTEN 0
|
|
#define SOCK_ID_IPV6_LISTEN 1
|
|
#define SOCK_ID_CLIENT 2
|
|
#define SOCK_ID_MAX 3
|
|
|
|
/* Basic TELNET implementation. */
|
|
|
|
static void telnet_server_cb(struct k_work *work);
|
|
static int telnet_init(struct shell_telnet *ctx);
|
|
|
|
NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC(telnet_server, NULL, telnet_server_cb,
|
|
SHELL_TELNET_POLLFD_COUNT);
|
|
|
|
|
|
static void telnet_end_client_connection(void)
|
|
{
|
|
int ret;
|
|
|
|
(void)zsock_close(sh_telnet->fds[SOCK_ID_CLIENT].fd);
|
|
|
|
sh_telnet->fds[SOCK_ID_CLIENT].fd = -1;
|
|
sh_telnet->output_lock = false;
|
|
|
|
k_work_cancel_delayable_sync(&sh_telnet->send_work,
|
|
&sh_telnet->work_sync);
|
|
|
|
ret = net_socket_service_register(&telnet_server, sh_telnet->fds,
|
|
ARRAY_SIZE(sh_telnet->fds), NULL);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to register socket service, %d", ret);
|
|
}
|
|
}
|
|
|
|
static void telnet_command_send_reply(uint8_t *msg, uint16_t len)
|
|
{
|
|
if (sh_telnet->fds[SOCK_ID_CLIENT].fd < 0) {
|
|
return;
|
|
}
|
|
|
|
while (len > 0) {
|
|
int ret;
|
|
|
|
ret = zsock_send(sh_telnet->fds[SOCK_ID_CLIENT].fd, msg, len, 0);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to send command %d, shutting down", ret);
|
|
telnet_end_client_connection();
|
|
break;
|
|
}
|
|
|
|
msg += ret;
|
|
len -= ret;
|
|
}
|
|
}
|
|
|
|
static void telnet_reply_ay_command(void)
|
|
{
|
|
static const char alive[] = "Zephyr at your service\r\n";
|
|
|
|
telnet_command_send_reply((uint8_t *)alive, strlen(alive));
|
|
}
|
|
|
|
static int telnet_echo_set(const struct shell *sh, bool val)
|
|
{
|
|
int ret = shell_echo_set(sh_telnet->shell_context, val);
|
|
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to set echo to: %d, err: %d", val, ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void telnet_reply_dont_command(struct telnet_simple_command *cmd)
|
|
{
|
|
switch (cmd->opt) {
|
|
case NVT_OPT_ECHO:
|
|
{
|
|
int ret = telnet_echo_set(sh_telnet->shell_context, false);
|
|
|
|
if (ret >= 0) {
|
|
cmd->op = NVT_CMD_WILL_NOT;
|
|
} else {
|
|
cmd->op = NVT_CMD_WILL;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
cmd->op = NVT_CMD_WILL_NOT;
|
|
break;
|
|
}
|
|
|
|
telnet_command_send_reply((uint8_t *)cmd,
|
|
sizeof(struct telnet_simple_command));
|
|
}
|
|
|
|
static void telnet_reply_do_command(struct telnet_simple_command *cmd)
|
|
{
|
|
switch (cmd->opt) {
|
|
case NVT_OPT_SUPR_GA:
|
|
cmd->op = NVT_CMD_WILL;
|
|
break;
|
|
case NVT_OPT_ECHO:
|
|
{
|
|
int ret = telnet_echo_set(sh_telnet->shell_context, true);
|
|
|
|
if (ret >= 0) {
|
|
cmd->op = NVT_CMD_WILL;
|
|
} else {
|
|
cmd->op = NVT_CMD_WILL_NOT;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
cmd->op = NVT_CMD_WILL_NOT;
|
|
break;
|
|
}
|
|
|
|
telnet_command_send_reply((uint8_t *)cmd,
|
|
sizeof(struct telnet_simple_command));
|
|
}
|
|
|
|
static void telnet_reply_command(struct telnet_simple_command *cmd)
|
|
{
|
|
if (!cmd->iac) {
|
|
return;
|
|
}
|
|
|
|
switch (cmd->op) {
|
|
case NVT_CMD_AO:
|
|
/* OK, no output then */
|
|
sh_telnet->output_lock = true;
|
|
sh_telnet->line_out.len = 0;
|
|
k_work_cancel_delayable_sync(&sh_telnet->send_work,
|
|
&sh_telnet->work_sync);
|
|
break;
|
|
case NVT_CMD_AYT:
|
|
telnet_reply_ay_command();
|
|
break;
|
|
case NVT_CMD_DO:
|
|
telnet_reply_do_command(cmd);
|
|
break;
|
|
case NVT_CMD_DO_NOT:
|
|
telnet_reply_dont_command(cmd);
|
|
break;
|
|
default:
|
|
LOG_DBG("Operation %u not handled", cmd->op);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int telnet_send(bool block)
|
|
{
|
|
int ret;
|
|
uint8_t *msg = sh_telnet->line_out.buf;
|
|
uint16_t len = sh_telnet->line_out.len;
|
|
|
|
if (sh_telnet->line_out.len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (sh_telnet->fds[SOCK_ID_CLIENT].fd < 0) {
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
while (len > 0) {
|
|
ret = zsock_send(sh_telnet->fds[SOCK_ID_CLIENT].fd, msg, len,
|
|
block ? 0 : ZSOCK_MSG_DONTWAIT);
|
|
if (!block && (ret < 0) && (errno == EAGAIN)) {
|
|
/* Not all data was sent - move the remaining data and
|
|
* update length.
|
|
*/
|
|
memmove(sh_telnet->line_out.buf, msg, len);
|
|
sh_telnet->line_out.len = len;
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
ret = -errno;
|
|
LOG_ERR("Failed to send %d, shutting down", -ret);
|
|
telnet_end_client_connection();
|
|
return ret;
|
|
}
|
|
|
|
msg += ret;
|
|
len -= ret;
|
|
}
|
|
|
|
/* We reinitialize the line buffer */
|
|
sh_telnet->line_out.len = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void telnet_send_prematurely(struct k_work *work)
|
|
{
|
|
int ret;
|
|
|
|
/* Use non-blocking send to prevent system workqueue blocking. */
|
|
ret = telnet_send(false);
|
|
if (ret == -EAGAIN) {
|
|
/* Not all data was sent, reschedule the work. */
|
|
k_work_reschedule(&sh_telnet->send_work, K_MSEC(TELNET_TIMEOUT));
|
|
}
|
|
}
|
|
|
|
static int telnet_command_length(uint8_t op)
|
|
{
|
|
if (op == NVT_CMD_SB || op == NVT_CMD_WILL || op == NVT_CMD_WILL_NOT ||
|
|
op == NVT_CMD_DO || op == NVT_CMD_DO_NOT) {
|
|
return TELNET_WILL_DO_COMMAND_LEN;
|
|
}
|
|
|
|
return TELNET_MIN_COMMAND_LEN;
|
|
}
|
|
|
|
static inline int telnet_handle_command(struct telnet_simple_command *cmd)
|
|
{
|
|
/* Commands are two or three bytes. */
|
|
if (cmd->iac != NVT_CMD_IAC) {
|
|
return 0;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_SHELL_TELNET_SUPPORT_COMMAND)) {
|
|
LOG_DBG("Got a command %u/%u/%u", cmd->iac, cmd->op, cmd->opt);
|
|
telnet_reply_command(cmd);
|
|
}
|
|
|
|
if (cmd->op == NVT_CMD_SB) {
|
|
/* TODO Add subnegotiation support. */
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void telnet_recv(struct zsock_pollfd *pollfd)
|
|
{
|
|
struct telnet_simple_command *cmd =
|
|
(struct telnet_simple_command *)sh_telnet->cmd_buf;
|
|
size_t len, off, buf_left, cmd_total_len;
|
|
uint8_t *buf;
|
|
int ret;
|
|
|
|
k_mutex_lock(&sh_telnet->rx_lock, K_FOREVER);
|
|
|
|
buf_left = sizeof(sh_telnet->rx_buf) - sh_telnet->rx_len;
|
|
if (buf_left == 0) {
|
|
/* No space left to read TCP stream, try again later. */
|
|
k_mutex_unlock(&sh_telnet->rx_lock);
|
|
k_msleep(10);
|
|
return;
|
|
}
|
|
|
|
buf = sh_telnet->rx_buf + sh_telnet->rx_len;
|
|
|
|
ret = zsock_recv(pollfd->fd, buf, buf_left, 0);
|
|
if (ret < 0) {
|
|
LOG_DBG("Telnet client error %d", ret);
|
|
goto error;
|
|
} else if (ret == 0) {
|
|
LOG_DBG("Telnet client closed connection");
|
|
goto error;
|
|
}
|
|
|
|
off = 0;
|
|
len = ret;
|
|
cmd_total_len = 0;
|
|
/* Filter out and process commands from the data buffer. */
|
|
while (off < len) {
|
|
if (sh_telnet->cmd_len > 0) {
|
|
/* Command mode */
|
|
if (sh_telnet->cmd_len == 1) {
|
|
/* Operation */
|
|
cmd->op = *(buf + off);
|
|
sh_telnet->cmd_len++;
|
|
cmd_total_len++;
|
|
off++;
|
|
|
|
if (telnet_command_length(cmd->op) >
|
|
TELNET_MIN_COMMAND_LEN) {
|
|
continue;
|
|
}
|
|
} else if (sh_telnet->cmd_len == 2) {
|
|
/* Option */
|
|
cmd->opt = *(buf + off);
|
|
sh_telnet->cmd_len++;
|
|
cmd_total_len++;
|
|
off++;
|
|
}
|
|
|
|
ret = telnet_handle_command(cmd);
|
|
if (ret < 0) {
|
|
goto error;
|
|
} else {
|
|
LOG_DBG("Handled command");
|
|
}
|
|
|
|
memset(cmd, 0, sizeof(*cmd));
|
|
sh_telnet->cmd_len = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (*(buf + off) == NVT_CMD_IAC) {
|
|
cmd->iac = *(buf + off);
|
|
sh_telnet->cmd_len++;
|
|
cmd_total_len++;
|
|
off++;
|
|
continue;
|
|
}
|
|
|
|
/* Data byte, remove command bytes from the buffer, if any. */
|
|
if (cmd_total_len > 0) {
|
|
size_t data_off = off;
|
|
|
|
off -= cmd_total_len;
|
|
len -= cmd_total_len;
|
|
cmd_total_len = 0;
|
|
|
|
memmove(buf + off, buf + data_off, len);
|
|
}
|
|
|
|
off++;
|
|
}
|
|
|
|
if (cmd_total_len > 0) {
|
|
/* In case the buffer ended with command, trim the buffer size
|
|
* here.
|
|
*/
|
|
len -= cmd_total_len;
|
|
}
|
|
|
|
if (len == 0) {
|
|
k_mutex_unlock(&sh_telnet->rx_lock);
|
|
return;
|
|
}
|
|
|
|
sh_telnet->rx_len += len;
|
|
|
|
k_mutex_unlock(&sh_telnet->rx_lock);
|
|
|
|
sh_telnet->shell_handler(SHELL_TRANSPORT_EVT_RX_RDY,
|
|
sh_telnet->shell_context);
|
|
|
|
return;
|
|
|
|
error:
|
|
k_mutex_unlock(&sh_telnet->rx_lock);
|
|
telnet_end_client_connection();
|
|
}
|
|
|
|
static void telnet_restart_server(void)
|
|
{
|
|
int ret;
|
|
|
|
if (sh_telnet->fds[SOCK_ID_IPV4_LISTEN].fd >= 0) {
|
|
(void)zsock_close(sh_telnet->fds[SOCK_ID_IPV4_LISTEN].fd);
|
|
sh_telnet->fds[SOCK_ID_IPV4_LISTEN].fd = -1;
|
|
}
|
|
|
|
if (sh_telnet->fds[SOCK_ID_IPV6_LISTEN].fd >= 0) {
|
|
(void)zsock_close(sh_telnet->fds[SOCK_ID_IPV6_LISTEN].fd);
|
|
sh_telnet->fds[SOCK_ID_IPV6_LISTEN].fd = -1;
|
|
}
|
|
|
|
if (sh_telnet->fds[SOCK_ID_CLIENT].fd >= 0) {
|
|
(void)zsock_close(sh_telnet->fds[SOCK_ID_CLIENT].fd);
|
|
sh_telnet->fds[SOCK_ID_CLIENT].fd = -1;
|
|
}
|
|
|
|
ret = telnet_init(sh_telnet);
|
|
if (ret < 0) {
|
|
LOG_ERR("Telnet fatal error, failed to restart server (%d)", ret);
|
|
(void)net_socket_service_unregister(&telnet_server);
|
|
}
|
|
}
|
|
|
|
static void telnet_accept(struct zsock_pollfd *pollfd)
|
|
{
|
|
int sock, ret = 0;
|
|
struct sockaddr addr;
|
|
socklen_t addrlen = sizeof(struct sockaddr);
|
|
|
|
sock = zsock_accept(pollfd->fd, &addr, &addrlen);
|
|
if (sock < 0) {
|
|
ret = -errno;
|
|
NET_ERR("Telnet accept error (%d)", ret);
|
|
return;
|
|
}
|
|
|
|
if (sh_telnet->fds[SOCK_ID_CLIENT].fd > 0) {
|
|
/* Too many connections. */
|
|
ret = 0;
|
|
NET_ERR("Telnet client already connected.");
|
|
goto error;
|
|
}
|
|
|
|
sh_telnet->fds[SOCK_ID_CLIENT].fd = sock;
|
|
sh_telnet->fds[SOCK_ID_CLIENT].events = ZSOCK_POLLIN;
|
|
sh_telnet->rx_len = 0;
|
|
sh_telnet->cmd_len = 0;
|
|
sh_telnet->line_out.len = 0;
|
|
|
|
ret = net_socket_service_register(&telnet_server, sh_telnet->fds,
|
|
ARRAY_SIZE(sh_telnet->fds), NULL);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to register socket service, (%d)", ret);
|
|
sh_telnet->fds[SOCK_ID_CLIENT].fd = -1;
|
|
goto error;
|
|
}
|
|
|
|
LOG_DBG("Telnet client connected (family AF_INET%s)",
|
|
addr.sa_family == AF_INET ? "" : "6");
|
|
|
|
/* Disable echo - if command handling is enabled we reply that we
|
|
* support echo.
|
|
*/
|
|
(void)telnet_echo_set(sh_telnet->shell_context, false);
|
|
|
|
return;
|
|
|
|
error:
|
|
if (sock > 0) {
|
|
(void)zsock_close(sock);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
telnet_restart_server();
|
|
}
|
|
}
|
|
|
|
static void telnet_server_cb(struct k_work *work)
|
|
{
|
|
struct net_socket_service_event *evt =
|
|
CONTAINER_OF(work, struct net_socket_service_event, work);
|
|
int sock_error;
|
|
socklen_t optlen = sizeof(int);
|
|
|
|
if (sh_telnet == NULL) {
|
|
return;
|
|
}
|
|
|
|
if ((evt->event.revents & ZSOCK_POLLERR) ||
|
|
(evt->event.revents & ZSOCK_POLLNVAL)) {
|
|
(void)zsock_getsockopt(evt->event.fd, SOL_SOCKET,
|
|
SO_ERROR, &sock_error, &optlen);
|
|
NET_ERR("Telnet socket %d error (%d)", evt->event.fd, sock_error);
|
|
|
|
if (evt->event.fd == sh_telnet->fds[SOCK_ID_CLIENT].fd) {
|
|
return telnet_end_client_connection();
|
|
}
|
|
|
|
goto error;
|
|
}
|
|
|
|
if (!(evt->event.revents & ZSOCK_POLLIN)) {
|
|
return;
|
|
}
|
|
|
|
if (evt->event.fd == sh_telnet->fds[SOCK_ID_IPV4_LISTEN].fd) {
|
|
return telnet_accept(&sh_telnet->fds[SOCK_ID_IPV4_LISTEN]);
|
|
} else if (evt->event.fd == sh_telnet->fds[SOCK_ID_IPV6_LISTEN].fd) {
|
|
return telnet_accept(&sh_telnet->fds[SOCK_ID_IPV6_LISTEN]);
|
|
} else if (evt->event.fd == sh_telnet->fds[SOCK_ID_CLIENT].fd) {
|
|
return telnet_recv(&sh_telnet->fds[SOCK_ID_CLIENT]);
|
|
}
|
|
|
|
NET_ERR("Unexpected FD received for telnet, restarting service.");
|
|
|
|
error:
|
|
telnet_restart_server();
|
|
}
|
|
|
|
static int telnet_setup_server(struct zsock_pollfd *pollfd, sa_family_t family,
|
|
struct sockaddr *addr, socklen_t addrlen)
|
|
{
|
|
int ret = 0;
|
|
|
|
pollfd->fd = zsock_socket(family, SOCK_STREAM, IPPROTO_TCP);
|
|
if (pollfd->fd < 0) {
|
|
ret = -errno;
|
|
LOG_ERR("Failed to create telnet AF_INET%s socket",
|
|
family == AF_INET ? "" : "6");
|
|
goto error;
|
|
}
|
|
|
|
if (zsock_bind(pollfd->fd, addr, addrlen) < 0) {
|
|
ret = -errno;
|
|
LOG_ERR("Cannot bind on family AF_INET%s (%d)",
|
|
family == AF_INET ? "" : "6", ret);
|
|
goto error;
|
|
}
|
|
|
|
if (zsock_listen(pollfd->fd, 1)) {
|
|
ret = -errno;
|
|
LOG_ERR("Cannot listen on family AF_INET%s (%d)",
|
|
family == AF_INET ? "" : "6", ret);
|
|
goto error;
|
|
}
|
|
|
|
pollfd->events = ZSOCK_POLLIN;
|
|
|
|
LOG_DBG("Telnet console enabled on AF_INET%s",
|
|
family == AF_INET ? "" : "6");
|
|
|
|
return 0;
|
|
|
|
error:
|
|
LOG_ERR("Unable to start telnet on AF_INET%s (%d)",
|
|
family == AF_INET ? "" : "6", ret);
|
|
|
|
if (pollfd->fd >= 0) {
|
|
(void)zsock_close(pollfd->fd);
|
|
pollfd->fd = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int telnet_init(struct shell_telnet *ctx)
|
|
{
|
|
int ret;
|
|
|
|
if (IS_ENABLED(CONFIG_NET_IPV4)) {
|
|
struct sockaddr_in any_addr4 = {
|
|
.sin_family = AF_INET,
|
|
.sin_port = htons(TELNET_PORT),
|
|
.sin_addr = INADDR_ANY_INIT
|
|
};
|
|
|
|
ret = telnet_setup_server(&ctx->fds[SOCK_ID_IPV4_LISTEN],
|
|
AF_INET, (struct sockaddr *)&any_addr4,
|
|
sizeof(any_addr4));
|
|
if (ret < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
|
struct sockaddr_in6 any_addr6 = {
|
|
.sin6_family = AF_INET6,
|
|
.sin6_port = htons(TELNET_PORT),
|
|
.sin6_addr = IN6ADDR_ANY_INIT
|
|
};
|
|
|
|
ret = telnet_setup_server(&ctx->fds[SOCK_ID_IPV6_LISTEN],
|
|
AF_INET6, (struct sockaddr *)&any_addr6,
|
|
sizeof(any_addr6));
|
|
if (ret < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ret = net_socket_service_register(&telnet_server, ctx->fds,
|
|
ARRAY_SIZE(ctx->fds), NULL);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to register socket service, %d", ret);
|
|
goto error;
|
|
}
|
|
|
|
LOG_INF("Telnet shell backend initialized");
|
|
|
|
return 0;
|
|
|
|
error:
|
|
if (ctx->fds[SOCK_ID_IPV4_LISTEN].fd >= 0) {
|
|
(void)zsock_close(ctx->fds[SOCK_ID_IPV4_LISTEN].fd);
|
|
ctx->fds[SOCK_ID_IPV4_LISTEN].fd = -1;
|
|
}
|
|
|
|
if (ctx->fds[SOCK_ID_IPV6_LISTEN].fd >= 0) {
|
|
(void)zsock_close(ctx->fds[SOCK_ID_IPV6_LISTEN].fd);
|
|
ctx->fds[SOCK_ID_IPV6_LISTEN].fd = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Shell API */
|
|
|
|
static int init(const struct shell_transport *transport,
|
|
const void *config,
|
|
shell_transport_handler_t evt_handler,
|
|
void *context)
|
|
{
|
|
int err;
|
|
|
|
sh_telnet = (struct shell_telnet *)transport->ctx;
|
|
|
|
memset(sh_telnet, 0, sizeof(struct shell_telnet));
|
|
for (int i = 0; i < ARRAY_SIZE(sh_telnet->fds); i++) {
|
|
sh_telnet->fds[i].fd = -1;
|
|
}
|
|
|
|
sh_telnet->shell_handler = evt_handler;
|
|
sh_telnet->shell_context = context;
|
|
|
|
err = telnet_init(sh_telnet);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
k_work_init_delayable(&sh_telnet->send_work, telnet_send_prematurely);
|
|
k_mutex_init(&sh_telnet->rx_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int uninit(const struct shell_transport *transport)
|
|
{
|
|
if (sh_telnet == NULL) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int enable(const struct shell_transport *transport, bool blocking)
|
|
{
|
|
if (sh_telnet == NULL) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int telnet_write(const struct shell_transport *transport,
|
|
const void *data, size_t length, size_t *cnt)
|
|
{
|
|
struct shell_telnet_line_buf *lb;
|
|
size_t copy_len;
|
|
int err;
|
|
uint32_t timeout;
|
|
bool was_running;
|
|
|
|
if (sh_telnet == NULL) {
|
|
*cnt = 0;
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (sh_telnet->fds[SOCK_ID_CLIENT].fd < 0 || sh_telnet->output_lock) {
|
|
*cnt = length;
|
|
return 0;
|
|
}
|
|
|
|
*cnt = 0;
|
|
lb = &sh_telnet->line_out;
|
|
|
|
/* Stop the transmission timer, so it does not interrupt the operation.
|
|
*/
|
|
timeout = k_ticks_to_ms_ceil32(
|
|
k_work_delayable_remaining_get(&sh_telnet->send_work));
|
|
was_running = k_work_cancel_delayable_sync(&sh_telnet->send_work,
|
|
&sh_telnet->work_sync);
|
|
|
|
do {
|
|
if (lb->len + length - *cnt > TELNET_LINE_SIZE) {
|
|
copy_len = TELNET_LINE_SIZE - lb->len;
|
|
} else {
|
|
copy_len = length - *cnt;
|
|
}
|
|
|
|
memcpy(lb->buf + lb->len, (uint8_t *)data + *cnt, copy_len);
|
|
lb->len += copy_len;
|
|
|
|
/* Send the data immediately if the buffer is full or line feed
|
|
* is recognized.
|
|
*/
|
|
if (lb->buf[lb->len - 1] == '\n' ||
|
|
lb->len == TELNET_LINE_SIZE) {
|
|
err = telnet_send(true);
|
|
if (err != 0) {
|
|
*cnt = length;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
*cnt += copy_len;
|
|
} while (*cnt < length);
|
|
|
|
if (lb->len > 0) {
|
|
/* Check if the timer was already running, initialize otherwise.
|
|
*/
|
|
timeout = was_running ? timeout : TELNET_TIMEOUT;
|
|
|
|
k_work_reschedule(&sh_telnet->send_work, K_MSEC(timeout));
|
|
}
|
|
|
|
sh_telnet->shell_handler(SHELL_TRANSPORT_EVT_TX_RDY,
|
|
sh_telnet->shell_context);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int telnet_read(const struct shell_transport *transport,
|
|
void *data, size_t length, size_t *cnt)
|
|
{
|
|
size_t read_len;
|
|
|
|
if (sh_telnet == NULL) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (sh_telnet->fds[SOCK_ID_CLIENT].fd < 0) {
|
|
goto no_data;
|
|
}
|
|
|
|
k_mutex_lock(&sh_telnet->rx_lock, K_FOREVER);
|
|
|
|
if (sh_telnet->rx_len == 0) {
|
|
k_mutex_unlock(&sh_telnet->rx_lock);
|
|
goto no_data;
|
|
}
|
|
|
|
read_len = sh_telnet->rx_len;
|
|
if (read_len > length) {
|
|
read_len = length;
|
|
}
|
|
|
|
memcpy(data, sh_telnet->rx_buf, read_len);
|
|
*cnt = read_len;
|
|
|
|
sh_telnet->rx_len -= read_len;
|
|
if (sh_telnet->rx_len > 0) {
|
|
memmove(sh_telnet->rx_buf, sh_telnet->rx_buf + read_len,
|
|
sh_telnet->rx_len);
|
|
}
|
|
|
|
k_mutex_unlock(&sh_telnet->rx_lock);
|
|
|
|
return 0;
|
|
|
|
no_data:
|
|
*cnt = 0;
|
|
return 0;
|
|
}
|
|
|
|
const struct shell_transport_api shell_telnet_transport_api = {
|
|
.init = init,
|
|
.uninit = uninit,
|
|
.enable = enable,
|
|
.write = telnet_write,
|
|
.read = telnet_read
|
|
};
|
|
|
|
static int enable_shell_telnet(void)
|
|
{
|
|
bool log_backend = CONFIG_SHELL_TELNET_INIT_LOG_LEVEL > 0;
|
|
uint32_t level = (CONFIG_SHELL_TELNET_INIT_LOG_LEVEL > LOG_LEVEL_DBG) ?
|
|
CONFIG_LOG_MAX_LEVEL : CONFIG_SHELL_TELNET_INIT_LOG_LEVEL;
|
|
static const struct shell_backend_config_flags cfg_flags =
|
|
SHELL_DEFAULT_BACKEND_CONFIG_FLAGS;
|
|
|
|
return shell_init(&shell_telnet, NULL, cfg_flags, log_backend, level);
|
|
}
|
|
|
|
SYS_INIT(enable_shell_telnet, APPLICATION, CONFIG_SHELL_TELNET_INIT_PRIORITY);
|
|
|
|
const struct shell *shell_backend_telnet_get_ptr(void)
|
|
{
|
|
return &shell_telnet;
|
|
}
|