mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
352 lines
8.4 KiB
C++
352 lines
8.4 KiB
C++
/*
|
|
* 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
|