Files
pyxis/lib/microreticulum-shim/Buffer.cpp
T
torlando-tech 02ceeda75b Track A.8: re-vendor shim from ca355e5; UniversalFileSystem→microStore;
relocate shim to lib/; pyxis lib API renames

Compile-tier graft progress on top of 40e561f. Key changes:

- Re-vendor lib/microreticulum-shim/ (was src-shim/) from the *actual*
  ca355e5 commit content rather than the stale "feat/t-deck HEAD"
  /tmp clone the previous commit pulled from. Recovers process_sync()
  on LXMRouter and MEMORY_MONITOR_POLL macro that were missing.
- Move src-shim/ → lib/microreticulum-shim/ + add library.json so
  PlatformIO discovers the .cpp files and links them. Was previously
  only on the include path; the .cpps weren't in the build.
  (This unblocks the 30+ undefined-reference linker errors for LXMF
  and Instrumentation symbols.)
- Drop -Isrc-shim/Utilities (/Cryptography/Instrumentation) from
  build_flags — they were over-broad and put our Stream.h on the
  GLOBAL header path, breaking Arduino's Wire.cpp which has
  `class TwoWire: public Stream`. -Ilib/microreticulum-shim alone
  resolves subdir lookups via <Cryptography/X.h>, <Utilities/Y.h>.

- UniversalFileSystem migrated to microStore::Adapters::SPIFFSFileSystem
  (activated by -DUSTORE_USE_SPIFFS). Vanilla upstream microReticulum
  @ 0.3.0 deleted RNS::FileSystem in favor of microStore. Pyxis's
  lib/universal_filesystem/ is now dead code on this build path.

- pyxis lib API renames for the post-graft world:
    SDLogger.cpp:        RNS::setLogCallback -> RNS::set_log_callback
    AnnounceListScreen:  Transport::get_destination_table ->
                         Transport::get_path_table
                         Transport::DestinationEntry ->
                         RNS::Persistence::DestinationEntry
    UIManager.cpp:       _lxst_destination ctor explicit RNS::Type::NONE
                         (vanilla Destination has no default ctor)
                         Identity::mark_persistent calls disabled w/
                         restoration TODO
    ConversationListScreen: Interface::get_rssi/get_stats calls
                            disabled (the methods are non-virtual
                            on BLEInterface/SX1262Interface post
                            de-virtualization in a0ff631)

Compile is clean against the fixed-cryptography submodule pin; current
failure layer is fork-only Type::Channel constants referenced by the
vendored shim's Buffer/ChannelData files. That's the next session's
problem — see pyxis_microReticulum_graft_spike_findings.md for the
plan options (most likely: remove Channel/Buffer/ChannelData/Ratchet
from the shim, since LXMF doesn't use Channel anyway per the 2026-05-04
investigation).
2026-05-05 01:39:44 -04:00

405 lines
12 KiB
C++

