/* * 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 . */ #include "uuid/telnet.h" #include #include #include #include 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 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(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 & data) { return raw_write(reinterpret_cast(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