1101 lines
39 KiB
C
1101 lines
39 KiB
C
/*
|
|
* Copyright 2013 Tenkiv, Inc.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* @file TelnetServer.c
|
|
* @brief Implements a control interface for the Tekdaqc via the Telnet protocol.
|
|
*
|
|
* Implements a control interface for the Tekdaqc via the Telnet protocol. Only a single connection
|
|
* is allowed at a time. Any attempts to connect while an active connection is present will result in
|
|
* an error message from the board.
|
|
*
|
|
* This file based on the Telnet server implementation in the TI Stellaris example Cave Adventure game,
|
|
* in particular, the methods for processing the Telnet state machine.
|
|
*
|
|
* @author Jared Woolston (jwoolston@tenkiv.com)
|
|
* @since v1.0.0.0
|
|
*/
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
/* INCLUDES */
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
|
|
#include "lwip/opt.h"
|
|
#ifdef CONFIG_TELNET_SERVER
|
|
#include "telnetserver.h"
|
|
#include "lwip/tcp.h"
|
|
#include "lwip/debug.h"
|
|
#include "lwip/stats.h"
|
|
#include "telnet_main.h"
|
|
#include <stdio.h>
|
|
#include <inttypes.h>
|
|
#define PRINT_TAG "TELNETD"
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
/* PRIVATE DEFINES */
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
#define SIZE_TOSTRING_BUFFER 512U
|
|
|
|
#define TELNET_PORT 23U
|
|
|
|
#define ERROR_MESSAGE_HEADER "\n\r--------------------\n\rError Message\n\r\tMessage: %s\n\r--------------------\n\r\x1E"
|
|
#define STATUS_MESSAGE_HEADER "\n\r--------------------\n\rStatus Message\n\r\tMessage: %s\n\r--------------------\n\r\x1E"
|
|
#define DEBUG_MESSAGE_HEADER "\n\r--------------------\n\rDebug Message\n\r\tMessage: %s\n\r--------------------\n\r\x1E"
|
|
#define COMMAND_DATA_MESSAGE_HEADER "\n\r--------------------\n\rCommand Data Message\n\r\tMessage: %s\n\r--------------------\n\r\x1E"
|
|
#define COMMAND_REVC_HEADER "%s\n\x1E"
|
|
//#define TELNET_DEBUG
|
|
//#define TELNET_CHAR_DEBUG
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
/* PRIVATE VARIABLES */
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* @internal
|
|
* @brief Pointer to the TCP port being used for the Telnet server.
|
|
*/
|
|
static struct tcp_pcb *telnet_pcb;
|
|
|
|
/**
|
|
* @internal
|
|
* @brief Pointer to the current Telnet server.
|
|
*/
|
|
static TelnetServer_t telnet_server;
|
|
|
|
/**
|
|
* @internal
|
|
* @brief Indicates the connection status of the telnet server.
|
|
*/
|
|
static bool IsConnected = false;
|
|
|
|
/**
|
|
* @internal
|
|
* @brief Buffer for printing the TOSTRING_BUFFER with additional formatting.
|
|
*/
|
|
static char MESSAGE_BUFFER[SIZE_TOSTRING_BUFFER];
|
|
|
|
/**
|
|
* @internal
|
|
* @brief The error message provided when an attempt is made to play the game when
|
|
* it is already being played over a different interface.
|
|
*/
|
|
static const char ErrorMessage[53] =
|
|
"The Tekdaqc is already in use...try again later!\r\n";
|
|
|
|
/**
|
|
* @internal
|
|
* @brief The initialization sequence sent to a remote telnet client when it first connects to the telnet server.
|
|
*/
|
|
static const char TelnetInit[] = { TELNET_IAC, TELNET_DO,
|
|
TELNET_OPT_SUPPRESS_GA, TELNET_IAC, TELNET_WILL, TELNET_OPT_ECHO };
|
|
|
|
/**
|
|
* @internal
|
|
* @brief This telnet server will always suppress go ahead generation, regardless of this setting.
|
|
*/
|
|
static TelnetOpts_t TelnetOptions[] = { { .option = TELNET_OPT_SUPPRESS_GA,
|
|
.flags = (0x01 << OPT_FLAG_WILL) }, { .option = TELNET_OPT_ECHO,
|
|
.flags = (1 << OPT_FLAG_DO) } };
|
|
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
/* PRIVATE FUNCTION PROTOTYPES */
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* @brief Called when the lwIP TCP/IP stack has an incoming connection request on the telnet port.
|
|
*/
|
|
static err_t telnet_accept(void *arg, struct tcp_pcb *pcb, err_t err);
|
|
|
|
/**
|
|
* @brief Called when the lwIP TCP/IP stack has an incoming packet to be processed.
|
|
*/
|
|
static err_t telnet_receive(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
|
|
|
|
/**
|
|
* @brief Called when the lwIP TCP/IP stack has received an acknowledge for data that has been transmitted.
|
|
*/
|
|
static err_t telnet_sent(void *arg, struct tcp_pcb *pcb, u16_t len);
|
|
|
|
/**
|
|
* @brief Called when the lwIP TCP/IP stack has detected an error.
|
|
*/
|
|
static void telnet_error(void *arg, err_t err);
|
|
|
|
/**
|
|
* @brief Clears the internal message string buffer.
|
|
*/
|
|
static void clear_to_message_buffer(void);
|
|
/**
|
|
* @brief Creates an initalizes a Telnet server.
|
|
*/
|
|
static TelnetServer_t* create_telnet_server(void);
|
|
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
/* PRIVATE METHODS */
|
|
/*--------------------------------------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
* @internal
|
|
* This function is called when the lwIP TCP/IP stack has an incoming
|
|
* connection request on the telnet port.
|
|
*
|
|
* @param arg void* Argument pointer passed to the handler by the lwIP stack.
|
|
* @param pcb tcp_pcb* struct The PCB structure this callback is for.
|
|
* @param err lwIP err_t with the current error status.
|
|
* @retval err lwIP err_t with the result of the this function.
|
|
*/
|
|
static err_t telnet_accept(void *arg, struct tcp_pcb *pcb, err_t err)
|
|
{
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Incoming connection requested on Telnet port.\n\r");
|
|
#endif
|
|
err_t ret_err;
|
|
|
|
LWIP_UNUSED_ARG(arg);
|
|
LWIP_UNUSED_ARG(err);
|
|
|
|
/* Check if already connected. */
|
|
if (telnet_is_connected() == true) {
|
|
/* There is already a connected client, so refuse this connection with
|
|
a message indicating this fact. */
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] A connection was attempted while an active connection is open.\n\r");
|
|
#endif
|
|
tcp_accepted(pcb);
|
|
tcp_arg(pcb, NULL);
|
|
tcp_sent(pcb, telnet_sent);
|
|
tcp_write(pcb, ErrorMessage, sizeof(ErrorMessage), 1);
|
|
tcp_output(pcb);
|
|
/* Temporarily accept this connection until the message is transmitted. */
|
|
return (ERR_OK);
|
|
}
|
|
|
|
/* Setup the TCP connection priority. */
|
|
tcp_setprio(pcb, TCP_PRIO_MIN);
|
|
|
|
create_telnet_server();
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Initializing telnet server.\n\r");
|
|
#endif
|
|
tcp_nagle_enable(pcb);
|
|
telnet_server.pcb = pcb;
|
|
telnet_server.pcb->so_options |= SOF_KEEPALIVE;
|
|
telnet_server.pcb->keep_idle = 300000UL; // 5 Minutes
|
|
telnet_server.pcb->keep_intvl = 1000UL; // 1 Second
|
|
telnet_server.pcb->keep_cnt = 9; // 9 Consecutive failures terminate
|
|
/* Mark that a client has connected. */
|
|
IsConnected = true;
|
|
/* Accept this connection. */
|
|
tcp_accepted(pcb);
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] An incoming connection was accepted.\n\r");
|
|
#endif
|
|
|
|
/* Setup the TCP callback argument. */
|
|
tcp_arg(pcb, &telnet_server);
|
|
|
|
/* Initialize lwIP tcp_recv callback function for pcb */
|
|
tcp_recv(pcb, telnet_receive);
|
|
|
|
/* Initialize lwIP tcp_err callback function for pcb */
|
|
tcp_err(pcb, telnet_error);
|
|
|
|
/* Initialize lwIP tcp_poll callback function for pcb */
|
|
tcp_poll(pcb, telnet_poll, 1);
|
|
|
|
/* Setup the TCP sent callback function. */
|
|
tcp_sent(pcb, telnet_sent);
|
|
/* Initialize the count of outstanding bytes. The initial byte acked as
|
|
part of the SYN -> SYN/ACK sequence is included so that the byte count
|
|
works out correctly at the end. */
|
|
telnet_server.outstanding = sizeof(TelnetInit) + 1;
|
|
/* Do not close the telnet connection until requested. */
|
|
telnet_server.close = 0;
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Writing the init messages\n\r");
|
|
#endif
|
|
/* Send the telnet initialization string. */
|
|
tcp_write(pcb, TelnetInit, sizeof(TelnetInit), 1);
|
|
tcp_output(pcb);
|
|
|
|
telnet_write_debug_message("[TELNET] Telnet Server Connected. Welcome!");
|
|
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Telnet Server Connected. Welcome.\n\r");
|
|
#endif
|
|
/* Return a success code. */
|
|
ret_err = ERR_OK;
|
|
return ret_err;
|
|
}
|
|
|
|
/*
|
|
* @internal
|
|
* This function is called when the lwIP TCP/IP stack has an incoming packet to
|
|
* be processed.
|
|
*
|
|
* @param arg void* Argument pointer passed to the handler by the lwIP stack.
|
|
* @param pcb tcp_pcb* struct The PCB structure this callback is for.
|
|
* @param p pbuf* struct The data buffer from the lwIP stack.
|
|
* @param err lwIP err_t with the current error status.
|
|
* @retval err lwIP err_t with the result of the this function.
|
|
*/
|
|
static err_t telnet_receive(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
|
{
|
|
struct pbuf *q;
|
|
unsigned long ulIdx;
|
|
unsigned char *pucData;
|
|
TelnetServer_t* server;
|
|
if (arg != NULL) {
|
|
server = (TelnetServer_t*) arg;
|
|
|
|
/* Process the incoming packet. */
|
|
if ((err == ERR_OK) && (p != NULL)) {
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Processing received packet.\n\r");
|
|
#endif
|
|
/* Accept the packet from TCP. */
|
|
tcp_recved(pcb, p->tot_len);
|
|
/* Loop through the pbufs in this packet. */
|
|
for (q = p, pucData = (unsigned char*) q->payload; q != NULL; q = q->next) {
|
|
/* Loop through the bytes in this pbuf. */
|
|
for (ulIdx = 0; ulIdx < q->len; ulIdx++) {
|
|
/* Process this character. */
|
|
telnet_process_character(pucData[ulIdx]);
|
|
}
|
|
}
|
|
send_messg_to_telnet(TELNET_MESSAGE_CHARS_NOTIFY);
|
|
|
|
/* Free the pbuf. */
|
|
pbuf_free(p);
|
|
} else if ((err == ERR_OK) && (p == NULL)) {
|
|
/* If a null packet is passed in, close the connection. */
|
|
server->length = 0;
|
|
telnet_close();
|
|
}
|
|
} else {
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Could not cast receive args to telnet server struct because they were null.\n\r");
|
|
return ERR_VAL;
|
|
#endif
|
|
}
|
|
/* Return okay. */
|
|
return (ERR_OK);
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* This function is called when the lwIP TCP/IP stack has received an
|
|
* acknowledge for data that has been transmitted.
|
|
*
|
|
* @param arg void* Argument pointer passed to the handler by the lwIP stack.
|
|
* @param pcb tcp_pcb* struct The PCB structure this callback is for.
|
|
* @param len u16_t The number of bytes which were sent.
|
|
* @retval err lwIP err_t with the result of the this function.
|
|
*/
|
|
static err_t telnet_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
|
|
{
|
|
/* See if this is for the game connection or for a secondary connection. */
|
|
if (arg) {
|
|
TelnetServer_t* server = (TelnetServer_t*) arg;
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Sent packet was acknowledged.\n\r");
|
|
#endif
|
|
/* Decrement the count of outstanding bytes. */
|
|
server->outstanding -= len;
|
|
} else {
|
|
/* See if this is the ACK for the error message. */
|
|
if (len == sizeof(ErrorMessage)) {
|
|
/* Close this telnet connection now that the error message has been transmitted. */
|
|
tcp_sent(pcb, 0);
|
|
tcp_close(pcb);
|
|
}
|
|
}
|
|
/* Return OK. */
|
|
return (ERR_OK);
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* This function implements the tcp_err callback function (called when a fatal
|
|
* tcp_connection error occurs. As soon as this function is called, the Telnet
|
|
* server is invalid and should not be used anymore.
|
|
*
|
|
* @param arg Pointer to argument parameter
|
|
* @param err Not used
|
|
* @retval None
|
|
*/
|
|
static void telnet_error(void *arg, err_t err)
|
|
{
|
|
LWIP_UNUSED_ARG(err);
|
|
//#ifdef TELNET_DEBUG
|
|
//LOGD(PRINT_TAG, "[Telnet Server] Telnet error received: %i\n\r", err);
|
|
//#endif
|
|
TelnetServer_t* server;
|
|
server = (TelnetServer_t*) arg;
|
|
if (server != NULL) {
|
|
/* free es structure */
|
|
mem_free(server);
|
|
}
|
|
IsConnected = false;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* Creates and initializes a Telnet server.
|
|
*
|
|
* @param none
|
|
* @retval TelnetServer_t* The structure used to represent the current server.
|
|
*/
|
|
static TelnetServer_t* create_telnet_server(void)
|
|
{
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Allocating memory for server.\n\r");
|
|
#endif
|
|
/* Allocate structure server to maintain Telnet connection information */
|
|
IsConnected = false;
|
|
telnet_server.halt = false;
|
|
telnet_server.recvWrite = 0;
|
|
telnet_server.recvRead = 0;
|
|
telnet_server.previous = 0;
|
|
telnet_server.length = 0;
|
|
for (int i = 0; i < TELNET_BUFFER_LENGTH; ++i) {
|
|
telnet_server.recvBuffer[i] = 0;
|
|
}
|
|
return &telnet_server;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* Clears the internal message string buffer.
|
|
*
|
|
* @param none
|
|
* @retval none
|
|
*/
|
|
static void clear_to_message_buffer(void)
|
|
{
|
|
for (int i = 0; i < SIZE_TOSTRING_BUFFER; ++i) {
|
|
MESSAGE_BUFFER[i] = '\0';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the provided TelnetServer_t struct with default values and creates a TCP port for it.
|
|
*
|
|
* @param none
|
|
* @retval none
|
|
*/
|
|
TelnetStatus_t initialize_telnet_server(void)
|
|
{
|
|
telnet_server.length = 0;
|
|
telnet_server.outstanding = 0;
|
|
for (uint_fast16_t i = 0; i < sizeof(telnet_server.buffer); ++i) {
|
|
telnet_server.buffer[i] = 0;
|
|
}
|
|
/* Create a new tcp pcb */
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Creating TCP port for Telnet server.\n\r");
|
|
#endif
|
|
telnet_pcb = tcp_new();
|
|
|
|
if (telnet_pcb != NULL) {
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Successfully created Telnet TCP port.\n\r");
|
|
#endif
|
|
err_t err;
|
|
/* Bind telnet to port TELNET_PORT */
|
|
err = tcp_bind(telnet_pcb, IP_ADDR_ANY, TELNET_PORT);
|
|
|
|
if (err == ERR_OK) {
|
|
/* Start tcp listening for Telnet PCB */
|
|
telnet_pcb = tcp_listen(telnet_pcb);
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Now listening for incoming connections on port %i\n\r", TELNET_PORT);
|
|
#endif
|
|
/* Initialize LwIP tcp_accept callback function */
|
|
tcp_accept(telnet_pcb, telnet_accept);
|
|
return TELNET_OK;
|
|
} else {
|
|
/* Deallocate the pcb */
|
|
memp_free(MEMP_TCP_PCB, telnet_pcb);
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Can not bind pcb\n\r");
|
|
#endif
|
|
return TELNET_ERR_BIND;
|
|
}
|
|
} else {
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Can not create new TCP port.\n\r");
|
|
#endif
|
|
return TELNET_ERR_PCBCREATE;
|
|
}
|
|
}
|
|
|
|
void close_telnet_tcp(void) {
|
|
if (telnet_is_connected() == true) {
|
|
telnet_close();
|
|
}
|
|
|
|
if (telnet_task_handle != NULL) {
|
|
tcp_accept(telnet_pcb, NULL);
|
|
tcp_close(telnet_pcb);
|
|
memp_free(MEMP_TCP_PCB, telnet_pcb);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* This function is called when the the TCP connection should be closed.
|
|
*
|
|
* @param none
|
|
* @retval none
|
|
*/
|
|
void telnet_close(void)
|
|
{
|
|
// LOGD(PRINT_TAG, "Closing telnet connection.\n\r");
|
|
struct tcp_pcb *pcb = telnet_server.pcb;
|
|
/* Remove all callbacks */
|
|
tcp_arg(pcb, NULL);
|
|
tcp_sent(pcb, NULL);
|
|
tcp_recv(pcb, NULL);
|
|
tcp_err(pcb, NULL);
|
|
tcp_poll(pcb, NULL, 0);
|
|
|
|
/* Clear the telnet data structure pointer, to indicate that there is no longer a connection. */
|
|
telnet_server.pcb = 0;
|
|
|
|
/* Close tcp connection */
|
|
tcp_close(pcb);
|
|
IsConnected = false;
|
|
|
|
/* Re-initialize the Telnet Server */
|
|
//initialize_telnet_server();
|
|
}
|
|
|
|
/**
|
|
* Returns the connection status of the Telnet server. Since this implementation supports only
|
|
* one client at a time, this method can be used to determine if an incoming request can be honored
|
|
* as well as allowing other parts of the program to adjust their behavior depending on if a client
|
|
* is connected.
|
|
*
|
|
* @param none
|
|
* @retval bool TRUE if the telnet server has an active connection.
|
|
*/
|
|
bool telnet_is_connected(void)
|
|
{
|
|
return (IsConnected);
|
|
}
|
|
|
|
/**
|
|
* Called by the lwIP stack when there is data to send/receive to/from the Telnet server.
|
|
*
|
|
* @param arg void* to argument passed to callback by lwIP stack.
|
|
* @param tpcb tcp_pcb* To the tcp_pcb struct for the current tcp connection.
|
|
* @retval err_t The error/status code.
|
|
*/
|
|
err_t telnet_poll(void *arg, struct tcp_pcb *tpcb)
|
|
{
|
|
err_t ret_err;
|
|
TelnetServer_t* server;
|
|
server = (TelnetServer_t*) arg;
|
|
|
|
if (server != NULL) {
|
|
unsigned long length = server->length;
|
|
if ((server->pcb != NULL) && (length != 0)) {
|
|
/* Write the data from the transmit buffer. */
|
|
tcp_write(server->pcb, server->buffer, length, 1);
|
|
|
|
/* Increment the count of outstanding bytes. */
|
|
server->outstanding += length;
|
|
|
|
/* Output the telnet data. */
|
|
tcp_output(server->pcb);
|
|
|
|
/* Reset the size of the data in the transmit buffer. */
|
|
server->length = 0;
|
|
}
|
|
/* See if the telnet connection should be closed; this will only occur once
|
|
all transmitted data has been ACKed by the client (so that some or all
|
|
of the final message is not lost). */
|
|
if (server->pcb && (server->outstanding == 0) && (server->close != 0)) {
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Telnet server should be closed.\n\r");
|
|
#endif
|
|
telnet_close();
|
|
}
|
|
ret_err = ERR_OK;
|
|
} else {
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Cannot process poll request due to null server structure.\n\r");
|
|
#endif
|
|
/* Nothing to be done */
|
|
tcp_abort(tpcb);
|
|
ret_err = ERR_ABRT;
|
|
}
|
|
return ret_err;
|
|
}
|
|
|
|
/**
|
|
* Writes a character into the telnet receive buffer.
|
|
*
|
|
* @param character char The character to write.
|
|
* @retval none
|
|
*/
|
|
void telnet_recv_buffer_write(char character)
|
|
{
|
|
unsigned long ulWrite;
|
|
/* Ignore this character if it is the NULL character. */
|
|
if (character == 0) {
|
|
#ifdef TELNET_CHAR_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Ignorning NULL character.\n\r");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/* Ignore this character if it is the second part of a CR/LF or LF/CR sequence. */
|
|
if (((character == '\r') && (telnet_server.previous == '\n'))
|
|
|| ((character == '\n') && (telnet_server.previous == '\r'))) {
|
|
return;
|
|
}
|
|
|
|
/* Store this character into the receive buffer if there is space for it. */
|
|
ulWrite = telnet_server.recvWrite;
|
|
if (((ulWrite + 1) % sizeof(telnet_server.recvBuffer))
|
|
!= telnet_server.recvRead) {
|
|
telnet_server.recvBuffer[ulWrite] = character;
|
|
telnet_server.recvWrite = (ulWrite + 1) % sizeof(telnet_server.recvBuffer);
|
|
#ifdef TELNET_CHAR_DEBUG
|
|
for (int i = 0; i <= telnet_server.recvWrite; ++i) {
|
|
LOGD(PRINT_TAG, "%c", telnet_server.recvBuffer[i]);
|
|
}
|
|
LOGD(PRINT_TAG, "\n\r");
|
|
#endif
|
|
} else {
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Could not store new character because buffer was full.\n\r");
|
|
#endif
|
|
}
|
|
|
|
/* Save this character as the previously received telnet character. */
|
|
telnet_server.previous = character;
|
|
}
|
|
|
|
/**
|
|
* Reads a character from the telnet interface.
|
|
*
|
|
* @param none
|
|
* @retval char The character read from the telnet interface.
|
|
*/
|
|
char telnet_read(void)
|
|
{
|
|
if (telnet_is_connected() == true){
|
|
uint32_t read;
|
|
char ret;
|
|
/* Return a NULL if there is no data in the receive buffer. */
|
|
read = telnet_server.recvRead;
|
|
if (read == telnet_server.recvWrite) {
|
|
return (0);
|
|
}
|
|
/* Read the next byte from the receive buffer. */
|
|
ret = telnet_server.recvBuffer[read];
|
|
telnet_server.recvRead = (read + 1) % sizeof(telnet_server.recvBuffer);
|
|
/* Return the byte that was read. */
|
|
return (ret);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes a character to the telnet interface.
|
|
*
|
|
* @param character const char The character to write to the interface.
|
|
* @retval none
|
|
*/
|
|
void telnet_write(const char character)
|
|
{
|
|
if (telnet_is_connected() == true) {
|
|
/* Delay until there is some space in the output buffer. The buffer is not
|
|
completly filled here to leave some room for the processing of received
|
|
telnet commands. */
|
|
while (telnet_server.length > (sizeof(telnet_server.buffer) - 32)) {
|
|
#ifdef TELNET_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Telnet buffer is full!\n\r");
|
|
#endif
|
|
/* Handle periodic timers for LwIP */
|
|
sys_ms_sleep(200);
|
|
//LwIP_Periodic_Handle(GetLocalTime());
|
|
}
|
|
|
|
/* Write this character into the output buffer. */
|
|
telnet_server.buffer[telnet_server.length++] = character;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes a string to the specified Telnet server. Disables Ethernet
|
|
* interrupts during this process in order to prevent an intervening
|
|
* interrupt from corrupting the output buffer.
|
|
*
|
|
* @param string char* Pointer to a C-String to write to the interface.
|
|
* @retval none
|
|
*/
|
|
void telnet_write_string(char* string)
|
|
{
|
|
if (telnet_is_connected() == true) {
|
|
#if 0
|
|
Eth_EXTI_Disable();
|
|
#endif
|
|
while (*string) {
|
|
telnet_write(*string);
|
|
++string;
|
|
}
|
|
#if 0
|
|
Eth_EXTI_Enable();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function will handle a WILL request for a telnet option. If it is an
|
|
* option that is known by the telnet server, a DO response will be generated
|
|
* if the option is not already enabled. For unknown options, a DONT response
|
|
* will always be generated.
|
|
*
|
|
* The response (if any) is written into the telnet transmit buffer.
|
|
*
|
|
* @param option char Option for the WILL command.
|
|
* @retval none
|
|
*/
|
|
void telnet_process_will(char option)
|
|
{
|
|
unsigned long ulIdx;
|
|
#ifdef TELNET_CHAR_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Processing WILL command with option: %c/0x%02X\n\r", option, option);
|
|
#endif
|
|
/* Loop through the known options. */
|
|
for (ulIdx = 0; ulIdx < (sizeof(TelnetOptions) / sizeof(TelnetOptions[0])); ulIdx++) {
|
|
/* See if this option matches the option in question. */
|
|
if (TelnetOptions[ulIdx].option == option) {
|
|
/* See if the WILL flag for this option has already been set. */
|
|
if (((TelnetOptions[ulIdx].flags >> OPT_FLAG_WILL) & 0x01) == 0) {
|
|
/* Set the WILL flag for this option. */
|
|
TelnetOptions[ulIdx].flags = (TelnetOptions[ulIdx].flags & 0xFD)
|
|
| (0x01 << OPT_FLAG_WILL);
|
|
/* Send a DO response to this option. */
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_IAC;
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_DO;
|
|
telnet_server.buffer[telnet_server.length++] = option;
|
|
}
|
|
/* Return without any further processing. */
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* This option is not recognized, so send a DONT response. */
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_IAC;
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_DONT;
|
|
telnet_server.buffer[telnet_server.length++] = option;
|
|
}
|
|
|
|
/**
|
|
* This function will handle a WONT request for a telnet option. If it is an
|
|
* option that is known by the telnet server, a DONT response will be generated
|
|
* if the option is not already disabled. For unknown options, a DONT response
|
|
* will always be generated.
|
|
*
|
|
* The response (if any) is written into the telnet transmit buffer.
|
|
*
|
|
* @param server Pointer to the server to process.
|
|
* @param option char Option for the WONT command.
|
|
* @retval none
|
|
*/
|
|
void telnet_process_wont(char option)
|
|
{
|
|
unsigned long ulIdx;
|
|
#ifdef TELNET_CHAR_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Processing WONT command with option: %c/0x%02X\n\r", option, option);
|
|
#endif
|
|
/* Loop through the known options. */
|
|
for (ulIdx = 0; ulIdx < (sizeof(TelnetOptions) / sizeof(TelnetOptions[0])); ulIdx++) {
|
|
/* See if this option matches the option in question. */
|
|
if (TelnetOptions[ulIdx].option == option) {
|
|
/* See if the WILL flag for this option is currently set. */
|
|
if (((TelnetOptions[ulIdx].flags >> OPT_FLAG_WILL) & 0x01) == 1) {
|
|
/* Clear the WILL flag for this option. */
|
|
TelnetOptions[ulIdx].flags = (TelnetOptions[ulIdx].flags & 0xFD)
|
|
| 0x00;
|
|
/* Send a DONT response to this option. */
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_IAC;
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_DONT;
|
|
telnet_server.buffer[telnet_server.length++] = option;
|
|
}
|
|
/* Return without any further processing. */
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* This option is not recognized, so send a DONT response. */
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_IAC;
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_DONT;
|
|
telnet_server.buffer[telnet_server.length++] = option;
|
|
}
|
|
|
|
/**
|
|
* This function will handle a DO request for a telnet option. If it is an
|
|
* option that is known by the telnet server, a WILL response will be generated
|
|
* if the option is not already enabled. For unknown options, a WONT response
|
|
* will always be generated.
|
|
*
|
|
* The response (if any) is written into the telnet transmit buffer.
|
|
*
|
|
* @param option char Option for the DO command.
|
|
* @return none
|
|
*/
|
|
void telnet_process_do(char option)
|
|
{
|
|
unsigned long ulIdx;
|
|
#ifdef TELNET_CHAR_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Processing DO command with option: %c/0x%02X\n\r", option, option);
|
|
#endif
|
|
/* Loop through the known options. */
|
|
for (ulIdx = 0; ulIdx < (sizeof(TelnetOptions) / sizeof(TelnetOptions[0])); ulIdx++) {
|
|
/* See if this option matches the option in question. */
|
|
if (TelnetOptions[ulIdx].option == option) {
|
|
/* See if the DO flag for this option has already been set. */
|
|
if (((TelnetOptions[ulIdx].flags >> OPT_FLAG_DO) & 0x01) == 0) {
|
|
/* Set the DO flag for this option. */
|
|
TelnetOptions[ulIdx].flags = (TelnetOptions[ulIdx].flags & 0xFB)
|
|
| (0x01 << OPT_FLAG_DO);
|
|
/* Send a WILL response to this option. */
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_IAC;
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_WILL;
|
|
telnet_server.buffer[telnet_server.length++] = option;
|
|
}
|
|
/* Return without any further processing. */
|
|
return;
|
|
}
|
|
}
|
|
|
|
// This option is not recognized, so send a WONT response.
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_IAC;
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_WONT;
|
|
telnet_server.buffer[telnet_server.length++] = option;
|
|
}
|
|
|
|
/**
|
|
* This funciton will handle a DONT request for a telnet option. If it is an
|
|
* option that is known by the telnet server, a WONT response will be generated
|
|
* if the option is not already disabled. For unknown options, a WONT resopnse
|
|
* will always be generated.
|
|
*
|
|
* The response (if any) is written into the telnet transmit buffer.
|
|
*
|
|
* @param option char Option for the DONT command.
|
|
* @return none
|
|
*/
|
|
void telnet_process_dont(char option)
|
|
{
|
|
unsigned long ulIdx;
|
|
#ifdef TELNET_CHAR_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Processing DONT command with option: %c/0x%02X\n\r", option, option);
|
|
#endif
|
|
/* Loop through the known options. */
|
|
for (ulIdx = 0; ulIdx < (sizeof(TelnetOptions) / sizeof(TelnetOptions[0])); ulIdx++) {
|
|
/* See if this option matches the option in question. */
|
|
if (TelnetOptions[ulIdx].option == option) {
|
|
/* See if the DO flag for this option is currently set. */
|
|
if (((TelnetOptions[ulIdx].flags >> OPT_FLAG_DO) & 0x01) == 1) {
|
|
/* Clear the DO flag for this option. */
|
|
TelnetOptions[ulIdx].flags = (TelnetOptions[ulIdx].flags & 0xFB)
|
|
| 0x00;
|
|
/* Send a WONT response to this option. */
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_IAC;
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_WONT;
|
|
telnet_server.buffer[telnet_server.length++] = option;
|
|
}
|
|
/* Return without any further processing. */
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* This option is not recognized, so send a WONT response. */
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_IAC;
|
|
telnet_server.buffer[telnet_server.length++] = TELNET_WONT;
|
|
telnet_server.buffer[telnet_server.length++] = option;
|
|
}
|
|
|
|
/*
|
|
* This function processes a character received from the telnet port, handling
|
|
* the interpretation of telnet commands (as indicated by the telnet interpret
|
|
* as command (IAC) byte).
|
|
*
|
|
* @param character char The character to process.
|
|
* @retval none
|
|
*/
|
|
void telnet_process_character(char character)
|
|
{
|
|
#ifdef TELNET_CHAR_DEBUG
|
|
LOGD(PRINT_TAG, "[Telnet Server] Processing Character: %c/0x%02X\n\r", character, character);
|
|
#endif
|
|
/* Determine the current state of the telnet command parser. */
|
|
switch (telnet_server.state) {
|
|
/* The normal state of the parser, were each character is either sent
|
|
to the UART or is a telnet IAC character. */
|
|
case STATE_NORMAL: {
|
|
/* See if this character is the IAC character. */
|
|
if (character == TELNET_IAC) {
|
|
/* Skip this character and go to the IAC state. */
|
|
telnet_server.state = STATE_IAC;
|
|
} else {
|
|
/* Write this character to the receive buffer. */
|
|
telnet_recv_buffer_write(character);
|
|
/* Echo this character */
|
|
telnet_write(character); //Echo the character back
|
|
}
|
|
break;
|
|
}
|
|
/* The previous character was the IAC character. */
|
|
case STATE_IAC: {
|
|
/* Determine how to interpret this character. */
|
|
switch (character) {
|
|
/* See if this character is also an IAC character. */
|
|
case TELNET_IAC: {
|
|
/* Write 0xff to the receive buffer. */
|
|
telnet_recv_buffer_write(0xff);
|
|
/* Switch back to normal mode. */
|
|
telnet_server.state = STATE_NORMAL;
|
|
/* This character has been handled. */
|
|
break;
|
|
}
|
|
/* See if this character is the WILL request. */
|
|
case TELNET_WILL: {
|
|
/* Switch to the WILL mode; the next character will have
|
|
the option in question. */
|
|
telnet_server.state = STATE_WILL;
|
|
/* This character has been handled. */
|
|
break;
|
|
}
|
|
/* See if this character is the WONT request. */
|
|
case TELNET_WONT: {
|
|
/* Switch to the WONT mode; the next character will have
|
|
the option in question. */
|
|
telnet_server.state = STATE_WONT;
|
|
/* This character has been handled. */
|
|
break;
|
|
}
|
|
/* See if this character is the DO request. */
|
|
case TELNET_DO: {
|
|
/* Switch to the DO mode; the next character will have the
|
|
option in question. */
|
|
telnet_server.state = STATE_DO;
|
|
/* This character has been handled. */
|
|
break;
|
|
}
|
|
/* See if this character is the DONT request. */
|
|
case TELNET_DONT: {
|
|
/* Switch to the DONT mode; the next character will have
|
|
the option in question. */
|
|
telnet_server.state = STATE_DONT;
|
|
/* This character has been handled. */
|
|
break;
|
|
}
|
|
/* See if this character is the AYT request. */
|
|
case TELNET_AYT: {
|
|
/* Send a short string back to the client so that it knows
|
|
that the server is still alive. */
|
|
telnet_server.buffer[telnet_server.length++] = '\r';
|
|
telnet_server.buffer[telnet_server.length++] = '\n';
|
|
telnet_server.buffer[telnet_server.length++] = '[';
|
|
telnet_server.buffer[telnet_server.length++] = 'Y';
|
|
telnet_server.buffer[telnet_server.length++] = 'e';
|
|
telnet_server.buffer[telnet_server.length++] = 's';
|
|
telnet_server.buffer[telnet_server.length++] = ']';
|
|
telnet_server.buffer[telnet_server.length++] = '\r';
|
|
telnet_server.buffer[telnet_server.length++] = '\n';
|
|
/* Switch back to normal mode. */
|
|
telnet_server.state = STATE_NORMAL;
|
|
/* This character has been handled. */
|
|
break;
|
|
}
|
|
/* Explicitly ignore the GA and NOP request, plus provide a
|
|
catch-all ignore for unrecognized requests. */
|
|
case TELNET_GA:
|
|
case TELNET_NOP:
|
|
default: {
|
|
/* Switch back to normal mode. */
|
|
telnet_server.state = STATE_NORMAL;
|
|
/* This character has been handled. */
|
|
break;
|
|
}
|
|
}
|
|
/* This state has been handled. */
|
|
break;
|
|
}
|
|
/* The previous character sequence was IAC WILL. */
|
|
case STATE_WILL: {
|
|
/* Process the WILL request on this option. */
|
|
telnet_process_will(character);
|
|
/* Switch back to normal mode. */
|
|
telnet_server.state = STATE_NORMAL;
|
|
/* This state has been handled. */
|
|
break;
|
|
}
|
|
/* The previous character sequence was IAC WONT. */
|
|
case STATE_WONT: {
|
|
/* Process the WONT request on this option. */
|
|
telnet_process_wont(character);
|
|
/* Switch back to normal mode. */
|
|
telnet_server.state = STATE_NORMAL;
|
|
/* This state has been handled. */
|
|
break;
|
|
}
|
|
/* The previous character sequence was IAC DO. */
|
|
case STATE_DO: {
|
|
/* Process the DO request on this option. */
|
|
telnet_process_do(character);
|
|
/* Switch back to normal mode. */
|
|
telnet_server.state = STATE_NORMAL;
|
|
/* This state has been handled. */
|
|
break;
|
|
}
|
|
/* The previous character sequence was IAC DONT. */
|
|
case STATE_DONT: {
|
|
/* Process the DONT request on this option. */
|
|
telnet_process_dont(character);
|
|
/* Switch back to normal mode. */
|
|
telnet_server.state = STATE_NORMAL;
|
|
/* This state has been handled. */
|
|
break;
|
|
}
|
|
/* A catch-all for unknown states. This should never be reached, but
|
|
is provided just in case it is ever needed. */
|
|
default: {
|
|
/* Switch back to normal mode. */
|
|
telnet_server.state = STATE_NORMAL;
|
|
/* This state has been handled. */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print a message to the telnet connection formatted as an error.
|
|
*
|
|
* @param message char* Pointer to the string to send
|
|
* @retval none
|
|
*/
|
|
void telnet_write_error_message(char* message)
|
|
{
|
|
if (telnet_is_connected() == true) {
|
|
clear_to_message_buffer();
|
|
char* character = message;
|
|
while (*character) {
|
|
character++;
|
|
}
|
|
uint16_t n = snprintf(MESSAGE_BUFFER, sizeof(MESSAGE_BUFFER),
|
|
ERROR_MESSAGE_HEADER, message);
|
|
if (n > 0) {
|
|
telnet_write_string(MESSAGE_BUFFER);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print a message to the telnet connection formatted as a status.
|
|
*
|
|
* @param message char* Pointer to the string to send
|
|
* @retval none
|
|
*/
|
|
void telnet_write_wtatus_message(char* message)
|
|
{
|
|
if (telnet_is_connected() == true) {
|
|
clear_to_message_buffer();
|
|
uint8_t count = 0;
|
|
char* character = message;
|
|
while (*character) {
|
|
++character;
|
|
++count;
|
|
}
|
|
uint16_t n = snprintf(MESSAGE_BUFFER, sizeof(MESSAGE_BUFFER),
|
|
STATUS_MESSAGE_HEADER, message);
|
|
if (n > 0) {
|
|
telnet_write_string(MESSAGE_BUFFER);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print a message to the telnet connection formatted as a debug.
|
|
*
|
|
* @param message char* Pointer to the string to send
|
|
* @retval none
|
|
*/
|
|
void telnet_write_debug_message(char* message)
|
|
{
|
|
if (telnet_is_connected() == true) {
|
|
clear_to_message_buffer();
|
|
uint8_t count = 0;
|
|
char* character = message;
|
|
while (*character) {
|
|
++character;
|
|
++count;
|
|
}
|
|
uint16_t n = snprintf(MESSAGE_BUFFER, sizeof(MESSAGE_BUFFER),
|
|
DEBUG_MESSAGE_HEADER, message);
|
|
if (n > 0) {
|
|
telnet_write_string(MESSAGE_BUFFER);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print a message to the telnet connection formatted as a command data.
|
|
*
|
|
* @param message char* Pointer to the string to send
|
|
* @retval none
|
|
*/
|
|
void telnet_write_command_data_message(char* message)
|
|
{
|
|
if (telnet_is_connected() == true) {
|
|
clear_to_message_buffer();
|
|
uint8_t count = 0;
|
|
char* character = message;
|
|
while (*character) {
|
|
++character;
|
|
++count;
|
|
}
|
|
uint16_t n = snprintf(MESSAGE_BUFFER, sizeof(MESSAGE_BUFFER),
|
|
COMMAND_DATA_MESSAGE_HEADER, message);
|
|
if (n > 0) {
|
|
telnet_write_string(MESSAGE_BUFFER);
|
|
}
|
|
}
|
|
}
|
|
|
|
void telnet_write_revc_message(char* message)
|
|
{
|
|
if (telnet_is_connected() == true) {
|
|
clear_to_message_buffer();
|
|
uint8_t count = 0;
|
|
char* character = message;
|
|
while (*character) {
|
|
++character;
|
|
++count;
|
|
}
|
|
uint16_t n = snprintf(MESSAGE_BUFFER, sizeof(MESSAGE_BUFFER),
|
|
COMMAND_REVC_HEADER, message);
|
|
if (n > 0) {
|
|
telnet_write_string(MESSAGE_BUFFER);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|