101 lines
3.4 KiB
C++
101 lines
3.4 KiB
C++
#pragma once
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
namespace mesh {
|
|
|
|
// The USB and TCP input buffers hold MAX_TRANS_UNIT * 2 + 32 bytes. Contact
|
|
// cards need almost twice a radio packet's size after hexadecimal encoding.
|
|
static const size_t kTerminalCommandCapacity = 255 * 2 + 32;
|
|
static const size_t kTerminalInitialOutputCapacity = 4096;
|
|
static const size_t kTerminalOutputCapacity = 32768;
|
|
|
|
// A bounded scrollback with absolute cursors. Reads do not consume data, so a
|
|
// lost HTTP response can be fetched again without skipping or repeating text.
|
|
// The caller serializes access and supplies storage only for a live session.
|
|
class TerminalOutputBuffer {
|
|
char* _data;
|
|
size_t _capacity;
|
|
uint64_t _end = 0;
|
|
uint64_t _first = 0;
|
|
|
|
public:
|
|
TerminalOutputBuffer(char* data, size_t capacity)
|
|
: _data(data), _capacity(capacity) {}
|
|
uint64_t end() const { return _end; }
|
|
void clear() { _end = _first = 0; }
|
|
void rebind(char* data, size_t capacity) {
|
|
const uint64_t first = _end > capacity && _end - capacity > _first
|
|
? _end - capacity : _first;
|
|
for (uint64_t i = first; i < _end; ++i) data[i % capacity] = _data[i % _capacity];
|
|
_data = data;
|
|
_capacity = capacity;
|
|
_first = first;
|
|
}
|
|
void append(const uint8_t* data, size_t len) {
|
|
for (size_t i = 0; i < len; ++i) {
|
|
if (_capacity) _data[_end % _capacity] = data[i];
|
|
++_end;
|
|
}
|
|
if (_end > _capacity && _end - _capacity > _first) _first = _end - _capacity;
|
|
}
|
|
size_t read(uint64_t& cursor, char* out, size_t size, bool& lost) const {
|
|
const uint64_t first = _first;
|
|
lost = cursor < first;
|
|
if (cursor < first) cursor = first;
|
|
if (cursor > _end) cursor = _end;
|
|
size_t n = 0;
|
|
while (n + 1 < size && cursor < _end) {
|
|
out[n++] = _data[cursor++ % _capacity];
|
|
}
|
|
// JSON strings must contain complete UTF-8 characters even when a page
|
|
// boundary lands inside a multibyte message or contact name.
|
|
if (n) {
|
|
size_t lead = n - 1;
|
|
while (lead > 0 && (static_cast<uint8_t>(out[lead]) & 0xC0) == 0x80) --lead;
|
|
const uint8_t byte = static_cast<uint8_t>(out[lead]);
|
|
const size_t width = byte >= 0xF0 ? 4 : byte >= 0xE0 ? 3 : byte >= 0xC0 ? 2 : 1;
|
|
if (n - lead < width) { cursor -= n - lead; n = lead; }
|
|
}
|
|
if (size) out[n] = 0;
|
|
return n;
|
|
}
|
|
};
|
|
|
|
// Exactly one command can be pending. A monotonically increasing sequence
|
|
// makes retries idempotent even after later commands have completed.
|
|
class TerminalCommandQueue {
|
|
public:
|
|
enum Result { Accepted, Replay, Busy, OutOfOrder, Invalid };
|
|
char command[kTerminalCommandCapacity] = {0};
|
|
uint32_t sequence = 0;
|
|
bool pending = false;
|
|
|
|
static bool valid(const char* text, size_t length) {
|
|
return text && length && length < kTerminalCommandCapacity
|
|
&& strlen(text) == length && !memchr(text, '\r', length)
|
|
&& !memchr(text, '\n', length);
|
|
}
|
|
|
|
Result submit(uint32_t seq, const char* text, size_t length) {
|
|
if (!valid(text, length)) return Invalid;
|
|
if (seq == 0) return OutOfOrder;
|
|
if (seq <= sequence) return Replay;
|
|
if (pending) return Busy;
|
|
if (seq != sequence + 1 || sequence == UINT32_MAX) return OutOfOrder;
|
|
memcpy(command, text, length);
|
|
command[length] = 0;
|
|
sequence = seq;
|
|
pending = true;
|
|
return Accepted;
|
|
}
|
|
void finish() {
|
|
memset(command, 0, sizeof(command));
|
|
pending = false;
|
|
}
|
|
};
|
|
|
|
} // namespace mesh
|