mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-26 12:39:52 +00:00
206 lines
7.2 KiB
C++
206 lines
7.2 KiB
C++
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include "NomadNetMemory.h"
|
|
|
|
namespace UI::LXMF::NomadNet {
|
|
|
|
// Single-slot callback-to-main-loop handoff. Reticulum callbacks only publish
|
|
// bytes and status here; the main loop is the sole consumer and LVGL owner.
|
|
class AsyncMailbox {
|
|
public:
|
|
static constexpr std::size_t MAX_BYTES = 64 * 1024;
|
|
static constexpr std::size_t MAX_WIRE_BYTES = MAX_BYTES + 64;
|
|
enum class Kind { NONE, LINK_ESTABLISHED, LINK_CLOSED, PROGRESS, RESPONSE, FAILED, OVERSIZED };
|
|
struct Event {
|
|
Kind kind = Kind::NONE;
|
|
ExternalVector<uint8_t> data;
|
|
std::size_t transfer_size = 0;
|
|
std::uint32_t generation = 0;
|
|
};
|
|
|
|
void begin(const std::vector<uint8_t>& link_token, std::uint32_t generation = 0) {
|
|
Guard guard(_lock);
|
|
_sealed = false;
|
|
if (generation != 0) _generation = generation;
|
|
if (_link_token == link_token &&
|
|
(_event.kind == Kind::LINK_ESTABLISHED || _event.kind == Kind::LINK_CLOSED)) return;
|
|
_link_token = link_token;
|
|
_request_token.clear();
|
|
_event = Event{};
|
|
}
|
|
|
|
void expect_request(const std::vector<uint8_t>& request_token) {
|
|
Guard guard(_lock);
|
|
_sealed = false;
|
|
if (_request_token == request_token &&
|
|
(_event.kind == Kind::PROGRESS || _event.kind == Kind::RESPONSE || _event.kind == Kind::FAILED ||
|
|
_event.kind == Kind::OVERSIZED)) return;
|
|
_request_token = request_token;
|
|
_event = Event{};
|
|
}
|
|
|
|
bool publish_link(const std::vector<uint8_t>& token, bool established) {
|
|
Guard guard(_lock);
|
|
if (_sealed) return false;
|
|
if (token.empty()) return false;
|
|
if (_link_token.empty()) _link_token = token;
|
|
if (token != _link_token) return false;
|
|
// A remote may close immediately after delivering a response. Preserve
|
|
// terminal request events until the main loop consumes them instead of
|
|
// replacing the page/error with a generic LINK_CLOSED event.
|
|
if (!established && (_event.kind == Kind::RESPONSE ||
|
|
_event.kind == Kind::FAILED ||
|
|
_event.kind == Kind::OVERSIZED)) return false;
|
|
_event.kind = established ? Kind::LINK_ESTABLISHED : Kind::LINK_CLOSED;
|
|
_event.generation = _generation;
|
|
_event.data.clear();
|
|
_event.transfer_size = 0;
|
|
return true;
|
|
}
|
|
|
|
bool publish_response(const std::vector<uint8_t>& token, const uint8_t* data,
|
|
std::size_t size, std::size_t transfer_size) {
|
|
Guard guard(_lock);
|
|
if (_sealed) return false;
|
|
if (token.empty()) return false;
|
|
if (_request_token.empty()) _request_token = token;
|
|
if (token != _request_token) return false;
|
|
if (size > _max_wire_bytes || (!data && size != 0)) {
|
|
set_oversized(size);
|
|
return true;
|
|
}
|
|
_event.data.clear();
|
|
try {
|
|
if (size != 0) _event.data.assign(data, data + size);
|
|
} catch (const std::bad_alloc&) {
|
|
// Callback context must never unwind through Reticulum. Discard any
|
|
// partial payload and publish a bounded terminal failure for the
|
|
// accepted request token instead.
|
|
_event.data.clear();
|
|
_event.kind = Kind::FAILED;
|
|
_event.generation = _generation;
|
|
_event.transfer_size = 0;
|
|
return true;
|
|
}
|
|
_event.kind = Kind::RESPONSE;
|
|
_event.generation = _generation;
|
|
_event.transfer_size = transfer_size;
|
|
return true;
|
|
}
|
|
|
|
bool publish_failed(const std::vector<uint8_t>& token,
|
|
std::size_t response_size = 0) {
|
|
Guard guard(_lock);
|
|
if (_sealed) return false;
|
|
if (token.empty()) return false;
|
|
if (_request_token.empty()) _request_token = token;
|
|
if (token != _request_token) return false;
|
|
if (response_size > _max_wire_bytes) {
|
|
set_oversized(response_size);
|
|
return true;
|
|
}
|
|
if (_event.kind == Kind::OVERSIZED || _event.kind == Kind::RESPONSE) return false;
|
|
_event.kind = Kind::FAILED;
|
|
_event.generation = _generation;
|
|
_event.data.clear();
|
|
_event.transfer_size = 0;
|
|
return true;
|
|
}
|
|
|
|
bool publish_progress(const std::vector<uint8_t>& token, std::size_t transfer_size) {
|
|
Guard guard(_lock);
|
|
if (_sealed) return false;
|
|
if (token.empty()) return false;
|
|
if (_request_token.empty()) _request_token = token;
|
|
if (token != _request_token) return false;
|
|
if (_event.kind == Kind::RESPONSE || _event.kind == Kind::FAILED ||
|
|
_event.kind == Kind::OVERSIZED) return false;
|
|
_event.kind = Kind::PROGRESS;
|
|
_event.generation = _generation;
|
|
_event.data.clear();
|
|
_event.transfer_size = transfer_size;
|
|
return true;
|
|
}
|
|
|
|
bool publish_oversized(const std::vector<uint8_t>& token, std::size_t response_size) {
|
|
Guard guard(_lock);
|
|
if (_sealed || token.empty()) return false;
|
|
if (_request_token.empty()) _request_token = token;
|
|
if (token != _request_token) return false;
|
|
if (_event.kind == Kind::RESPONSE) return false;
|
|
set_oversized(response_size);
|
|
return true;
|
|
}
|
|
|
|
bool take(Event& event) {
|
|
Guard guard(_lock);
|
|
if (_event.kind == Kind::NONE) return false;
|
|
event = std::move(_event);
|
|
_event = Event{};
|
|
return true;
|
|
}
|
|
|
|
void clear() {
|
|
Guard guard(_lock);
|
|
reset(false);
|
|
}
|
|
|
|
// Open an explicit pre-arm window before constructing a Link. Some
|
|
// implementations can call back before begin() receives its token.
|
|
void prepare(std::uint32_t generation = 0,
|
|
std::size_t max_wire_bytes = MAX_WIRE_BYTES) {
|
|
Guard guard(_lock);
|
|
reset(false);
|
|
_generation = generation;
|
|
_max_wire_bytes = max_wire_bytes > MAX_WIRE_BYTES
|
|
? MAX_WIRE_BYTES : max_wire_bytes;
|
|
}
|
|
|
|
// Terminal cleanup can synchronously invoke RequestReceipt's failed
|
|
// callback. Reject all callbacks until the next operation calls prepare().
|
|
void seal() {
|
|
Guard guard(_lock);
|
|
reset(true);
|
|
}
|
|
|
|
private:
|
|
void reset(bool sealed) {
|
|
_link_token.clear();
|
|
_request_token.clear();
|
|
_event = Event{};
|
|
_sealed = sealed;
|
|
}
|
|
|
|
class Guard {
|
|
public:
|
|
explicit Guard(std::atomic_flag& lock) : _lock(lock) {
|
|
while (_lock.test_and_set(std::memory_order_acquire)) {}
|
|
}
|
|
~Guard() { _lock.clear(std::memory_order_release); }
|
|
private:
|
|
std::atomic_flag& _lock;
|
|
};
|
|
|
|
void set_oversized(std::size_t transfer_size) {
|
|
_event.kind = Kind::OVERSIZED;
|
|
_event.generation = _generation;
|
|
_event.data.clear();
|
|
_event.transfer_size = transfer_size;
|
|
}
|
|
|
|
std::atomic_flag _lock = ATOMIC_FLAG_INIT;
|
|
bool _sealed = false;
|
|
std::vector<uint8_t> _link_token;
|
|
std::vector<uint8_t> _request_token;
|
|
std::uint32_t _generation = 0;
|
|
std::size_t _max_wire_bytes = MAX_WIRE_BYTES;
|
|
Event _event;
|
|
};
|
|
|
|
} // namespace UI::LXMF::NomadNet
|