#include "Buffer.h"
#include "ChannelData.h" // Required for Channel template instantiation
#include "Cryptography/BZ2.h"
#include <algorithm>
namespace RNS {
//==============================================================================
// RawChannelReaderData - Internal data for RawChannelReader
//==============================================================================
class RawChannelReaderData {
public:
RawChannelReaderData(uint16_t stream_id, Channel& channel)
: _stream_id(stream_id), _channel(&channel), _eof(false), _closed(false) {
MEM("RawChannelReaderData object created");
}
~RawChannelReaderData() {
if (!_closed) {
close();
}
MEM("RawChannelReaderData object destroyed");
}
bool handle_message(MessageBase& msg) {
if (_closed) return false;
// Only handle StreamDataMessage
if (msg.msgtype() != StreamDataMessage::MSGTYPE) {
return false;
}
StreamDataMessage& stream_msg = static_cast<StreamDataMessage&>(msg);
// Filter by stream_id
if (stream_msg.stream_id != _stream_id) {
return false;
}
DEBUGF("RawChannelReader: Received %zu bytes on stream %u (eof=%d)",
stream_msg.data.size(), _stream_id, stream_msg.eof);
// Accumulate data
if (stream_msg.data) {
_buffer += stream_msg.data;
}
// Set EOF flag
if (stream_msg.eof) {
_eof = true;
DEBUG("RawChannelReader: EOF received");
}
// Notify callbacks
notify_ready();
return true; // Message consumed
}
void notify_ready() {
size_t avail = _buffer.size();
if (avail == 0 && !_eof) return;
// Make a copy of callbacks in case they modify the list
auto callbacks_copy = _ready_callbacks;
for (auto& callback : callbacks_copy) {
try {
callback(avail);
} catch (...) {
ERROR("RawChannelReader: Callback threw exception");
}
}
}
void close() {
_closed = true;
_ready_callbacks.clear();
DEBUG("RawChannelReader: Closed");
}
uint16_t _stream_id;
Channel* _channel;
Bytes _buffer;
bool _eof;
bool _closed;
std::vector<RawChannelReader::ReadyCallback> _ready_callbacks;
};
//==============================================================================
// StreamDataMessage
//==============================================================================
StreamDataMessage::StreamDataMessage(uint16_t stream_id, const Bytes& data,
bool eof, bool compressed)
: stream_id(stream_id), data(data), eof(eof), compressed(compressed) {
}
Bytes StreamDataMessage::pack() const {
// Build 2-byte header: EOF(bit15) | COMP(bit14) | STREAM_ID(bits13-0)
uint16_t header = (stream_id & Type::Buffer::STREAM_ID_MASK);
if (eof) header |= Type::Buffer::FLAG_EOF;
if (compressed) header |= Type::Buffer::FLAG_COMPRESSED;
Bytes result;
result.reserve(Type::Buffer::STREAM_OVERHEAD + data.size());
// Big-endian header (matching Python struct.pack(">H", header))
result.append(static_cast<uint8_t>((header >> 8) & 0xFF));
result.append(static_cast<uint8_t>(header & 0xFF));
// Data payload (already compressed if compressed flag is set)
if (data) {
result += data;
}
return result;
}
void StreamDataMessage::unpack(const Bytes& raw) {
if (raw.size() < Type::Buffer::STREAM_OVERHEAD) {
ERROR("StreamDataMessage::unpack: Data too short");
return;
}
// Read big-endian header
uint16_t header = (static_cast<uint16_t>(raw[0]) << 8) | raw[1];
eof = (header & Type::Buffer::FLAG_EOF) != 0;
compressed = (header & Type::Buffer::FLAG_COMPRESSED) != 0;
stream_id = header & Type::Buffer::STREAM_ID_MASK;
// Extract data payload
if (raw.size() > Type::Buffer::STREAM_OVERHEAD) {
data = raw.mid(Type::Buffer::STREAM_OVERHEAD);
// Decompress if needed
if (compressed && data.size() > 0) {
data = Cryptography::bz2_decompress(data);
}
} else {
data = Bytes::NONE;
}
}
//==============================================================================
// RawChannelReader - Uses shared_ptr to RawChannelReaderData
//==============================================================================
RawChannelReader::RawChannelReader(uint16_t stream_id, Channel& channel)
: _object(std::make_shared<RawChannelReaderData>(stream_id, channel)) {
// Register StreamDataMessage type (as system message)
_object->_channel->register_message_type<StreamDataMessage>(true);
// Add message handler - capture shared_ptr to keep data alive
// and use weak_ptr to avoid preventing destruction
std::weak_ptr<RawChannelReaderData> weak_obj = _object;
_object->_channel->add_message_handler([weak_obj](MessageBase& msg) -> bool {
if (auto obj = weak_obj.lock()) {
return obj->handle_message(msg);
}
return false; // Object destroyed, don't handle
});
DEBUGF("RawChannelReader: Created for stream_id=%u", stream_id);
}
RawChannelReader::~RawChannelReader() {
// shared_ptr handles cleanup automatically
MEM("RawChannelReader object destroyed");
}
Bytes RawChannelReader::read(size_t max_bytes) {
if (!_object || _object->_buffer.size() == 0) {
return Bytes::NONE;
}
size_t to_read = (max_bytes == 0) ? _object->_buffer.size()
: std::min(max_bytes, _object->_buffer.size());
Bytes result = _object->_buffer.left(to_read);
_object->_buffer = _object->_buffer.mid(to_read);
DEBUGF("RawChannelReader: Read %zu bytes, %zu remaining", to_read, _object->_buffer.size());
return result;
}
Bytes RawChannelReader::readline() {
if (!_object) return Bytes::NONE;
// Search for newline
for (size_t i = 0; i < _object->_buffer.size(); i++) {
if (_object->_buffer[i] == '\n') {
// Return including the newline
Bytes line = _object->_buffer.left(i + 1);
_object->_buffer = _object->_buffer.mid(i + 1);
return line;
}
}
// No complete line available
// If EOF, return remaining data as final line (no newline)
if (_object->_eof && _object->_buffer.size() > 0) {
Bytes line = _object->_buffer;
_object->_buffer = Bytes::NONE;
return line;
}
return Bytes::NONE;
}
size_t RawChannelReader::available() const {
if (!_object) return 0;
return _object->_buffer.size();
}
bool RawChannelReader::eof() const {
if (!_object) return true;
return _object->_eof && _object->_buffer.size() == 0;
}
void RawChannelReader::add_ready_callback(ReadyCallback callback) {
if (!_object) return;
_object->_ready_callbacks.push_back(callback);
}
void RawChannelReader::remove_ready_callback(ReadyCallback callback) {
// Note: Function comparison is tricky in C++
// This is a simplified implementation
// For production, consider using callback IDs
}
void RawChannelReader::close() {
if (!_object) return;
_object->close();
}
//==============================================================================
// RawChannelWriter
//==============================================================================
RawChannelWriter::RawChannelWriter(uint16_t stream_id, Channel& channel)
: _stream_id(stream_id), _channel(&channel), _eof_sent(false) {
// Calculate max data length (Channel MDU minus stream header)
_max_data_len = _channel->mdu() - Type::Buffer::STREAM_OVERHEAD;
// Register StreamDataMessage type (as system message)
// This may already be registered by a reader, but that's OK
_channel->register_message_type<StreamDataMessage>(true);
DEBUGF("RawChannelWriter: Created for stream_id=%u, max_data_len=%zu",
_stream_id, _max_data_len);
}
RawChannelWriter::~RawChannelWriter() {
if (!_eof_sent && _channel) {
close();
}
}
RawChannelWriter::RawChannelWriter(RawChannelWriter&& other) noexcept
: _stream_id(other._stream_id),
_channel(other._channel),
_max_data_len(other._max_data_len),
_eof_sent(other._eof_sent) {
other._channel = nullptr;
other._eof_sent = true;
}
RawChannelWriter& RawChannelWriter::operator=(RawChannelWriter&& other) noexcept {
if (this != &other) {
if (!_eof_sent && _channel) {
close();
}
_stream_id = other._stream_id;
_channel = other._channel;
_max_data_len = other._max_data_len;
_eof_sent = other._eof_sent;
other._channel = nullptr;
other._eof_sent = true;
}
return *this;
}
size_t RawChannelWriter::write(const Bytes& data) {
if (!_channel || _eof_sent) {
ERROR("RawChannelWriter: Cannot write after close");
return 0;
}
if (!data || data.size() == 0) {
return 0;
}
size_t chunk_len = std::min(data.size(), Type::Buffer::MAX_CHUNK_LEN);
Bytes chunk = data.left(chunk_len);
bool use_compression = false;
Bytes send_data;
size_t processed = 0;
// Try compression at decreasing chunk sizes (matching Python Buffer.py)
if (chunk_len > Type::Buffer::COMPRESSION_MIN_SIZE) {
for (size_t try_num = 1; try_num <= Type::Buffer::COMPRESSION_TRIES; try_num++) {
size_t segment_len = chunk_len / try_num;
if (segment_len == 0) break;
Bytes segment = chunk.left(segment_len);
Bytes compressed = Cryptography::bz2_compress(segment);
if (compressed.size() < _max_data_len &&
compressed.size() < segment_len) {
use_compression = true;
send_data = compressed;
processed = segment_len;
DEBUGF("RawChannelWriter: Compression succeeded: %zu -> %zu bytes",
segment_len, compressed.size());
break;
}
}
}
// If compression didn't help, send uncompressed
if (!use_compression) {
send_data = chunk.left(_max_data_len);
processed = send_data.size();
}
// Build and send message
StreamDataMessage msg;
msg.stream_id = _stream_id;
msg.data = send_data;
msg.eof = false;
msg.compressed = use_compression;
_channel->send(msg);
DEBUGF("RawChannelWriter: Sent %zu bytes on stream %u (compressed=%d)",
processed, _stream_id, use_compression);
return processed;
}
void RawChannelWriter::flush() {
// In the current implementation, write() sends immediately
// This is a no-op but provided for API compatibility
DEBUG("RawChannelWriter: Flush called (no-op)");
}
void RawChannelWriter::close() {
if (_eof_sent) return;
if (_channel) {
// Send EOF message
StreamDataMessage msg;
msg.stream_id = _stream_id;
msg.data = Bytes::NONE;
msg.eof = true;
msg.compressed = false;
_channel->send(msg);
DEBUGF("RawChannelWriter: Sent EOF on stream %u", _stream_id);
}
_eof_sent = true;
}
//==============================================================================
// Buffer namespace factory functions
//==============================================================================
namespace Buffer {
RawChannelReader create_reader(uint16_t stream_id, Channel& channel,
RawChannelReader::ReadyCallback callback) {
RawChannelReader reader(stream_id, channel);
if (callback) {
reader.add_ready_callback(callback);
}
return reader;
}
RawChannelWriter create_writer(uint16_t stream_id, Channel& channel) {
return RawChannelWriter(stream_id, channel);
}
std::pair<RawChannelReader, RawChannelWriter>
create_bidirectional_buffer(uint16_t rx_stream_id, uint16_t tx_stream_id,
Channel& channel,
RawChannelReader::ReadyCallback callback) {
RawChannelReader reader(rx_stream_id, channel);
if (callback) {
reader.add_ready_callback(callback);
}
RawChannelWriter writer(tx_stream_id, channel);
return std::make_pair(std::move(reader), std::move(writer));
}
} // namespace Buffer
} // namespace RNS