refactor: split the library into fumi-core and make it embeddable
This commit is contained in:
parent
d3cd0afb4f
commit
8fb5661b53
28 changed files with 975 additions and 724 deletions
288
core/shim/smolmail_rns.cpp
Normal file
288
core/shim/smolmail_rns.cpp
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* microReticulum bridge for the fumi RNS client carrier (upstream spec sec
|
||||
* 13). Owns the Reticulum instance, the UDP interface and the loop thread
|
||||
* that drives Reticulum::loop(). Every request/response exchange goes through
|
||||
* a single slot guarded by a condvar: the CLI has one request in flight at a
|
||||
* time, so no map of callbacks is needed.
|
||||
*
|
||||
* microReticulum splices the request payload into its msgpack envelope
|
||||
* verbatim (Link.cpp pack_request_envelope), so the request must be packed
|
||||
* as a msgpack binary by the caller; the response, by contrast, arrives
|
||||
* already decoded (unpack_response_envelope yields the payload itself), so
|
||||
* only the outbound direction touches msgpack here.
|
||||
*/
|
||||
#include "smolmail_rns.h"
|
||||
#include "udp_interface.h"
|
||||
|
||||
#include <microStore/FileSystem.h>
|
||||
#include <microStore/Adapters/UniversalFileSystem.h>
|
||||
#include <MsgPack.h>
|
||||
|
||||
#include <microReticulum.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
// Anchored in src/rns/transport.rs and upstream spec sec 13.1/13.5.
|
||||
static const char* APP_NAME = "smolmail";
|
||||
static const char* APP_ASPECT = "server";
|
||||
static const char* REQ_PATH = "smolmail/1";
|
||||
|
||||
static const double LOOP_SLEEP_SECS = 0.01;
|
||||
static const double CONNECT_POLL_SECS = 0.05;
|
||||
static const double PATH_POLL_SECS = 0.1;
|
||||
|
||||
static RNS::Reticulum reticulum({RNS::Type::NONE});
|
||||
static RNS::Interface udp_interface({RNS::Type::NONE});
|
||||
static RNS::Link active_link({RNS::Type::NONE});
|
||||
static microStore::FileSystem filesystem{microStore::Adapters::UniversalFileSystem()};
|
||||
|
||||
static volatile bool running = false;
|
||||
|
||||
// The single response slot (plan: one request in flight at a time).
|
||||
static std::mutex slot_mutex;
|
||||
static std::condition_variable slot_cv;
|
||||
static RNS::Bytes slot_response;
|
||||
static volatile bool slot_ready = false; // response or failure arrived
|
||||
static volatile bool slot_failed = false;
|
||||
|
||||
// Link establishment, signalled from the loop thread.
|
||||
static std::mutex link_mutex;
|
||||
static std::condition_variable link_cv;
|
||||
static volatile bool link_established = false;
|
||||
static volatile bool link_closed_early = false;
|
||||
|
||||
static void on_link_established(RNS::Link& established) {
|
||||
(void)established;
|
||||
std::lock_guard<std::mutex> lock(link_mutex);
|
||||
link_established = true;
|
||||
link_cv.notify_all();
|
||||
}
|
||||
|
||||
static void on_link_closed(RNS::Link& closed) {
|
||||
(void)closed;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(link_mutex);
|
||||
link_closed_early = true;
|
||||
link_cv.notify_all();
|
||||
}
|
||||
// A dead link fails any request waiting on the slot rather than letting
|
||||
// it run to the timeout (upstream spec sec 13.5: a local error).
|
||||
std::lock_guard<std::mutex> lock(slot_mutex);
|
||||
slot_failed = true;
|
||||
slot_ready = true;
|
||||
slot_cv.notify_all();
|
||||
}
|
||||
|
||||
static void on_response(const RNS::RequestReceipt& receipt) {
|
||||
RNS::Bytes response = receipt.get_response();
|
||||
std::lock_guard<std::mutex> lock(slot_mutex);
|
||||
slot_response = response;
|
||||
slot_failed = false;
|
||||
slot_ready = true;
|
||||
slot_cv.notify_all();
|
||||
}
|
||||
|
||||
static void on_failed(const RNS::RequestReceipt& receipt) {
|
||||
(void)receipt;
|
||||
std::lock_guard<std::mutex> lock(slot_mutex);
|
||||
slot_failed = true;
|
||||
slot_ready = true;
|
||||
slot_cv.notify_all();
|
||||
}
|
||||
|
||||
// The unwrapped large-response payload, filled in by smolmail_rns_request.
|
||||
static RNS::Bytes unwrapped;
|
||||
|
||||
static void loop_thread_main() {
|
||||
while (running) {
|
||||
reticulum.loop();
|
||||
RNS::Utilities::OS::sleep(LOOP_SLEEP_SECS);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int smolmail_rns_start(const char* storage_dir,
|
||||
const char* udp_listen_host, uint16_t udp_listen_port,
|
||||
const char* udp_forward_host, uint16_t udp_forward_port) {
|
||||
if (running) {
|
||||
return 0; // already started; the storage path cannot change mid-run
|
||||
}
|
||||
|
||||
// Registered before anything else, as the interop examples do, so
|
||||
// persistence writes have a filesystem to go through.
|
||||
filesystem.init();
|
||||
RNS::Utilities::OS::register_filesystem(filesystem);
|
||||
RNS::Reticulum::storagepath(storage_dir);
|
||||
|
||||
udp_interface = new UDPInterface("smolmail_rns_udp",
|
||||
udp_listen_host ? udp_listen_host : "",
|
||||
udp_listen_port,
|
||||
udp_forward_host ? udp_forward_host : "",
|
||||
udp_forward_port);
|
||||
udp_interface.mode(RNS::Type::Interface::MODE_GATEWAY);
|
||||
RNS::Transport::register_interface(udp_interface);
|
||||
if (!udp_interface.start()) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
reticulum = RNS::Reticulum();
|
||||
// Transport mode must be on for a client: the known-destinations store
|
||||
// (what Identity::recall reads, and what path discovery stores the
|
||||
// announced identity into) is only initialised inside it. The Python
|
||||
// client never trips this because its known-destinations map always
|
||||
// exists in memory; microReticulum with RNS_USE_FS keeps it in a
|
||||
// FileStore instead.
|
||||
reticulum.transport_enabled(true);
|
||||
reticulum.start();
|
||||
|
||||
running = true;
|
||||
std::thread(loop_thread_main).detach();
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int smolmail_rns_connect(const uint8_t* destination_hash, uint32_t timeout_ms,
|
||||
uint8_t* link_id_out) {
|
||||
const RNS::Bytes hash(destination_hash, 16);
|
||||
|
||||
// Request a path and WAIT before creating the link (upstream spec sec
|
||||
// 13.3): with no path RNS assumes the maximum hop count and the link
|
||||
// fails after minutes instead of promptly. The deadline is the stack's
|
||||
// own path request timeout, not a number copied from the document.
|
||||
if (!RNS::Transport::has_path(hash)) {
|
||||
RNS::Transport::request_path(hash);
|
||||
const double deadline = RNS::Utilities::OS::time()
|
||||
+ (double)RNS::Type::Transport::PATH_REQUEST_TIMEOUT;
|
||||
while (!RNS::Transport::has_path(hash)
|
||||
&& RNS::Utilities::OS::time() < deadline) {
|
||||
RNS::Utilities::OS::sleep(PATH_POLL_SECS);
|
||||
}
|
||||
if (!RNS::Transport::has_path(hash)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Recall can return nothing immediately after a path appears; the caller
|
||||
// treats that as a retry, not an error.
|
||||
RNS::Identity identity = RNS::Identity::recall(hash);
|
||||
if (!identity) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
// One fresh link per session, never reused across authentications:
|
||||
// link_id is what stops an AUTH replaying on another link (upstream
|
||||
// spec sec 13.6).
|
||||
RNS::Destination destination(identity, RNS::Type::Destination::OUT,
|
||||
RNS::Type::Destination::SINGLE, APP_NAME, APP_ASPECT);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(link_mutex);
|
||||
link_established = false;
|
||||
link_closed_early = false;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> slot_lock(slot_mutex);
|
||||
slot_ready = false;
|
||||
slot_failed = false;
|
||||
}
|
||||
active_link = RNS::Link(destination, on_link_established, on_link_closed);
|
||||
|
||||
const double deadline = RNS::Utilities::OS::time() + (double)timeout_ms / 1000.0;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(link_mutex);
|
||||
while (!link_established && !link_closed_early
|
||||
&& RNS::Utilities::OS::time() < deadline) {
|
||||
link_cv.wait_for(lock, std::chrono::duration<double>(CONNECT_POLL_SECS));
|
||||
}
|
||||
}
|
||||
if (!link_established || active_link.status() != RNS::Type::Link::ACTIVE) {
|
||||
active_link.teardown();
|
||||
active_link = RNS::Link({RNS::Type::NONE});
|
||||
return -3;
|
||||
}
|
||||
memcpy(link_id_out, active_link.link_id().data(), 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int smolmail_rns_request(const uint8_t* request, size_t request_len,
|
||||
uint32_t timeout_ms, uint8_t* out, size_t cap,
|
||||
size_t* out_len) {
|
||||
if (!active_link || active_link.status() != RNS::Type::Link::ACTIVE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The request payload must be msgpack-encoded itself: Link::request
|
||||
// splices it verbatim into the envelope's third element.
|
||||
MsgPack::Packer packer;
|
||||
packer.packBinary(request, request_len);
|
||||
RNS::Bytes encoded(packer.data(), packer.size());
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(slot_mutex);
|
||||
slot_ready = false;
|
||||
slot_failed = false;
|
||||
slot_response.clear();
|
||||
}
|
||||
// A client MUST set its own request timeout (upstream spec sec 13.5):
|
||||
// Reticulum's default is derived from the round trip time and covers a
|
||||
// packet, not a FETCH page.
|
||||
RNS::RequestReceipt receipt = active_link.request(RNS::Bytes(REQ_PATH), encoded,
|
||||
on_response, on_failed, nullptr,
|
||||
(double)timeout_ms / 1000.0);
|
||||
if (!receipt) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
// No path, a dead link, a rejected resource and a timeout are local
|
||||
// errors; none of them is a status code.
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(slot_mutex);
|
||||
const bool concluded = slot_cv.wait_for(lock,
|
||||
std::chrono::milliseconds(timeout_ms + 1000),
|
||||
[]() { return slot_ready; });
|
||||
if (!concluded) {
|
||||
return -3;
|
||||
}
|
||||
if (slot_failed) {
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
|
||||
// microReticulum decodes the response envelope differently for the two
|
||||
// transfer modes: a small response arrives as the bare smolmail payload
|
||||
// (`status u8 || payload`), while a large one, transferred as a
|
||||
// resource, is still wrapped in its msgpack binary (Link.cpp hands the
|
||||
// raw remainder to handle_response on that path). The wrapper is
|
||||
// unambiguous: a smolmail status is a single byte below 16, and the
|
||||
// msgpack bin headers are 0xC4..0xC6, so only those are unwrapped.
|
||||
const RNS::Bytes& payload = [&]() -> const RNS::Bytes& {
|
||||
if (slot_response.size() > 0 && slot_response.data()[0] >= 0xC4
|
||||
&& slot_response.data()[0] <= 0xC6) {
|
||||
MsgPack::Unpacker unpacker;
|
||||
unpacker.feed(slot_response.data(), slot_response.size());
|
||||
if (unpacker.isBin()) {
|
||||
MsgPack::bin_t<uint8_t> bin;
|
||||
unpacker.deserialize(bin);
|
||||
unwrapped = RNS::Bytes(bin.data(), bin.size());
|
||||
}
|
||||
}
|
||||
return unwrapped.size() > 0 ? unwrapped : slot_response;
|
||||
}();
|
||||
if (!payload || payload.size() == 0) {
|
||||
return -5;
|
||||
}
|
||||
if (payload.size() > cap) {
|
||||
return -6;
|
||||
}
|
||||
memcpy(out, payload.data(), payload.size());
|
||||
*out_len = payload.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void smolmail_rns_close(void) {
|
||||
if (active_link) {
|
||||
active_link.teardown();
|
||||
active_link = RNS::Link({RNS::Type::NONE});
|
||||
}
|
||||
}
|
||||
59
core/shim/smolmail_rns.h
Normal file
59
core/shim/smolmail_rns.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* C ABI between fumi (Rust) and the microReticulum client shim.
|
||||
*
|
||||
* All calls block the caller; the shim owns the Reticulum loop thread and
|
||||
* every callback fires on that thread. link_id and destination_hash are
|
||||
* always 16 bytes. No client Reticulum identity is created or loaded anywhere
|
||||
* (upstream spec sec 13.8): a server MUST NOT require identification, and a
|
||||
* durable client handle is exactly what the wire format withholds.
|
||||
*/
|
||||
#ifndef SMOLMAIL_RNS_H
|
||||
#define SMOLMAIL_RNS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Implemented in smolmail_rns.cpp, called from src/rns/ffi.rs.
|
||||
* udp_forward_host may be NULL: with no forward target the interface sends
|
||||
* to the source address of the last datagram received, which a client that
|
||||
* only listens cannot use -- a client speaks first, so the forward target
|
||||
* should point at the server's UDP interface.
|
||||
* Returns 0 on success, negative on failure. */
|
||||
int smolmail_rns_start(const char *storage_dir,
|
||||
const char *udp_listen_host, uint16_t udp_listen_port,
|
||||
const char *udp_forward_host, uint16_t udp_forward_port);
|
||||
|
||||
/* Requests a path if none is known and waits for it (upstream spec sec
|
||||
* 13.3), recalls the identity, builds the OUT/SINGLE smolmail.server
|
||||
* destination, opens the link and waits for ACTIVE. Returns the 16-byte
|
||||
* link id, which the caller's AUTH and REGISTER bind values are derived
|
||||
* from (upstream spec sec 13.6).
|
||||
* Returns 0 on success, negative on failure. */
|
||||
int smolmail_rns_connect(const uint8_t *destination_hash, uint32_t timeout_ms,
|
||||
uint8_t *link_id_out);
|
||||
|
||||
/* request is `op u8 || body`, the response `status u8 || payload`
|
||||
* (upstream spec sec 13.5). A CLI has one request in flight at a time, so
|
||||
* the shim keeps a single response slot rather than a map.
|
||||
* Returns 0 on success and writes the response length to *out_len;
|
||||
* negative on failure: no link (-1), send failure (-2), timeout or closed
|
||||
* link (-3), failed transfer (-4), malformed response (-5), response larger
|
||||
* than cap (-6). These are local errors and MUST NOT be reported as status
|
||||
* codes (upstream spec sec 13.5). */
|
||||
int smolmail_rns_request(const uint8_t *request, size_t request_len,
|
||||
uint32_t timeout_ms, uint8_t *out, size_t cap,
|
||||
size_t *out_len);
|
||||
|
||||
/* Tears the link down rather than leave it on keepalives, which run for
|
||||
* minutes (upstream spec sec 13.10). */
|
||||
void smolmail_rns_close(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SMOLMAIL_RNS_H */
|
||||
198
core/shim/udp_interface.cpp
Normal file
198
core/shim/udp_interface.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#include "udp_interface.h"
|
||||
|
||||
#include <microReticulum/Transport.h>
|
||||
#include <microReticulum/Log.h>
|
||||
|
||||
#ifndef ARDUINO
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
using namespace RNS;
|
||||
|
||||
UDPInterface::UDPInterface(const char* name,
|
||||
const std::string& local_host, int local_port,
|
||||
const std::string& remote_host, int remote_port)
|
||||
: RNS::InterfaceImpl(name) {
|
||||
|
||||
_IN = true;
|
||||
_OUT = true;
|
||||
_bitrate = BITRATE_GUESS;
|
||||
_HW_MTU = 1064;
|
||||
|
||||
_local_host = local_host;
|
||||
_local_port = local_port;
|
||||
if (!remote_host.empty()) {
|
||||
_forward_configured = true;
|
||||
_remote_host = remote_host;
|
||||
_remote_port = remote_port;
|
||||
}
|
||||
}
|
||||
|
||||
/*virtual*/ UDPInterface::~UDPInterface() {
|
||||
stop();
|
||||
}
|
||||
|
||||
/*virtual*/ bool UDPInterface::start() {
|
||||
_online = false;
|
||||
|
||||
#ifdef ARDUINO
|
||||
udp.begin(_local_port);
|
||||
#else
|
||||
// resolve local host
|
||||
struct in_addr local_addr;
|
||||
if (inet_aton(_local_host.c_str(), &local_addr) == 0) {
|
||||
struct hostent* host_ent = gethostbyname(_local_host.c_str());
|
||||
if (host_ent == nullptr || host_ent->h_addr_list[0] == nullptr) {
|
||||
ERRORF("Unable to resolve local host %s", _local_host.c_str());
|
||||
return false;
|
||||
}
|
||||
_local_address = *((in_addr_t*)(host_ent->h_addr_list[0]));
|
||||
}
|
||||
else {
|
||||
_local_address = local_addr.s_addr;
|
||||
}
|
||||
|
||||
_remote_address = INADDR_NONE;
|
||||
if (_forward_configured) {
|
||||
struct in_addr remote_addr;
|
||||
if (inet_aton(_remote_host.c_str(), &remote_addr) == 0) {
|
||||
struct hostent* host_ent = gethostbyname(_remote_host.c_str());
|
||||
if (host_ent == nullptr || host_ent->h_addr_list[0] == nullptr) {
|
||||
ERRORF("Unable to resolve remote host %s", _remote_host.c_str());
|
||||
return false;
|
||||
}
|
||||
_remote_address = *((in_addr_t*)(host_ent->h_addr_list[0]));
|
||||
}
|
||||
else {
|
||||
_remote_address = remote_addr.s_addr;
|
||||
}
|
||||
}
|
||||
|
||||
_socket = socket( PF_INET, SOCK_DGRAM, 0 );
|
||||
if (_socket < 0) {
|
||||
ERRORF("Unable to create socket with error %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
int broadcast = 1;
|
||||
setsockopt(_socket, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
|
||||
|
||||
int reuse = 1;
|
||||
setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
#ifdef SO_REUSEPORT
|
||||
setsockopt(_socket, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
|
||||
#endif
|
||||
|
||||
INFOF("Binding UDP socket %d to %s:%d", _socket, _local_host.c_str(), _local_port);
|
||||
sockaddr_in bind_addr;
|
||||
memset(&bind_addr, 0, sizeof(bind_addr));
|
||||
bind_addr.sin_family = AF_INET;
|
||||
bind_addr.sin_addr.s_addr = _local_address;
|
||||
bind_addr.sin_port = htons(_local_port);
|
||||
if (bind(_socket, (struct sockaddr*)&bind_addr, sizeof(bind_addr)) == -1) {
|
||||
ERRORF("Unable to bind socket with error %d", errno);
|
||||
close(_socket);
|
||||
_socket = -1;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
_online = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*virtual*/ void UDPInterface::stop() {
|
||||
#ifndef ARDUINO
|
||||
if (_socket > -1) {
|
||||
close(_socket);
|
||||
_socket = -1;
|
||||
}
|
||||
#endif
|
||||
_online = false;
|
||||
}
|
||||
|
||||
/*virtual*/ void UDPInterface::loop() {
|
||||
if (!_online) {
|
||||
return;
|
||||
}
|
||||
#ifdef ARDUINO
|
||||
udp.parsePacket();
|
||||
size_t len = udp.read(_buffer.writable(Type::Reticulum::MTU), Type::Reticulum::MTU);
|
||||
if (len > 0) {
|
||||
_buffer.resize(len);
|
||||
on_incoming(_buffer);
|
||||
}
|
||||
#else
|
||||
// One datagram per recvfrom() with MSG_DONTWAIT, looping until the
|
||||
// kernel queue is empty — the same drain pattern as the microReticulum
|
||||
// example, which avoids stale/zero FIONREAD counts on some platforms.
|
||||
while (true) {
|
||||
sockaddr_in src_addr{};
|
||||
socklen_t src_addr_len = sizeof(src_addr);
|
||||
ssize_t len = recvfrom(_socket,
|
||||
_buffer.writable(_HW_MTU),
|
||||
_HW_MTU,
|
||||
MSG_DONTWAIT,
|
||||
(struct sockaddr*)&src_addr,
|
||||
&src_addr_len);
|
||||
if (len <= 0) {
|
||||
break;
|
||||
}
|
||||
_buffer.resize(static_cast<size_t>(len));
|
||||
if (!_forward_configured) {
|
||||
_last_src_addr = src_addr;
|
||||
_have_src = true;
|
||||
}
|
||||
on_incoming(_buffer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*virtual*/ bool UDPInterface::send_outgoing(const RNS::Bytes& data) {
|
||||
bool success = true;
|
||||
try {
|
||||
if (_online) {
|
||||
#ifdef ARDUINO
|
||||
udp.beginPacket(_remote_host.c_str(), _remote_port);
|
||||
udp.write(data.data(), data.size());
|
||||
udp.endPacket();
|
||||
#else
|
||||
sockaddr_in sock_addr;
|
||||
if (_forward_configured) {
|
||||
memset(&sock_addr, 0, sizeof(sock_addr));
|
||||
sock_addr.sin_family = AF_INET;
|
||||
sock_addr.sin_addr.s_addr = _remote_address;
|
||||
sock_addr.sin_port = htons(_remote_port);
|
||||
}
|
||||
else if (_have_src) {
|
||||
// No forward target: reply to whoever spoke to us last.
|
||||
sock_addr = _last_src_addr;
|
||||
}
|
||||
else {
|
||||
WARNING("UDPInterface: no forward target and no peer heard from yet, dropping outgoing packet");
|
||||
return false;
|
||||
}
|
||||
ssize_t sent = sendto(_socket, data.data(), data.size(), 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
|
||||
if (sent != (ssize_t)data.size()) {
|
||||
WARNINGF("Failed sending %d bytes via UDP", (int)data.size());
|
||||
success = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
InterfaceImpl::handle_outgoing(data);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
ERRORF("Could not transmit on %s. The contained exception was: %s", toString().c_str(), e.what());
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void UDPInterface::on_incoming(const RNS::Bytes& data) {
|
||||
InterfaceImpl::handle_incoming(data);
|
||||
}
|
||||
64
core/shim/udp_interface.h
Normal file
64
core/shim/udp_interface.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* UDP interface for the bunshin RNS shim, adapted from microReticulum's
|
||||
* examples/common/udp_interface (Apache-2.0). The example version hardcodes
|
||||
* compile-time defaults; this one takes its endpoints from the caller, and
|
||||
* with no forward target configured it sends to the source address of the
|
||||
* last datagram received, so a listen-only server can answer any client.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <microReticulum/Interface.h>
|
||||
#include <microReticulum/Bytes.h>
|
||||
#include <microReticulum/Type.h>
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
class UDPInterface : public RNS::InterfaceImpl {
|
||||
|
||||
public:
|
||||
static const uint32_t BITRATE_GUESS = 10*1000*1000;
|
||||
|
||||
UDPInterface(const char* name,
|
||||
const std::string& local_host, int local_port,
|
||||
const std::string& remote_host, int remote_port);
|
||||
virtual ~UDPInterface();
|
||||
|
||||
virtual bool start();
|
||||
virtual void stop();
|
||||
virtual void loop();
|
||||
|
||||
virtual inline std::string toString() const { return "UDPInterface[" + _name + "/" + _local_host + ":" + std::to_string(_local_port) + "]"; }
|
||||
|
||||
protected:
|
||||
virtual bool send_outgoing(const RNS::Bytes& data);
|
||||
void on_incoming(const RNS::Bytes& data);
|
||||
|
||||
private:
|
||||
RNS::Bytes _buffer;
|
||||
|
||||
std::string _local_host;
|
||||
int _local_port;
|
||||
std::string _remote_host;
|
||||
int _remote_port;
|
||||
|
||||
bool _forward_configured = false;
|
||||
|
||||
#ifdef ARDUINO
|
||||
WiFiUDP udp;
|
||||
#else
|
||||
int _socket = -1;
|
||||
in_addr_t _local_address = INADDR_ANY;
|
||||
in_addr_t _remote_address = INADDR_NONE;
|
||||
sockaddr_in _last_src_addr = {};
|
||||
bool _have_src = false;
|
||||
#endif
|
||||
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue