/* * This file is part of the Micro Python project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2015 Daniel Campora * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include "py/mpconfig.h" #include MICROPY_HAL_H #include "py/obj.h" #include "telnet.h" #include "simplelink.h" #include "modwlan.h" #include "debug.h" #include "mpexception.h" #include "serverstask.h" #include "mperror.h" #include "genhdr/py-version.h" /****************************************************************************** DEFINE PRIVATE CONSTANTS ******************************************************************************/ #define TELNET_PORT 23 #define TELNET_RX_BUFFER_SIZE 256 #define TELNET_MAX_CLIENTS 1 #define TELNET_TX_RETRIES_MAX 25 #define TELNET_WAIT_TIME_MS 7 #define TELNET_LOGIN_RETRIES_MAX 3 #define TELNET_TIMEOUT_MS 300000 // 5 minutes #define TELNET_CYCLE_TIME_MS (SERVERS_CYCLE_TIME_MS * 2) /****************************************************************************** DEFINE PRIVATE TYPES ******************************************************************************/ typedef enum { E_TELNET_RESULT_OK = 0, E_TELNET_RESULT_AGAIN, E_TELNET_RESULT_FAILED } telnet_result_t; typedef enum { E_TELNET_STE_DISABLED = 0, E_TELNET_STE_START, E_TELNET_STE_LISTEN, E_TELNET_STE_CONNECTED, E_TELNET_STE_LOGGED_IN } telnet_state_t; typedef enum { E_TELNET_STE_SUB_WELCOME, E_TELNET_STE_SUB_SND_USER_OPTIONS, E_TELNET_STE_SUB_REQ_USER, E_TELNET_STE_SUB_GET_USER, E_TELNET_STE_SUB_REQ_PASSWORD, E_TELNET_STE_SUB_SND_PASSWORD_OPTIONS, E_TELNET_STE_SUB_GET_PASSWORD, E_TELNET_STE_SUB_INVALID_LOGGIN, E_TELNET_STE_SUB_SND_REPL_OPTIONS, E_TELNET_STE_SUB_LOGGIN_SUCCESS } telnet_connected_substate_t; typedef union { telnet_connected_substate_t connected; } telnet_substate_t; typedef struct { uint8_t *rxBuffer; int16_t sd; int16_t n_sd; int16_t rxWindex; int16_t rxRindex; uint16_t timeout; telnet_state_t state; telnet_substate_t substate; uint8_t txRetries; uint8_t logginRetries; bool enabled; bool credentialsValid; } telnet_data_t; /****************************************************************************** DECLARE PRIVATE DATA ******************************************************************************/ static telnet_data_t telnet_data; static const char* telnet_welcome_msg = "Micro Python " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"; static const char* telnet_request_user = "Login as:"; static const char* telnet_request_password = "Password:"; static const char* telnet_invalid_loggin = "\r\nInvalid credentials, try again\r\n"; static char telnet_loggin_success[] = "\r\nLogin succeeded!\r\nType \"help()\" for more information.\r\n"; static const uint8_t telnet_options_user[] = // IAC WONT ECHO IAC WONT SUPPRESS_GO_AHEAD IAC WILL LINEMODE { 255, 252, 1, 255, 252, 3, 255, 251, 34 }; static const uint8_t telnet_options_pass[] = // IAC WILL ECHO IAC WONT SUPPRESS_GO_AHEAD IAC WILL LINEMODE { 255, 251, 1, 255, 252, 3, 255, 251, 34 }; static const uint8_t telnet_options_repl[] = // IAC WILL ECHO IAC WILL SUPPRESS_GO_AHEAD IAC WONT LINEMODE { 255, 251, 1, 255, 251, 3, 255, 252, 34 }; /****************************************************************************** DECLARE PRIVATE FUNCTIONS ******************************************************************************/ static void telnet_wait_for_enabled (void); static bool telnet_create_socket (void); static void telnet_wait_for_connection (void); static void telnet_send_and_proceed (void *data, _i16 Len, telnet_connected_substate_t next_state); static telnet_result_t telnet_send_non_blocking (void *data, _i16 Len); static telnet_result_t telnet_recv_text_non_blocking (void *buff, _i16 Maxlen, _i16 *rxLen); static void telnet_process (void); static void telnet_parse_input (uint8_t *str, int16_t *len); static bool telnet_send_with_retries (int16_t sd, const void *pBuf, int16_t len); static void telnet_reset (void); /****************************************************************************** DEFINE PUBLIC FUNCTIONS ******************************************************************************/ void telnet_init (void) { // Allocate memory for the receive buffer (from the RTOS heap) ASSERT ((telnet_data.rxBuffer = mem_Malloc(TELNET_RX_BUFFER_SIZE)) != NULL); telnet_data.state = E_TELNET_STE_DISABLED; } void telnet_run (void) { _i16 rxLen; switch (telnet_data.state) { case E_TELNET_STE_DISABLED: telnet_wait_for_enabled(); break; case E_TELNET_STE_START: if (telnet_create_socket()) { telnet_data.state = E_TELNET_STE_LISTEN; } break; case E_TELNET_STE_LISTEN: telnet_wait_for_connection(); break; case E_TELNET_STE_CONNECTED: switch (telnet_data.substate.connected) { case E_TELNET_STE_SUB_WELCOME: telnet_send_and_proceed((void *)telnet_welcome_msg, strlen(telnet_welcome_msg), E_TELNET_STE_SUB_SND_USER_OPTIONS); break; case E_TELNET_STE_SUB_SND_USER_OPTIONS: telnet_send_and_proceed((void *)telnet_options_user, sizeof(telnet_options_user), E_TELNET_STE_SUB_REQ_USER); break; case E_TELNET_STE_SUB_REQ_USER: telnet_send_and_proceed((void *)telnet_request_user, strlen(telnet_request_user), E_TELNET_STE_SUB_GET_USER); break; case E_TELNET_STE_SUB_GET_USER: if (E_TELNET_RESULT_OK == telnet_recv_text_non_blocking(telnet_data.rxBuffer, TELNET_RX_BUFFER_SIZE, &rxLen)) { // Skip /r/n if (rxLen < 2 || memcmp(servers_user, (const char *)telnet_data.rxBuffer, MAX((rxLen - 2), strlen(servers_user)))) { telnet_data.credentialsValid = false; } telnet_data.substate.connected = E_TELNET_STE_SUB_SND_PASSWORD_OPTIONS; } break; case E_TELNET_STE_SUB_SND_PASSWORD_OPTIONS: telnet_send_and_proceed((void *)telnet_options_pass, sizeof(telnet_options_pass), E_TELNET_STE_SUB_REQ_PASSWORD); break; case E_TELNET_STE_SUB_REQ_PASSWORD: telnet_send_and_proceed((void *)telnet_request_password, strlen(telnet_request_password), E_TELNET_STE_SUB_GET_PASSWORD); // to catch a possible "/r/n" that was left telnet_recv_text_non_blocking(telnet_data.rxBuffer, TELNET_RX_BUFFER_SIZE, &rxLen); break; case E_TELNET_STE_SUB_GET_PASSWORD: if (E_TELNET_RESULT_OK == telnet_recv_text_non_blocking(telnet_data.rxBuffer, TELNET_RX_BUFFER_SIZE, &rxLen)) { // Skip /r/n if (rxLen < 2 || memcmp(servers_pass, (const char *)telnet_data.rxBuffer, MAX((rxLen - 2), strlen(servers_pass)))) { telnet_data.credentialsValid = false; } if (telnet_data.credentialsValid) { telnet_data.substate.connected = E_TELNET_STE_SUB_SND_REPL_OPTIONS; } else { telnet_data.substate.connected = E_TELNET_STE_SUB_INVALID_LOGGIN; } } break; case E_TELNET_STE_SUB_INVALID_LOGGIN: if (E_TELNET_RESULT_OK == telnet_send_non_blocking((void *)telnet_invalid_loggin, strlen(telnet_invalid_loggin))) { telnet_data.credentialsValid = true; if (++telnet_data.logginRetries >= TELNET_LOGIN_RETRIES_MAX) { telnet_reset(); } else { telnet_data.substate.connected = E_TELNET_STE_SUB_SND_USER_OPTIONS; } } break; case E_TELNET_STE_SUB_SND_REPL_OPTIONS: telnet_send_and_proceed((void *)telnet_options_repl, sizeof(telnet_options_repl), E_TELNET_STE_SUB_LOGGIN_SUCCESS); break; case E_TELNET_STE_SUB_LOGGIN_SUCCESS: if (E_TELNET_RESULT_OK == telnet_send_non_blocking((void *)telnet_loggin_success, strlen(telnet_loggin_success))) { // fake "enter" key pressed to display the prompt telnet_data.rxBuffer[telnet_data.rxWindex++] = '\r'; telnet_data.state= E_TELNET_STE_LOGGED_IN; } default: break; } break; case E_TELNET_STE_LOGGED_IN: telnet_process(); break; default: break; } if (telnet_data.state >= E_TELNET_STE_CONNECTED) { if (telnet_data.timeout++ > (TELNET_TIMEOUT_MS / TELNET_CYCLE_TIME_MS)) { telnet_reset(); } } } void telnet_tx_strn (const char *str, int len) { if (len > 0 && telnet_data.n_sd > 0) { telnet_send_with_retries(telnet_data.n_sd, str, len); } } void telnet_tx_strn_cooked (const char *str, uint len) { int32_t nslen = 0; const char *_str = str; for (int i = 0; i < len; i++) { if (str[i] == '\n') { telnet_send_with_retries(telnet_data.n_sd, _str, nslen); telnet_send_with_retries(telnet_data.n_sd, "\r\n", 2); _str += nslen + 1; nslen = 0; } else { nslen++; } } if (_str < str + len) { telnet_send_with_retries(telnet_data.n_sd, _str, nslen); } } bool telnet_rx_any (void) { return (telnet_data.n_sd > 0) ? ((telnet_data.rxRindex < telnet_data.rxWindex) && (telnet_data.state == E_TELNET_STE_LOGGED_IN)) : false; } int telnet_rx_char (void) { int rx_char = -1; if (telnet_data.rxRindex < telnet_data.rxWindex) { rx_char = (int)telnet_data.rxBuffer[telnet_data.rxRindex++]; } return rx_char; } void telnet_enable (void) { telnet_data.enabled = true; } void telnet_disable (void) { telnet_reset(); telnet_data.enabled = false; telnet_data.state = E_TELNET_STE_DISABLED; } bool telnet_is_enabled (void) { return telnet_data.enabled; } bool telnet_is_active (void) { return (telnet_data.state == E_TELNET_STE_LOGGED_IN); } /****************************************************************************** DEFINE PRIVATE FUNCTIONS ******************************************************************************/ static void telnet_wait_for_enabled (void) { // Init telnet's data telnet_data.n_sd = -1; telnet_data.sd = -1; // Check if the telnet service has been enabled if (telnet_data.enabled) { telnet_data.state = E_TELNET_STE_START; } } static bool telnet_create_socket (void) { SlSockNonblocking_t nonBlockingOption; sockaddr_in sServerAddress; _i16 result; // Open a socket for telnet ASSERT ((telnet_data.sd = sl_Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) > 0); if (telnet_data.sd > 0) { // Enable non-blocking mode nonBlockingOption.NonblockingEnabled = 1; ASSERT (sl_SetSockOpt(telnet_data.sd, SOL_SOCKET, SL_SO_NONBLOCKING, &nonBlockingOption, sizeof(nonBlockingOption)) == SL_SOC_OK); // Bind the socket to a port number sServerAddress.sin_family = AF_INET; sServerAddress.sin_addr.s_addr = htonl(INADDR_ANY); sServerAddress.sin_port = htons(TELNET_PORT); ASSERT (sl_Bind(telnet_data.sd, (const SlSockAddr_t *)&sServerAddress, sizeof(sServerAddress)) == SL_SOC_OK); // Start listening ASSERT ((result = sl_Listen (telnet_data.sd, TELNET_MAX_CLIENTS)) == SL_SOC_OK); return (result == SL_SOC_OK) ? true : false; } return false; } static void telnet_wait_for_connection (void) { SlSocklen_t in_addrSize; sockaddr_in sClientAddress; // accepts a connection from a TCP client, if there is any, otherwise returns SL_EAGAIN telnet_data.n_sd = sl_Accept(telnet_data.sd, (SlSockAddr_t *)&sClientAddress, (SlSocklen_t *)&in_addrSize); if (telnet_data.n_sd == SL_EAGAIN) { return; } else { // close the listening socket, we don't need it anymore sl_Close(telnet_data.sd); if (telnet_data.n_sd <= 0) { // error telnet_reset(); return; } // client connected, so go on telnet_data.rxWindex = 0; telnet_data.rxRindex = 0; telnet_data.txRetries = 0; telnet_data.state = E_TELNET_STE_CONNECTED; telnet_data.substate.connected = E_TELNET_STE_SUB_WELCOME; telnet_data.credentialsValid = true; telnet_data.logginRetries = 0; telnet_data.timeout = 0; } } static void telnet_send_and_proceed (void *data, _i16 Len, telnet_connected_substate_t next_state) { if (E_TELNET_RESULT_OK == telnet_send_non_blocking(data, Len)) { telnet_data.substate.connected = next_state; } } static telnet_result_t telnet_send_non_blocking (void *data, _i16 Len) { int16_t result = sl_Send(telnet_data.n_sd, data, Len, 0); if (result > 0) { telnet_data.txRetries = 0; return E_TELNET_RESULT_OK; } else if ((TELNET_TX_RETRIES_MAX >= ++telnet_data.txRetries) && (result == SL_EAGAIN)) { return E_TELNET_RESULT_AGAIN; } else { // error telnet_reset(); return E_TELNET_RESULT_FAILED; } } static telnet_result_t telnet_recv_text_non_blocking (void *buff, _i16 Maxlen, _i16 *rxLen) { *rxLen = sl_Recv(telnet_data.n_sd, buff, Maxlen, 0); // if there's data received, parse it if (*rxLen > 0) { telnet_data.timeout = 0; telnet_parse_input (buff, rxLen); if (*rxLen > 0) { return E_TELNET_RESULT_OK; } } else if (*rxLen != SL_EAGAIN) { // error telnet_reset(); return E_TELNET_RESULT_FAILED; } return E_TELNET_RESULT_AGAIN; } static void telnet_process (void) { if (!telnet_rx_any()) { telnet_data.rxWindex = 0; telnet_data.rxRindex = 0; } if (telnet_data.rxWindex < TELNET_RX_BUFFER_SIZE) { _i16 rxLen; if (E_TELNET_RESULT_OK == telnet_recv_text_non_blocking(&telnet_data.rxBuffer[telnet_data.rxWindex], TELNET_RX_BUFFER_SIZE - telnet_data.rxWindex, &rxLen)) { telnet_data.rxWindex += rxLen; } } } static void telnet_parse_input (uint8_t *str, int16_t *len) { int16_t b_len = *len; uint8_t *b_str = str; for (uint8_t *_str = b_str; _str < b_str + b_len; ) { if (*_str <= 127) { if (telnet_data.state == E_TELNET_STE_LOGGED_IN && *_str == user_interrupt_char) { // raise keyboard exception mpexception_keyboard_nlr_jump(); (*len)--; _str++; } else { *str++ = *_str++; } } else { _str += 3; *len -= 3; } } } static bool telnet_send_with_retries (int16_t sd, const void *pBuf, int16_t len) { int32_t retries = 0; // abort sending if we happen to be within interrupt context if ((HAL_NVIC_INT_CTRL_REG & HAL_VECTACTIVE_MASK) == 0) { do { _i16 result = sl_Send(sd, pBuf, len, 0); if (result > 0) { return true; } else if (SL_EAGAIN != result) { return false; } HAL_Delay (TELNET_WAIT_TIME_MS); } while (++retries <= TELNET_TX_RETRIES_MAX); } else { // blink the system led mperror_signal_error(); } return false; } static void telnet_reset (void) { // close the connection and start all over again servers_close_socket(&telnet_data.n_sd); servers_close_socket(&telnet_data.sd); telnet_data.state = E_TELNET_STE_START; }