My Project
stream.cpp
1 /*
2  * uuid-telnet - Telnet service
3  * Copyright 2019 Simon Arlott
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "uuid/telnet.h"
20 
21 #include <Arduino.h>
22 
23 #include <algorithm>
24 #include <string>
25 #include <vector>
26 
27 namespace uuid {
28 
29 namespace telnet {
30 
31 TelnetStream::TelnetStream(WiFiClient &client)
32  : client_(client) {
33  output_buffer_.reserve(BUFFER_SIZE);
34 }
35 
37  raw_write({
38  IAC, WILL, OPT_ECHO,
40  IAC, WILL, OPT_SGA,
41  IAC, DONT, OPT_ECHO,
42  IAC, DO, OPT_BINARY,
43  IAC, DO, OPT_SGA
44  });
45 }
46 
48  if (peek() == -1) {
49  return 0;
50  } else {
51  return 1;
52  }
53 }
54 
56  if (peek_ != -1) {
57  int data = peek_;
58  peek_ = -1;
59  return data;
60  }
61 
62  buffer_flush();
63 
64 restart:
65  int data = raw_read();
66 
67  if (data == -1) {
68  return -1;
69  }
70 
71  unsigned char c = data;
72 
73  if (sub_negotiation_) {
74  if (previous_raw_in_ == IAC) {
75  switch (c) {
76  case SE:
77  sub_negotiation_ = false;
78  previous_raw_in_ = 0;
79  goto restart;
80 
81  case IAC:
82  previous_raw_in_ = 0;
83  goto restart;
84  }
85  } else {
86  switch (c) {
87  case IAC:
88  previous_raw_in_ = c;
89  goto restart;
90 
91  default:
92  previous_raw_in_ = 0;
93  goto restart;
94  }
95  }
96  } else {
97  if (previous_raw_in_ == IAC) {
98  switch (c) {
99  case IP:
100  // Interrupt (^C)
101  previous_raw_in_ = 0;
102  c = '\x03';
103  break;
104 
105  case EC:
106  // Backspace (^H)
107  previous_raw_in_ = 0;
108  c = '\x08';
109  break;
110 
111  case EL:
112  // Delete line (^U)
113  previous_raw_in_ = 0;
114  c = '\x15';
115  break;
116 
117  case IAC:
118  previous_raw_in_ = 0;
119  break;
120 
121  case SB:
122  case WILL:
123  case WONT:
124  case DO:
125  case DONT:
126  previous_raw_in_ = c;
127  goto restart;
128 
129  case SE:
130  case DM:
131  case BRK:
132  case AO:
133  case AYT:
134  case GA:
135  case NOP:
136  default:
137  previous_raw_in_ = 0;
138  goto restart;
139  }
140  } else if (previous_raw_in_ == SB) {
141  sub_negotiation_ = true;
142  previous_raw_in_ = 0;
143  goto restart;
144  } else if (previous_raw_in_ == WILL || previous_raw_in_ == WONT) {
145  switch (c) {
146  case OPT_ECHO:
147  // Don't do these
148  raw_write({IAC, DONT, c});
149  break;
150 
151  case OPT_BINARY:
152  case OPT_SGA:
153  // Do these
154  raw_write({IAC, DO, c});
155  break;
156 
157  default:
158  // Don't do anything else
159  raw_write({IAC, DONT, c});
160  break;
161  }
162 
163  previous_raw_in_ = 0;
164  goto restart;
165  } else if (previous_raw_in_ == DO) {
166  switch (c) {
167  case OPT_ECHO:
168  case OPT_BINARY:
169  case OPT_SGA:
170  // These are always enabled
171  break;
172 
173  default:
174  // Refuse to do anything else
175  raw_write({IAC, WONT, c});
176  break;
177  }
178 
179  previous_raw_in_ = 0;
180  goto restart;
181  } else if (previous_raw_in_ == DONT) {
182  switch (c) {
183  case OPT_ECHO:
184  case OPT_BINARY:
185  case OPT_SGA:
186  // Insist that we do these
187  raw_write({IAC, WILL, c});
188  break;
189 
190  default:
191  // Everything else is always disabled
192  break;
193  }
194 
195  previous_raw_in_ = 0;
196  goto restart;
197  } else {
198  switch (c) {
199  case IAC:
200  previous_raw_in_ = c;
201  goto restart;
202 
203  default:
204  previous_raw_in_ = 0;
205  break;
206  }
207  }
208  }
209 
210  if (previous_in_ == CR) {
211  if (c == NUL) {
212  previous_in_ = 0;
213  goto restart;
214  }
215  }
216 
217  previous_in_ = c;
218  return c;
219 }
220 
222  buffer_flush();
223 
224  // It's too complicated to implement this by calling peek()
225  // on the original stream, especially if the original stream
226  // doesn't actually support peeking.
227  if (peek_ == -1) {
228  peek_ = read();
229  }
230 
231  return peek_;
232 }
233 
234 size_t TelnetStream::write(uint8_t data) {
235  if (previous_out_ == CR && data != LF) {
236  previous_out_ = data;
237 
238  if (raw_write({NUL, data}) != 2) {
239  return 0;
240  }
241  } else {
242  previous_out_ = data;
243  }
244 
245  if (data == IAC) {
246  if (raw_write({IAC, IAC}) != 2) {
247  return 0;
248  }
249  } else {
250  if (raw_write(data) != 1) {
251  return 0;
252  }
253  }
254 
255  return 1;
256 }
257 
258 size_t TelnetStream::write(const uint8_t *buffer, size_t size) {
259  std::vector<unsigned char> data;
260  data.reserve(size);
261 
262  while (size-- > 0) {
263  unsigned char c = *buffer++;
264 
265  if (previous_out_ == CR && c != LF) {
266  data.push_back((unsigned char)NUL);
267  }
268 
269  if (c == IAC) {
270  data.push_back((unsigned char)IAC);
271  }
272 
273  previous_out_ = c;
274  data.push_back(c);
275  }
276 
277  size_t len = raw_write(data);
278  if (len < size) {
279  len = 0;
280  }
281  return len;
282 }
283 
285  // This is a pure virtual function in Arduino's Stream class, which
286  // makes no sense because that class is for input and this is an
287  // output function. Later versions move it to Print as an empty
288  // virtual function so this is here for backward compatibility.
289 }
290 
292  return client_.available();
293 }
294 
296  return client_.read();
297 }
298 
300  if (!output_buffer_.empty()) {
301  size_t len = client_.write(reinterpret_cast<const unsigned char*>(output_buffer_.data()), output_buffer_.size());
302  if (len != output_buffer_.size()) {
303  client_.stop();
304  }
305  output_buffer_.clear();
306  output_buffer_.shrink_to_fit();
307  }
308 }
309 
310 size_t TelnetStream::raw_write(unsigned char data) {
311  output_buffer_.push_back(data);
312 
313  if (output_buffer_.size() >= BUFFER_SIZE) {
314  buffer_flush();
315  }
316 
317  return 1;
318 }
319 
320 size_t TelnetStream::raw_write(const std::vector<unsigned char> &data) {
321  return raw_write(reinterpret_cast<const unsigned char*>(data.data()), data.size());
322 }
323 
324 size_t TelnetStream::raw_write(const uint8_t *buffer, size_t size) {
325  size_t offset = 0;
326  size_t remaining = size;
327 
328  if (!output_buffer_.empty()) {
329  // Fill the rest of the buffer
330  size_t block = std::min(remaining, BUFFER_SIZE - output_buffer_.size());
331 
332  output_buffer_.insert(output_buffer_.end(), buffer, buffer + block);
333  offset += block;
334  remaining -= block;
335 
336  if (output_buffer_.size() >= BUFFER_SIZE) {
337  buffer_flush();
338  }
339  }
340 
341  if (remaining >= BUFFER_SIZE) {
342  // Output directly if it won't fit in the buffer
343  size_t len = client_.write(buffer + offset, remaining);
344  if (len != remaining) {
345  client_.stop();
346  return offset + len;
347  }
348  } else if (remaining > 0) {
349  // Put the rest in the buffer
350  output_buffer_.insert(output_buffer_.end(), buffer + offset, buffer + offset + remaining);
351  }
352 
353  return size;
354 }
355 
356 } // namespace telnet
357 
358 } // namespace uuid
uuid::telnet::TelnetStream::IAC
static constexpr const unsigned char IAC
Definition: telnet.h:151
uuid::telnet::TelnetStream::client_
WiFiClient & client_
Definition: telnet.h:216
uuid::telnet::TelnetStream::raw_read
int raw_read()
Read one byte directly from the available input.
Definition: stream.cpp:295
uuid::telnet::TelnetStream::IP
static constexpr const unsigned char IP
Definition: telnet.h:140
uuid::telnet::TelnetStream::OPT_SGA
static constexpr const unsigned char OPT_SGA
Definition: telnet.h:155
uuid::telnet::TelnetStream::previous_raw_in_
unsigned char previous_raw_in_
Definition: telnet.h:217
uuid::telnet::TelnetStream::raw_write
size_t raw_write(unsigned char data)
Write one byte directly to the output stream.
Definition: stream.cpp:310
uuid::telnet::TelnetStream::NOP
static constexpr const unsigned char NOP
Definition: telnet.h:137
uuid::telnet::TelnetStream::sub_negotiation_
bool sub_negotiation_
Definition: telnet.h:218
uuid::telnet::TelnetStream::LF
static constexpr const unsigned char LF
Definition: telnet.h:132
uuid::telnet::TelnetStream::WILL
static constexpr const unsigned char WILL
Definition: telnet.h:147
uuid::telnet::TelnetStream::AYT
static constexpr const unsigned char AYT
Definition: telnet.h:142
uuid::telnet::TelnetStream::write
size_t write(uint8_t data) override
Write one byte to the output stream.
Definition: stream.cpp:234
uuid::telnet::TelnetStream::DM
static constexpr const unsigned char DM
Definition: telnet.h:138
uuid::telnet::TelnetStream::CR
static constexpr const unsigned char CR
Definition: telnet.h:135
uuid::telnet::TelnetStream::SB
static constexpr const unsigned char SB
Definition: telnet.h:146
uuid::telnet::TelnetStream::previous_in_
unsigned char previous_in_
Definition: telnet.h:219
uuid::telnet::TelnetStream::DONT
static constexpr const unsigned char DONT
Definition: telnet.h:150
uuid::telnet::TelnetStream::available
int available() override
Check for available input.
Definition: stream.cpp:47
uuid::telnet::TelnetStream::GA
static constexpr const unsigned char GA
Definition: telnet.h:145
uuid::telnet::TelnetStream::read
int read() override
Read one byte from the available input.
Definition: stream.cpp:55
uuid::telnet::TelnetStream::buffer_flush
void buffer_flush()
Flush output stream buffer.
Definition: stream.cpp:299
uuid::telnet::TelnetStream::TelnetStream
TelnetStream(WiFiClient &client)
Create a new telnet stream wrapper.
Definition: stream.cpp:31
uuid::telnet::TelnetStream::BRK
static constexpr const unsigned char BRK
Definition: telnet.h:139
uuid::telnet::TelnetStream::peek_
int peek_
Definition: telnet.h:221
uuid::telnet::TelnetStream::WONT
static constexpr const unsigned char WONT
Definition: telnet.h:148
uuid::telnet::TelnetStream::NUL
static constexpr const unsigned char NUL
Definition: telnet.h:128
uuid
Common utilities.
Definition: get_uptime_ms.cpp:28
uuid::telnet::TelnetStream::EC
static constexpr const unsigned char EC
Definition: telnet.h:143
uuid::telnet::TelnetStream::output_buffer_
std::vector< char > output_buffer_
Definition: telnet.h:222
uuid::telnet::TelnetStream::OPT_ECHO
static constexpr const unsigned char OPT_ECHO
Definition: telnet.h:154
uuid::telnet::TelnetStream::OPT_BINARY
static constexpr const unsigned char OPT_BINARY
Definition: telnet.h:153
uuid::telnet::TelnetStream::flush
void flush() override
Does nothing.
Definition: stream.cpp:284
uuid::telnet::TelnetStream::raw_available
int raw_available()
Directly check for available input.
Definition: stream.cpp:291
uuid::telnet::TelnetStream::peek
int peek() override
Read one byte from the available input without advancing to the next one.
Definition: stream.cpp:221
uuid::telnet::TelnetStream::BUFFER_SIZE
static constexpr const size_t BUFFER_SIZE
Definition: telnet.h:157
uuid::telnet::TelnetStream::previous_out_
unsigned char previous_out_
Definition: telnet.h:220
uuid::telnet::TelnetStream::DO
static constexpr const unsigned char DO
Definition: telnet.h:149
uuid::telnet::TelnetStream::AO
static constexpr const unsigned char AO
Definition: telnet.h:141
uuid::telnet::TelnetStream::SE
static constexpr const unsigned char SE
Definition: telnet.h:136
uuid::telnet::TelnetStream::EL
static constexpr const unsigned char EL
Definition: telnet.h:144
uuid::telnet::TelnetStream::start
void start()
Perform initial negotiation.
Definition: stream.cpp:36