initial commit

This commit is contained in:
proddy
2020-07-05 18:29:08 +02:00
parent 26b201ea2f
commit c5933e8c14
739 changed files with 86566 additions and 20952 deletions

View File

@@ -0,0 +1,358 @@
/*
* uuid-telnet - Telnet service
* Copyright 2019 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "uuid/telnet.h"
#include <Arduino.h>
#include <algorithm>
#include <string>
#include <vector>
namespace uuid {
namespace telnet {
TelnetStream::TelnetStream(WiFiClient &client)
: client_(client) {
output_buffer_.reserve(BUFFER_SIZE);
}
void TelnetStream::start() {
raw_write({
IAC, WILL, OPT_ECHO,
IAC, WILL, OPT_BINARY,
IAC, WILL, OPT_SGA,
IAC, DONT, OPT_ECHO,
IAC, DO, OPT_BINARY,
IAC, DO, OPT_SGA
});
}
int TelnetStream::available() {
if (peek() == -1) {
return 0;
} else {
return 1;
}
}
int TelnetStream::read() {
if (peek_ != -1) {
int data = peek_;
peek_ = -1;
return data;
}
buffer_flush();
restart:
int data = raw_read();
if (data == -1) {
return -1;
}
unsigned char c = data;
if (sub_negotiation_) {
if (previous_raw_in_ == IAC) {
switch (c) {
case SE:
sub_negotiation_ = false;
previous_raw_in_ = 0;
goto restart;
case IAC:
previous_raw_in_ = 0;
goto restart;
}
} else {
switch (c) {
case IAC:
previous_raw_in_ = c;
goto restart;
default:
previous_raw_in_ = 0;
goto restart;
}
}
} else {
if (previous_raw_in_ == IAC) {
switch (c) {
case IP:
// Interrupt (^C)
previous_raw_in_ = 0;
c = '\x03';
break;
case EC:
// Backspace (^H)
previous_raw_in_ = 0;
c = '\x08';
break;
case EL:
// Delete line (^U)
previous_raw_in_ = 0;
c = '\x15';
break;
case IAC:
previous_raw_in_ = 0;
break;
case SB:
case WILL:
case WONT:
case DO:
case DONT:
previous_raw_in_ = c;
goto restart;
case SE:
case DM:
case BRK:
case AO:
case AYT:
case GA:
case NOP:
default:
previous_raw_in_ = 0;
goto restart;
}
} else if (previous_raw_in_ == SB) {
sub_negotiation_ = true;
previous_raw_in_ = 0;
goto restart;
} else if (previous_raw_in_ == WILL || previous_raw_in_ == WONT) {
switch (c) {
case OPT_ECHO:
// Don't do these
raw_write({IAC, DONT, c});
break;
case OPT_BINARY:
case OPT_SGA:
// Do these
raw_write({IAC, DO, c});
break;
default:
// Don't do anything else
raw_write({IAC, DONT, c});
break;
}
previous_raw_in_ = 0;
goto restart;
} else if (previous_raw_in_ == DO) {
switch (c) {
case OPT_ECHO:
case OPT_BINARY:
case OPT_SGA:
// These are always enabled
break;
default:
// Refuse to do anything else
raw_write({IAC, WONT, c});
break;
}
previous_raw_in_ = 0;
goto restart;
} else if (previous_raw_in_ == DONT) {
switch (c) {
case OPT_ECHO:
case OPT_BINARY:
case OPT_SGA:
// Insist that we do these
raw_write({IAC, WILL, c});
break;
default:
// Everything else is always disabled
break;
}
previous_raw_in_ = 0;
goto restart;
} else {
switch (c) {
case IAC:
previous_raw_in_ = c;
goto restart;
default:
previous_raw_in_ = 0;
break;
}
}
}
if (previous_in_ == CR) {
if (c == NUL) {
previous_in_ = 0;
goto restart;
}
}
previous_in_ = c;
return c;
}
int TelnetStream::peek() {
buffer_flush();
// It's too complicated to implement this by calling peek()
// on the original stream, especially if the original stream
// doesn't actually support peeking.
if (peek_ == -1) {
peek_ = read();
}
return peek_;
}
size_t TelnetStream::write(uint8_t data) {
if (previous_out_ == CR && data != LF) {
previous_out_ = data;
if (raw_write({NUL, data}) != 2) {
return 0;
}
} else {
previous_out_ = data;
}
if (data == IAC) {
if (raw_write({IAC, IAC}) != 2) {
return 0;
}
} else {
if (raw_write(data) != 1) {
return 0;
}
}
return 1;
}
size_t TelnetStream::write(const uint8_t *buffer, size_t size) {
std::vector<unsigned char> data;
data.reserve(size);
while (size-- > 0) {
unsigned char c = *buffer++;
if (previous_out_ == CR && c != LF) {
data.push_back((unsigned char)NUL);
}
if (c == IAC) {
data.push_back((unsigned char)IAC);
}
previous_out_ = c;
data.push_back(c);
}
size_t len = raw_write(data);
if (len < size) {
len = 0;
}
return len;
}
void TelnetStream::flush() {
// This is a pure virtual function in Arduino's Stream class, which
// makes no sense because that class is for input and this is an
// output function. Later versions move it to Print as an empty
// virtual function so this is here for backward compatibility.
}
int TelnetStream::raw_available() {
return client_.available();
}
int TelnetStream::raw_read() {
return client_.read();
}
void TelnetStream::buffer_flush() {
if (!output_buffer_.empty()) {
size_t len = client_.write(reinterpret_cast<const unsigned char*>(output_buffer_.data()), output_buffer_.size());
if (len != output_buffer_.size()) {
client_.stop();
}
output_buffer_.clear();
output_buffer_.shrink_to_fit();
}
}
size_t TelnetStream::raw_write(unsigned char data) {
output_buffer_.push_back(data);
if (output_buffer_.size() >= BUFFER_SIZE) {
buffer_flush();
}
return 1;
}
size_t TelnetStream::raw_write(const std::vector<unsigned char> &data) {
return raw_write(reinterpret_cast<const unsigned char*>(data.data()), data.size());
}
size_t TelnetStream::raw_write(const uint8_t *buffer, size_t size) {
size_t offset = 0;
size_t remaining = size;
if (!output_buffer_.empty()) {
// Fill the rest of the buffer
size_t block = std::min(remaining, BUFFER_SIZE - output_buffer_.size());
output_buffer_.insert(output_buffer_.end(), buffer, buffer + block);
offset += block;
remaining -= block;
if (output_buffer_.size() >= BUFFER_SIZE) {
buffer_flush();
}
}
if (remaining >= BUFFER_SIZE) {
// Output directly if it won't fit in the buffer
size_t len = client_.write(buffer + offset, remaining);
if (len != remaining) {
client_.stop();
return offset + len;
}
} else if (remaining > 0) {
// Put the rest in the buffer
output_buffer_.insert(output_buffer_.end(), buffer + offset, buffer + offset + remaining);
}
return size;
}
} // namespace telnet
} // namespace uuid

View File

@@ -0,0 +1,237 @@
/*
* uuid-telnet - Telnet service
* Copyright 2019 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "uuid/telnet.h"
#include <Arduino.h>
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
#include <WiFiUdp.h>
#include <algorithm>
#include <list>
#include <memory>
#include <string>
#include <uuid/common.h>
#include <uuid/log.h>
#ifndef UUID_TELNET_HAVE_WIFICLIENT_REMOTE
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#define UUID_TELNET_HAVE_WIFICLIENT_REMOTE 1
#else
#define UUID_TELNET_HAVE_WIFICLIENT_REMOTE 0
#endif
#endif
#ifndef UUID_TELNET_HAVE_WIFICLIENT_NODELAY
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#define UUID_TELNET_HAVE_WIFICLIENT_NODELAY 1
#else
#define UUID_TELNET_HAVE_WIFICLIENT_NODELAY 0
#endif
#endif
#ifndef UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE
#if defined(ARDUINO_ARCH_ESP8266)
#define UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE 1
#else
#define UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE 0
#endif
#endif
static const char __pstr__logger_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = "telnet";
namespace uuid {
namespace telnet {
uuid::log::Logger TelnetService::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::DAEMON};
TelnetService::TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags)
: TelnetService(DEFAULT_PORT, commands, context, flags) {
}
TelnetService::TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags)
: TelnetService(port,
[commands, context, flags](Stream & stream, const IPAddress & addr __attribute__((unused)), uint16_t port __attribute__((unused)))
-> std::shared_ptr<uuid::console::Shell> { return std::make_shared<uuid::console::StreamConsole>(commands, stream, context, flags); }) {
}
TelnetService::TelnetService(shell_factory_function shell_factory)
: TelnetService(DEFAULT_PORT, shell_factory) {
}
TelnetService::TelnetService(uint16_t port, shell_factory_function shell_factory)
: server_(port)
, shell_factory_(shell_factory) {
}
void TelnetService::start() {
server_.begin();
}
void TelnetService::close_all() {
while (!connections_.empty()) {
connections_.front().stop();
connections_.pop_front();
}
}
void TelnetService::stop() {
server_.stop();
}
size_t TelnetService::maximum_connections() const {
return maximum_connections_;
}
void TelnetService::maximum_connections(size_t count) {
maximum_connections_ = std::max((size_t)1, count);
while (connections_.size() > maximum_connections_) {
for (auto it = connections_.begin(); it != connections_.end();) {
if (it->active()) {
it->stop();
it = connections_.erase(it);
break;
} else {
it = connections_.erase(it);
}
}
}
}
unsigned long TelnetService::initial_idle_timeout() const {
return initial_idle_timeout_;
}
void TelnetService::initial_idle_timeout(unsigned long timeout) {
initial_idle_timeout_ = timeout;
}
unsigned long TelnetService::default_write_timeout() const {
return write_timeout_;
}
void TelnetService::default_write_timeout(unsigned long timeout) {
write_timeout_ = timeout;
}
void TelnetService::loop() {
for (auto it = connections_.begin(); it != connections_.end();) {
if (!it->loop()) {
it = connections_.erase(it);
} else {
it++;
}
}
WiFiClient client = server_.available();
if (client) {
if (connections_.size() >= maximum_connections_) {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
logger_.info(F("New connection from [%s]:%u rejected (connection limit reached)"),
uuid::printable_to_string(client.remoteIP()).c_str(),
client.remotePort());
#else
logger_.info(F("New connection rejected (connection limit reached)"));
#endif
client.println(F("Maximum connection limit reached"));
client.stop();
} else {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
logger_.info(F("New connection from [%s]:%u accepted"), uuid::printable_to_string(client.remoteIP()).c_str(), client.remotePort());
#endif
connections_.emplace_back(shell_factory_, std::move(client), initial_idle_timeout_, write_timeout_);
#if !(UUID_TELNET_HAVE_WIFICLIENT_REMOTE)
logger_.info(F("New connection %p accepted"), &connections_.back());
#endif
}
}
}
TelnetService::Connection::Connection(shell_factory_function & shell_factory, WiFiClient && client, unsigned long idle_timeout, unsigned long write_timeout)
: client_(std::move(client))
, stream_(client_) {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
// These have to be copied because they're not accessible on closed connections
addr_ = client_.remoteIP();
port_ = client_.remotePort();
#else
port_ = 0;
#endif
#if UUID_TELNET_HAVE_WIFICLIENT_NODELAY
client_.setNoDelay(true);
#endif
#if UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE
// Disconnect after 30 seconds without a response
client_.keepAlive(5, 5, 5);
#endif
if (write_timeout > 0) {
client_.setTimeout(write_timeout);
}
stream_.start();
if (client_.connected()) {
std::shared_ptr<uuid::console::Shell> shell = shell_factory(stream_, addr_, port_);
shell->idle_timeout(idle_timeout);
shell->start();
shell_ = shell;
} else {
shell_ = nullptr;
}
}
bool TelnetService::Connection::active() {
return shell_.use_count() > 1;
}
bool TelnetService::Connection::loop() {
if (active()) {
if (!client_.connected()) {
shell_->stop();
}
return true;
} else {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
logger_.info(F("Connection from [%s]:%u closed"), uuid::printable_to_string(addr_).c_str(), port_);
#else
logger_.info(F("Connection %p closed"), this);
#endif
return false;
}
}
void TelnetService::Connection::stop() {
if (shell_) {
shell_->stop();
}
}
} // namespace telnet
} // namespace uuid

View File

@@ -0,0 +1,441 @@
/*
* uuid-telnet - Telnet service
* Copyright 2019 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UUID_TELNET_H_
#define UUID_TELNET_H_
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP8266
# include <ESP8266WiFi.h>
#else
# include <WiFi.h>
#endif
#include <WiFiUdp.h>
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <vector>
#include <uuid/console.h>
namespace uuid {
/**
* Telnet service.
*
* - <a href="https://github.com/nomis/mcu-uuid-telnet/">Git Repository</a>
* - <a href="https://mcu-uuid-telnet.readthedocs.io/">Documentation</a>
*/
namespace telnet {
/**
* Stream wrapper that performs telnet protocol handling, option
* negotiation and output buffering.
*
* @since 0.1.0
*/
class TelnetStream: public ::Stream {
public:
/**
* Create a new telnet stream wrapper.
*
* @param[in] client Client connection.
* @since 0.1.0
*/
explicit TelnetStream(WiFiClient &client);
virtual ~TelnetStream() = default;
/**
* Perform initial negotiation.
*
* @since 0.1.0
*/
void start();
/**
* Check for available input.
*
* @return The number of bytes available to read.
* @since 0.1.0
*/
int available() override;
/**
* Read one byte from the available input.
*
* @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0
*/
int read() override;
/**
* Read one byte from the available input without advancing to the
* next one.
*
* @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0
*/
int peek() override;
/**
* Write one byte to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] data Data to be output.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t write(uint8_t data) override;
/**
* Write an array of bytes to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] buffer Buffer to be output.
* @param[in] size Length of the buffer.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t write(const uint8_t *buffer, size_t size) override;
/**
* Does nothing.
*
* This is a pure virtual function in Arduino's Stream class, which
* makes no sense because that class is for input and this is an
* output function. Later versions move it to Print as an empty
* virtual function so this is here for backward compatibility.
*
* @since 0.1.0
*/
void flush() override;
private:
static constexpr const unsigned char NUL = 0; /*!< No operation. @since 0.1.0 */
static constexpr const unsigned char BEL = 7; /*!< Produces an audible or visible signal. @since 0.1.0 */
static constexpr const unsigned char BS = 8; /*!< Moves the print head one character position towards the left margin. @since 0.1.0 */
static constexpr const unsigned char HT = 9; /*!< Moves the printer to the next horizontal tab stop. @since 0.1.0 */
static constexpr const unsigned char LF = 10; /*!< Line Feed. @since 0.1.0 */
static constexpr const unsigned char VT = 11; /*!< Moves the printer to the next vertical tab stop. @since 0.1.0 */
static constexpr const unsigned char FF = 12; /*!< Moves the printer to the top of the next page, keeping the same horizontal position. @since 0.1.0 */
static constexpr const unsigned char CR = 13; /*!< Carriage Return. @since 0.1.0 */
static constexpr const unsigned char SE = 240; /*!< End of sub-negotiation parameters. @since 0.1.0 */
static constexpr const unsigned char NOP = 241; /*!< No operation. @since 0.1.0 */
static constexpr const unsigned char DM = 242; /*!< The data stream portion of a Synch. @since 0.1.0 */
static constexpr const unsigned char BRK = 243; /*!< NVT character BRK. @since 0.1.0 */
static constexpr const unsigned char IP = 244; /*!< Interrupt Process function. @since 0.1.0 */
static constexpr const unsigned char AO = 245; /*!< Abort Output function. @since 0.1.0 */
static constexpr const unsigned char AYT = 246; /*!< Are You There function. @since 0.1.0 */
static constexpr const unsigned char EC = 247; /*!< Erase Character function. @since 0.1.0 */
static constexpr const unsigned char EL = 248; /*!< Erase Line function. @since 0.1.0 */
static constexpr const unsigned char GA = 249; /*!< Go Ahead signal. @since 0.1.0 */
static constexpr const unsigned char SB = 250; /*!< Sub-negotiation of the indicated option. @since 0.1.0 */
static constexpr const unsigned char WILL = 251; /*!< Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. @since 0.1.0 */
static constexpr const unsigned char WONT = 252; /*!< Indicates the refusal to perform, or continue performing, the indicated option. @since 0.1.0 */
static constexpr const unsigned char DO = 253; /*!< Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option. @since 0.1.0 */
static constexpr const unsigned char DONT = 254; /*!< Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option. @since 0.1.0 */
static constexpr const unsigned char IAC = 255; /*!< Interpret As Command escape character. @since 0.1.0 */
static constexpr const unsigned char OPT_BINARY = 0; /*!< Binary (8-bit) transmission mode. (RFC 856). @since 0.1.0 */
static constexpr const unsigned char OPT_ECHO = 1; /*!< Remote Echo (RFC 857). @since 0.1.0 */
static constexpr const unsigned char OPT_SGA = 3; /*!< Suppress Go Ahead (RFC 858). @since 0.1.0 */
static constexpr const size_t BUFFER_SIZE = 536; /*!< Output buffer size. @since 0.1.0 */
TelnetStream(const TelnetStream&) = delete;
TelnetStream& operator=(const TelnetStream&) = delete;
/**
* Directly check for available input.
*
* @return The number of bytes available to read.
* @since 0.1.0
*/
int raw_available();
/**
* Read one byte directly from the available input.
*
* @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0
*/
int raw_read();
/**
* Flush output stream buffer.
*
* Disconnect the client if the socket buffer is full.
*
* @since 0.1.0
*/
void buffer_flush();
/**
* Write one byte directly to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] data Data to be output.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t raw_write(unsigned char data);
/**
* Write a vector of bytes directly to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] data Data to be output.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t raw_write(const std::vector<unsigned char> &data);
/**
* Write an array of bytes directly to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] buffer Buffer to be output.
* @param[in] size Length of the buffer.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t raw_write(const uint8_t *buffer, size_t size);
WiFiClient &client_; /*!< Client connection. @since 0.1.0 */
unsigned char previous_raw_in_ = 0; /*!< Previous raw character that was received. Used to detect commands. @since 0.1.0 */
bool sub_negotiation_ = false; /*!< Sub-negotiation mode. @since 0.1.0 */
unsigned char previous_in_ = 0; /*!< Previous character that was received. Used to detect CR NUL. @since 0.1.0 */
unsigned char previous_out_ = 0; /*!< Previous character that was sent. Used to insert NUL after CR without LF. @since 0.1.0 */
int peek_ = -1; /*!< Previously read data cached by peek(). @since 0.1.0 */
std::vector<char> output_buffer_; /*!< Buffer data to be output until a read function is called. @since 0.1.0 */
};
/**
* Provides access to a console shell as a telnet server.
*
* @since 0.1.0
*/
class TelnetService {
public:
static constexpr size_t MAX_CONNECTIONS = 3; /*!< Maximum number of concurrent open connections. @since 0.1.0 */
static constexpr uint16_t DEFAULT_PORT = 23; /*!< Default TCP port to listen on. @since 0.1.0 */
static constexpr unsigned long DEFAULT_IDLE_TIMEOUT = 600; /*!< Default initial idle timeout (in seconds). @since 0.1.0 */
static constexpr unsigned long DEFAULT_WRITE_TIMEOUT = 0; /*!< Default write timeout (in milliseconds). @ since 0.1.0 */
/**
* Function to handle the creation of a shell.
*
* @param[in] stream Stream for the telnet connection.
* @param[in] addr Remote IP address.
* @param[in] port Remote port.
* @since 0.1.0
*/
using shell_factory_function = std::function<std::shared_ptr<uuid::console::Shell>(Stream &stream, const IPAddress &addr, uint16_t port)>;
/**
* Create a new telnet service listening on the default port.
*
* @param[in] commands Commands available for execution in shells.
* @param[in] context Default context for shells.
* @param[in] flags Initial flags for shells.
* @since 0.1.0
*/
TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context = 0, unsigned int flags = 0);
/**
* Create a new telnet service listening on a specific port.
*
* @param[in] port TCP listening port.
* @param[in] commands Commands available for execution in shells.
* @param[in] context Default context for shells.
* @param[in] flags Initial flags for shells.
* @since 0.1.0
*/
TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context = 0, unsigned int flags = 0);
/**
* Create a new telnet service listening on the default port.
*
* @param[in] shell_factory Function to create a shell for new connections.
* @since 0.1.0
*/
explicit TelnetService(shell_factory_function shell_factory);
/**
* Create a new telnet service listening on a specific port.
*
* @param[in] port TCP listening port.
* @param[in] shell_factory Function to create a shell for new connections.
* @since 0.1.0
*/
TelnetService(uint16_t port, shell_factory_function shell_factory);
~TelnetService() = default;
/**
* Start listening for connections on the configured port.
*
* @since 0.1.0
*/
void start();
/**
* Close all connections.
*
* The listening status is not affected.
*
* @since 0.1.0
*/
void close_all();
/**
* Stop listening for connections.
*
* Existing connections are not affected.
*
* @since 0.1.0
*/
void stop();
/**
* Get the maximum number of concurrent open connections.
*
* @return The maximum number of concurrent open connections.
* @since 0.1.0
*/
size_t maximum_connections() const;
/**
* Set the maximum number of concurrent open connections.
*
* Defaults to TelnetService::MAX_CONNECTIONS.
*
* @since 0.1.0
*/
void maximum_connections(size_t count);
/**
* Get the initial idle timeout for new connections.
*
* @return The initial idle timeout in seconds (or 0 for disabled).
* @since 0.1.0
*/
unsigned long initial_idle_timeout() const;
/**
* Set the initial idle timeout for new connections.
*
* Defaults to TelnetService::DEFAULT_IDLE_TIMEOUT.
*
* @param[in] timeout Idle timeout in seconds (or 0 to disable).
* @since 0.1.0
*/
void initial_idle_timeout(unsigned long timeout);
/**
* Get the default socket write timeout for new connections.
*
* @return The default socket write timeout in seconds (or 0 for
* platform default).
* @since 0.1.0
*/
unsigned long default_write_timeout() const;
/**
* Set the default socket write timeout for new connections.
*
* Defaults to TelnetService::DEFAULT_WRITE_TIMEOUT (platform
* default).
*
* @param[in] timeout Socket write timeout in seconds (or 0 for
* platform default).
* @since 0.1.0
*/
void default_write_timeout(unsigned long timeout);
/**
* Accept new connections.
*
* @since 0.1.0
*/
void loop();
private:
/**
* Telnet connection.
*
* Holds the client and stream instance for the lifetime of the shell.
*
* @since 0.1.0
*/
class Connection {
public:
/**
* Create a telnet connection shell.
*
* @param[in] shell_factory Function to create a shell for new connections.
* @param[in] client Client connection.
* @param[in] idle_timeout Idle timeout in seconds.
* @param[in] write_timeout Idle timeout in milliseconds.
* @since 0.1.0
*/
Connection(shell_factory_function &shell_factory, WiFiClient &&client, unsigned long idle_timeout, unsigned long write_timeout);
~Connection() = default;
/**
* Check if the shell is still active.
*
* @return Active status of the shell.
* @since 0.1.0
*/
bool active();
/**
* Stop the shell if the client is not connected.
*
* @return Active status of the shell.
* @since 0.1.0
*/
bool loop();
/**
* Stop the shell.
*
* @since 0.1.0
*/
void stop();
private:
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
WiFiClient client_; /*!< Client connection. @since 0.1.0 */
TelnetStream stream_; /*!< Telnet stream for the connection. @since 0.1.0 */
std::shared_ptr<uuid::console::Shell> shell_; /*!< Shell for connection. @since 0.1.0 */
IPAddress addr_; /*!< Remote address of connection. @since 0.1.0 */
uint16_t port_; /*!< Remote port of connection. @since 0.1.0 */
};
TelnetService(const TelnetService&) = delete;
TelnetService& operator=(const TelnetService&) = delete;
static uuid::log::Logger logger_; /*!< uuid::log::Logger instance for telnet services. @since 0.1.0 */
WiFiServer server_; /*!< TCP server. @since 0.1.0 */
size_t maximum_connections_ = MAX_CONNECTIONS; /*!< Maximum number of concurrent open connections. @since 0.1.0 */
std::list<Connection> connections_; /*!< Open connections. @since 0.1.0 */
shell_factory_function shell_factory_; /*!< Function to create a shell. @since 0.1.0 */
unsigned long initial_idle_timeout_ = DEFAULT_IDLE_TIMEOUT; /*!< Initial idle timeout (in seconds). @since 0.1.0 */
unsigned long write_timeout_ = DEFAULT_WRITE_TIMEOUT; /*!< Write timeout (in milliseconds). @since 0.1.0 */
};
} // namespace telnet
} // namespace uuid
#endif