/ firmware / src / networking / telnet.cpp
telnet.cpp
  1  #include "telnet.h"
  2  
  3  #if CERATINA_TELNET_ENABLED
  4  
  5  #include "../console/remote.h"
  6  #include <led.h>
  7  
  8  #include <Arduino.h>
  9  #include <ESPTelnet.h>
 10  #include <EscapeCodes.h>
 11  
 12  //------------------------------------------
 13  //  Telnet transport
 14  //------------------------------------------
 15  static ESPTelnet telnet_inst;
 16  static EscapeCodes ansi;
 17  static bool is_started = false;
 18  static String client_ip_str;
 19  
 20  static char ring_buf[config::telnet::RING_SIZE];
 21  static char wbuf[config::telnet::WRITE_BUF];
 22  static char line[config::shell::BUF_IN];
 23  
 24  static void telnet_flush(const char *data, size_t len, void *ctx) {
 25    (void)ctx;
 26    telnet_inst.write((const uint8_t *)data, len);
 27  }
 28  
 29  static console::remote::Shell shell(
 30    ring_buf, config::telnet::RING_SIZE,
 31    wbuf, config::telnet::WRITE_BUF,
 32    line, config::shell::BUF_IN,
 33    telnet_flush, nullptr
 34  );
 35  
 36  //------------------------------------------
 37  //  Connection callbacks
 38  //------------------------------------------
 39  static void on_connect(String ip) {
 40    client_ip_str = ip;
 41    Serial.printf("[telnet] client connected from %s\n", ip.c_str());
 42    LED.set(colors::Cyan);
 43    shell.reset();
 44    telnet_inst.print(ansi.cls());
 45    shell.send_motd("Telnet");
 46    shell.send_prompt();
 47  }
 48  
 49  static void on_disconnect(String ip) {
 50    shell.save_history();
 51    Serial.printf("[telnet] client disconnected (%s)\n", ip.c_str());
 52    client_ip_str = "";
 53    LED.set(colors::Green);
 54  }
 55  
 56  static void on_reconnect(String ip) {
 57    Serial.printf("[telnet] client reconnected from %s\n", ip.c_str());
 58  }
 59  
 60  static void on_connection_attempt(String ip) {
 61    Serial.printf("[telnet] rejected connection from %s (session active: %s)\n",
 62                  ip.c_str(), client_ip_str.c_str());
 63  }
 64  
 65  static void on_input(String input) {
 66    for (size_t i = 0; i < input.length(); i++) {
 67      char ch = input[i];
 68      if (ch == '\r') continue;
 69      shell.push_input(ch);
 70    }
 71  }
 72  
 73  void networking::telnet::initialize() {
 74    if (is_started) return;
 75  
 76  //------------------------------------------
 77  //  Public API
 78  //------------------------------------------
 79    telnet_inst.onConnect(on_connect);
 80    telnet_inst.onDisconnect(on_disconnect);
 81    telnet_inst.onReconnect(on_reconnect);
 82    telnet_inst.onConnectionAttempt(on_connection_attempt);
 83    telnet_inst.onInputReceived(on_input);
 84    telnet_inst.setLineMode(false);
 85    telnet_inst.setKeepAliveInterval(config::telnet::KEEPALIVE_MS);
 86  
 87    if (!telnet_inst.begin(config::telnet::PORT, false)) {
 88      Serial.println(F("[telnet] failed to start"));
 89      return;
 90    }
 91  
 92    is_started = true;
 93    Serial.printf("[telnet] listening on port %d\n", config::telnet::PORT);
 94  }
 95  
 96  void networking::telnet::service() {
 97    if (!is_started) return;
 98    telnet_inst.loop();
 99    if (!telnet_inst.isConnected()) return;
100    shell.service();
101  }
102  
103  bool networking::telnet::isConnected() {
104    return is_started && telnet_inst.isConnected();
105  }
106  
107  const char *networking::telnet::clientIP() {
108    return client_ip_str.c_str();
109  }
110  
111  void networking::telnet::disconnect() {
112    if (is_started && telnet_inst.isConnected())
113      telnet_inst.disconnectClient();
114  }
115  
116  #else
117  
118  void networking::telnet::initialize() {}
119  void networking::telnet::service() {}
120  bool networking::telnet::isConnected() { return false; }
121  const char *networking::telnet::clientIP() { return ""; }
122  void networking::telnet::disconnect() {}
123  
124  #endif
125  
126  #ifdef PIO_UNIT_TESTING
127  
128  #include "telnet.h"
129  #include <testing/utils.h>
130  
131  #include <Arduino.h>
132  
133  static void test_telnet_config(void) {
134    GIVEN("telnet is enabled");
135    THEN("the configuration is valid");
136  
137  #if CERATINA_TELNET_ENABLED
138    TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(0, config::telnet::PORT,
139      "device: telnet port must be > 0");
140    TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(0, config::telnet::RING_SIZE,
141      "device: ring buffer must be > 0");
142    TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(0, config::telnet::WRITE_BUF,
143      "device: write buffer must be > 0");
144  
145    TEST_PRINTF("telnet enabled on port %d", config::telnet::PORT);
146  #else
147    TEST_IGNORE_MESSAGE("telnet not enabled");
148  #endif
149  }
150  
151  void networking::telnet::test(void) {
152    MODULE("Telnet");
153    RUN_TEST(test_telnet_config);
154  }
155  
156  #endif