mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-11-11 14:47:39 +00:00
248 lines
17 KiB
C++
248 lines
17 KiB
C++
#include "platform/esp/idf_common/hostlink_track_directories_runtime.h"
|
|
#include <cassert>
|
|
#include <vector>
|
|
|
|
struct Adapter : chat::IMeshAdapter
|
|
{
|
|
const chat::lxmf::TrackDeliveryBinding* binding = nullptr;
|
|
unsigned binds = 0;
|
|
bool allow_bind = true;
|
|
bool binding_busy = false;
|
|
bool allow_send = true;
|
|
unsigned sends = 0;
|
|
std::vector<uint8_t> network_wire;
|
|
unsigned path_requests = 0;
|
|
uint8_t path_target[16]{};
|
|
bool allow_path = true;
|
|
bool requestTrackDirectoryPath(const uint8_t* destination) override
|
|
{
|
|
++path_requests;
|
|
std::memcpy(path_target, destination, 16);
|
|
return allow_path;
|
|
}
|
|
chat::DeviceAuthorResult readDeviceAuthorKey(uint8_t* key, uint32_t& generation) const override
|
|
{
|
|
std::memset(key, 7, 64);
|
|
generation = 9;
|
|
return chat::DeviceAuthorResult::Ok;
|
|
}
|
|
bool trackLocalDestination(uint8_t* value) override
|
|
{
|
|
std::memset(value, 8, 16);
|
|
return true;
|
|
}
|
|
chat::MeshSendResult sendTrackData(const uint8_t* destination, chat::lxmf::ByteSpan body, const uint8_t* author, uint32_t generation, bool response, std::array<uint8_t, 32>*) override
|
|
{
|
|
assert(destination[0] == 4 && author[0] == 7 && generation == 9 && !response);
|
|
++sends;
|
|
network_wire.assign(body.data, body.data + body.size);
|
|
return allow_send ? chat::MeshSendResult::success(1) : chat::MeshSendResult::fail(chat::MeshOperationFailure::Busy);
|
|
}
|
|
bool bindTrackDelivery(const chat::lxmf::TrackDeliveryBinding* value) override
|
|
{
|
|
++binds;
|
|
if (!allow_bind) return false;
|
|
binding = value;
|
|
return true;
|
|
}
|
|
chat::TrackBindingChange replaceTrackDelivery(const chat::lxmf::TrackDeliveryBinding* expected, const chat::lxmf::TrackDeliveryBinding* replacement) override
|
|
{
|
|
++binds;
|
|
if (!allow_bind) return chat::TrackBindingChange::Unsupported;
|
|
if (binding_busy) return chat::TrackBindingChange::Busy;
|
|
if (binding != expected) return chat::TrackBindingChange::Conflict;
|
|
binding = replacement;
|
|
return chat::TrackBindingChange::Applied;
|
|
}
|
|
bool sendText(chat::ChannelId, const std::string&, chat::MessageId*, chat::NodeId) override { return false; }
|
|
bool pollIncomingText(chat::MeshIncomingText*) override { return false; }
|
|
bool sendAppData(chat::ChannelId, uint32_t, const uint8_t*, size_t, chat::NodeId, bool, chat::MessageId, bool) override { return false; }
|
|
bool pollIncomingData(chat::MeshIncomingData*) override { return false; }
|
|
void applyConfig(const chat::MeshConfig&) override {}
|
|
bool isReady() const override { return true; }
|
|
bool pollIncomingRawPacket(uint8_t*, size_t&, size_t) override { return false; }
|
|
};
|
|
int main()
|
|
{
|
|
using namespace hostlink::management;
|
|
namespace runtime = directory_runtime;
|
|
Adapter adapter;
|
|
uint8_t session[16], other[16], body[] = {0x91, 0}, output[320];
|
|
std::memset(session, 1, 16);
|
|
std::memset(other, 2, 16);
|
|
EnvelopeView request{session, other, uint16_t(Operation::TrackDirectoryList), body, sizeof(body)};
|
|
size_t written = 0;
|
|
directory_free_bytes = 1;
|
|
assert(runtime::list(adapter, request, 1000, output, sizeof(output), written) == Status::Busy && !runtime::owner && !directory_live_allocations);
|
|
directory_free_bytes = 65536;
|
|
adapter.allow_bind = false;
|
|
assert(runtime::list(adapter, request, 1000, output, sizeof(output), written) == Status::Unsupported && !runtime::owner && !directory_live_allocations);
|
|
adapter.allow_bind = true;
|
|
assert(runtime::list(adapter, request, 1000, output, sizeof(output), written) == Status::Ok && runtime::owner && directory_live_allocations == 1 && adapter.binding);
|
|
assert(written == 3 && output[0] == 0x92 && output[1] == 0xc0 && output[2] == 0x90);
|
|
{
|
|
uint8_t discover_body[32], target[16];
|
|
std::memset(target, 3, 16);
|
|
geocaching::protocol::CmpWriter discovery_request(discover_body, sizeof(discover_body));
|
|
assert(discovery_request.array(1) && discovery_request.binary({target, 16}));
|
|
EnvelopeView discovery{session, other, uint16_t(Operation::TrackDirectoryDiscover), discover_body, discovery_request.size()};
|
|
assert(runtime::discover(adapter, discovery, 1000, output, sizeof(output), written) == Status::Ok && written == 1 && output[0] == 0x90);
|
|
assert(adapter.path_requests == 1 && !std::memcmp(adapter.path_target, target, 16) && runtime::owner->pool.size() == 0);
|
|
adapter.allow_path = false;
|
|
assert(runtime::discover(adapter, discovery, 1000, output, sizeof(output), written) == Status::Busy && written == 0);
|
|
adapter.allow_path = true;
|
|
discovery.session = other;
|
|
assert(runtime::discover(adapter, discovery, 1000, output, sizeof(output), written) == Status::Conflict && adapter.path_requests == 2);
|
|
discovery.session = session;
|
|
std::memset(discover_body + 3, 0, 16);
|
|
assert(runtime::discover(adapter, discovery, 1000, output, sizeof(output), written) == Status::InvalidField && adapter.path_requests == 2);
|
|
}
|
|
uint8_t discovery[16], delivery[16], public_key[64], epoch[16], metadata[128];
|
|
std::memset(discovery, 3, 16);
|
|
std::memset(delivery, 4, 16);
|
|
std::memset(public_key, 5, 64);
|
|
std::memset(epoch, 6, 16);
|
|
geocaching::protocol::CmpWriter announcement(metadata, sizeof(metadata));
|
|
assert(announcement.array(5) && announcement.unsignedInteger(1) && announcement.text(track_sharing::kApplicationType) && announcement.binary({delivery, 16}) && announcement.binary({epoch, 16}) && announcement.text("Node"));
|
|
chat::lxmf::TrackDirectoryAnnouncementView view{{discovery, 16}, {delivery, 16}, {public_key, 64}, {metadata, announcement.size()}};
|
|
assert(chat::lxmf::deliverVerifiedTrackDirectory(adapter.binding, view));
|
|
assert(runtime::list(adapter, request, 1001, output, sizeof(output), written) == Status::Ok);
|
|
geocaching::protocol::CmpReader reader({output, written});
|
|
size_t fields = 0;
|
|
uint64_t ready = 0;
|
|
geocaching::ByteView field;
|
|
std::string_view name;
|
|
assert(reader.array(fields, 2) && fields == 2 && reader.nil() && reader.array(fields, 1) && fields == 1 && reader.array(fields, 8) && fields == 8);
|
|
assert(reader.binary(field, 16) && field.size == 16 && !std::memcmp(field.data, delivery, 16));
|
|
assert(reader.binary(field, 64) && field.size == 64 && !std::memcmp(field.data, public_key, 64));
|
|
assert(reader.binary(field, 16) && field.size == 16 && !std::memcmp(field.data, epoch, 16));
|
|
assert(reader.text(name, 40) && name == "Node" && reader.unsignedInteger(ready) && ready == 0);
|
|
assert(reader.unsignedInteger(ready) && ready == 0 && reader.unsignedInteger(ready) && ready == 0 && reader.unsignedInteger(ready) && ready == 0 && reader.finished());
|
|
runtime::PinnedDirectory pinned;
|
|
pinned.file_limit = 123;
|
|
assert(runtime::pin(adapter, session, {delivery, 16}, {epoch, 16}, 1002, pinned) == Status::Unauthorized && pinned.file_limit == 123);
|
|
uint8_t network_scratch[512], reply[512], local[16], message_hash[32]{}, wrong_source[16]{};
|
|
std::memset(local, 8, 16);
|
|
adapter.allow_send = false;
|
|
runtime::advance(1002, true, network_scratch, sizeof(network_scratch));
|
|
assert(adapter.sends == 1 && !runtime::owner->pending);
|
|
adapter.allow_send = true;
|
|
runtime::advance(1003, true, network_scratch, sizeof(network_scratch));
|
|
assert(adapter.sends == 1);
|
|
runtime::advance(4003, true, network_scratch, sizeof(network_scratch));
|
|
assert(adapter.sends == 2 && runtime::owner->pending);
|
|
runtime::advance(4004, true, network_scratch, sizeof(network_scratch));
|
|
assert(adapter.sends == 2);
|
|
geocaching::protocol::CmpReader outgoing({adapter.network_wire.data(), adapter.network_wire.size()});
|
|
uint64_t value = 0;
|
|
geocaching::ByteView request_id;
|
|
assert(outgoing.array(fields, 6) && fields == 6 && outgoing.unsignedInteger(value) && value == 1 && outgoing.unsignedInteger(value) && value == 0 &&
|
|
outgoing.unsignedInteger(value) && value == 0 && outgoing.binary(request_id, 16) && request_id.size == 16 && outgoing.unsignedInteger(value) && value == 8192 &&
|
|
outgoing.array(fields, 0) && fields == 0 && outgoing.finished());
|
|
geocaching::protocol::CmpWriter capabilities(reply, sizeof(reply));
|
|
assert(capabilities.array(6) && capabilities.unsignedInteger(1) && capabilities.unsignedInteger(1) && capabilities.unsignedInteger(0) &&
|
|
capabilities.binary(request_id) && capabilities.unsignedInteger(200) && capabilities.array(8) && capabilities.unsignedInteger(1) &&
|
|
capabilities.text(track_sharing::kApplicationType) && capabilities.binary({public_key, 64}) && capabilities.binary({epoch, 16}) &&
|
|
capabilities.unsignedInteger(8388608) && capabilities.unsignedInteger(1024) && capabilities.unsignedInteger(32) && capabilities.array(9));
|
|
for (unsigned op = 0; op < 9; ++op) assert(capabilities.unsignedInteger(op));
|
|
chat::lxmf::CustomDeliveryView response{{wrong_source, 16}, {local, 16}, {message_hash, 32}, {reply, capabilities.size()}};
|
|
assert(!chat::lxmf::deliverVerifiedTrack(adapter.binding, false, false, response) && runtime::owner->pending);
|
|
response.source = {delivery, 16};
|
|
assert(!chat::lxmf::deliverVerifiedTrack(adapter.binding, true, false, response));
|
|
assert(chat::lxmf::deliverVerifiedTrack(adapter.binding, false, false, response) && !runtime::owner->pending && runtime::owner->pool.at(0)->capabilities_verified);
|
|
runtime::advance(4005, true, network_scratch, sizeof(network_scratch));
|
|
assert(adapter.sends == 2);
|
|
assert(runtime::list(adapter, request, 4005, output, sizeof(output), written) == Status::Ok);
|
|
geocaching::protocol::CmpReader negotiated({output, written});
|
|
assert(negotiated.array(fields, 2) && fields == 2 && negotiated.nil() && negotiated.array(fields, 1) && fields == 1 && negotiated.array(fields, 8) && fields == 8);
|
|
for (const auto length : {size_t(16), size_t(64), size_t(16)}) assert(negotiated.binary(field, length) && field.size == length);
|
|
assert(negotiated.text(name, 40) && name == "Node" && negotiated.unsignedInteger(value) && value == 1 &&
|
|
negotiated.unsignedInteger(value) && value == 8388608 && negotiated.unsignedInteger(value) && value == 1024 && negotiated.unsignedInteger(value) && value == 32 && negotiated.finished());
|
|
assert(runtime::pin(adapter, session, {delivery, 16}, {epoch, 16}, 4006, pinned) == Status::Ok);
|
|
assert(!std::memcmp(pinned.delivery, delivery, 16) && !std::memcmp(pinned.public_key, public_key, 64) && !std::memcmp(pinned.epoch, epoch, 16));
|
|
assert(pinned.file_limit == 8388608 && pinned.chunk_limit == 1024 && pinned.query_limit == 32);
|
|
const auto pinned_before_failure = pinned;
|
|
assert(runtime::pin(adapter, other, {delivery, 16}, {epoch, 16}, 4007, pinned) == Status::Unauthorized);
|
|
assert(!std::memcmp(pinned.delivery, pinned_before_failure.delivery, 16) && !std::memcmp(pinned.public_key, pinned_before_failure.public_key, 64) &&
|
|
!std::memcmp(pinned.epoch, pinned_before_failure.epoch, 16) && pinned.file_limit == pinned_before_failure.file_limit &&
|
|
pinned.chunk_limit == pinned_before_failure.chunk_limit && pinned.query_limit == pinned_before_failure.query_limit);
|
|
assert(runtime::pin(adapter, session, {pinned.delivery, 16}, {epoch, 16}, 4007, pinned) == Status::InvalidField);
|
|
runtime::owner->locked.test_and_set();
|
|
assert(runtime::pin(adapter, session, {delivery, 16}, {epoch, 16}, 4007, pinned) == Status::Busy);
|
|
runtime::owner->locked.clear();
|
|
assert(runtime::pin(adapter, session, {delivery, 16}, {epoch, 16}, 34006, pinned) == Status::Expired);
|
|
epoch[0] ^= 1;
|
|
assert(runtime::pin(adapter, session, {delivery, 16}, {epoch, 16}, 4007, pinned) == Status::Expired);
|
|
directory_clock_us = 5000000;
|
|
geocaching::protocol::CmpWriter refreshed(metadata, sizeof(metadata));
|
|
assert(refreshed.array(5) && refreshed.unsignedInteger(1) && refreshed.text(track_sharing::kApplicationType) && refreshed.binary({delivery, 16}) && refreshed.binary({epoch, 16}) && refreshed.text("Node"));
|
|
view.app_data.size = refreshed.size();
|
|
assert(chat::lxmf::deliverVerifiedTrackDirectory(adapter.binding, view) && !runtime::owner->pool.at(0)->capabilities_verified);
|
|
runtime::advance(5000, true, network_scratch, sizeof(network_scratch));
|
|
assert(adapter.sends == 3 && runtime::owner->pending);
|
|
assert(!chat::lxmf::deliverVerifiedTrack(adapter.binding, false, false, response) && runtime::owner->pending);
|
|
geocaching::protocol::CmpReader fresh({adapter.network_wire.data(), adapter.network_wire.size()});
|
|
geocaching::ByteView fresh_id;
|
|
assert(fresh.array(fields, 6) && fields == 6 && fresh.unsignedInteger(value) && value == 1 && fresh.unsignedInteger(value) && value == 0 && fresh.unsignedInteger(value) && value == 0 && fresh.binary(fresh_id, 16));
|
|
geocaching::protocol::CmpWriter late(reply, sizeof(reply));
|
|
assert(late.array(6) && late.unsignedInteger(1) && late.unsignedInteger(1) && late.unsignedInteger(0) && late.binary(fresh_id) && late.unsignedInteger(200) &&
|
|
late.array(8) && late.unsignedInteger(1) && late.text(track_sharing::kApplicationType) && late.binary({public_key, 64}) && late.binary({epoch, 16}) &&
|
|
late.unsignedInteger(8388608) && late.unsignedInteger(1024) && late.unsignedInteger(32) && late.array(9));
|
|
for (unsigned op = 0; op < 9; ++op) assert(late.unsignedInteger(op));
|
|
response.data.size = late.size();
|
|
track_sharing::TrackDirectoryCapabilities late_capabilities;
|
|
assert(track_sharing::decodeTrackCapabilities({reply, late.size()}, fresh_id, late_capabilities));
|
|
directory_clock_us = 20001000;
|
|
assert(!chat::lxmf::deliverVerifiedTrack(adapter.binding, false, false, response) && runtime::owner->pending);
|
|
runtime::advance(20001, true, network_scratch, sizeof(network_scratch));
|
|
assert(!runtime::owner->pending && adapter.sends == 3);
|
|
assert(runtime::list(adapter, request, 20002, output, sizeof(output), written) == Status::Ok);
|
|
runtime::advance(20003, true, network_scratch, sizeof(network_scratch));
|
|
assert(adapter.sends == 3);
|
|
request.session = other;
|
|
assert(runtime::list(adapter, request, 20003, output, sizeof(output), written) == Status::Conflict);
|
|
request.session = session;
|
|
runtime::owner->locked.test_and_set();
|
|
assert(runtime::list(adapter, request, 20003, output, sizeof(output), written) == Status::Busy);
|
|
assert(!chat::lxmf::deliverVerifiedTrackDirectory(adapter.binding, view));
|
|
runtime::owner->locked.clear();
|
|
runtime::closeSession(other);
|
|
assert(runtime::owner);
|
|
runtime::advance(50002, true);
|
|
assert(!runtime::owner && !adapter.binding && !directory_live_allocations);
|
|
assert(runtime::list(adapter, request, 70000, output, sizeof(output), written) == Status::Ok);
|
|
runtime::advance(70001, false);
|
|
assert(!runtime::owner && !adapter.binding && !directory_live_allocations);
|
|
assert(runtime::list(adapter, request, 73000, output, sizeof(output), written) == Status::Ok);
|
|
runtime::closeSession(session);
|
|
assert(!runtime::owner && !adapter.binding && !directory_live_allocations);
|
|
assert(!std::memcmp(pinned.public_key, public_key, 64) && !std::memcmp(pinned.epoch, pinned_before_failure.epoch, 16));
|
|
uint8_t persisted[track_sharing::kPublicationDirectoryBytes];
|
|
track_sharing::PublicationDirectory recovered;
|
|
assert(track_sharing::encodePublicationDirectory(pinned, persisted, sizeof(persisted)));
|
|
assert(track_sharing::decodePublicationDirectory(persisted, sizeof(persisted), recovered));
|
|
assert(track_sharing::publicationDirectoriesEqual(pinned, recovered));
|
|
chat::lxmf::TrackDeliveryBinding job;
|
|
job.handler = [](const chat::lxmf::CustomDeliveryView&, void*)
|
|
{ return false; };
|
|
job.expected_local_author = public_key;
|
|
job.expected_author_generation = 1;
|
|
assert(runtime::list(adapter, request, 80000, output, sizeof(output), written) == Status::Ok && runtime::owner && directory_live_allocations == 1);
|
|
const auto* directory_binding = adapter.binding;
|
|
adapter.binding_busy = true;
|
|
assert(runtime::handover(adapter, &job) == chat::TrackBindingChange::Busy && adapter.binding == directory_binding && runtime::owner && directory_live_allocations == 1);
|
|
assert(!runtime::release() && runtime::owner && adapter.binding == directory_binding);
|
|
adapter.binding_busy = false;
|
|
assert(runtime::handover(adapter, &job) == chat::TrackBindingChange::Applied && !runtime::owner && adapter.binding == &job && !directory_live_allocations);
|
|
runtime::closeSession(session);
|
|
assert(adapter.binding == &job);
|
|
assert(runtime::list(adapter, request, 80001, output, sizeof(output), written) == Status::Busy && !runtime::owner && adapter.binding == &job && !directory_live_allocations);
|
|
assert(adapter.replaceTrackDelivery(&job, nullptr) == chat::TrackBindingChange::Applied);
|
|
assert(runtime::list(adapter, request, 80002, output, sizeof(output), written) == Status::Ok && runtime::owner);
|
|
// Another owner has replaced the slot. Retire the obsolete directory
|
|
// context after observing Conflict, without clearing the new registration.
|
|
adapter.binding = &job;
|
|
assert(runtime::release() && !runtime::owner && adapter.binding == &job && !directory_live_allocations);
|
|
assert(adapter.replaceTrackDelivery(&job, nullptr) == chat::TrackBindingChange::Applied);
|
|
}
|