mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-08-14 14:49:47 +00:00
1247 lines
60 KiB
C++
1247 lines
60 KiB
C++
#include "chat/domain/reticulum_identity.h"
|
|
#include "chat/infra/mesh_incoming_queue.h"
|
|
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_delivery_runtime.h"
|
|
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_runtime.h"
|
|
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_runtime.h"
|
|
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_service_runtime.h"
|
|
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_resource_runtime.h"
|
|
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_runtime_state.h"
|
|
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_transport_runtime.h"
|
|
#include "team/protocol/team_portnum.h"
|
|
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
|
|
template <std::size_t N>
|
|
std::array<uint8_t, N> filled_hash(uint8_t seed)
|
|
{
|
|
std::array<uint8_t, N> out = {};
|
|
for (std::size_t index = 0; index < out.size(); ++index)
|
|
{
|
|
out[index] = static_cast<uint8_t>(seed + index);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
template <std::size_t N>
|
|
bool same_hash(const uint8_t (&lhs)[N], const std::array<uint8_t, N>& rhs)
|
|
{
|
|
return std::memcmp(lhs, rhs.data(), N) == 0;
|
|
}
|
|
|
|
template <std::size_t N>
|
|
bool all_zero(const uint8_t (&value)[N])
|
|
{
|
|
for (uint8_t byte : value)
|
|
{
|
|
if (byte != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <std::size_t N>
|
|
void copy_hash(uint8_t (&out)[N], const std::array<uint8_t, N>& value)
|
|
{
|
|
std::memcpy(out, value.data(), N);
|
|
}
|
|
|
|
std::array<uint8_t, chat::lxmf::runtime::kAnnounceRandomBlobSize>
|
|
announce_blob(uint8_t seed, uint64_t emitted)
|
|
{
|
|
std::array<uint8_t, chat::lxmf::runtime::kAnnounceRandomBlobSize> out = {};
|
|
for (std::size_t index = 0; index < 5; ++index)
|
|
{
|
|
out[index] = static_cast<uint8_t>(seed + index);
|
|
}
|
|
for (std::size_t index = out.size(); index > 5; --index)
|
|
{
|
|
out[index - 1U] = static_cast<uint8_t>(emitted & 0xFFU);
|
|
emitted >>= 8U;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
using namespace chat::lxmf::runtime;
|
|
namespace reticulum = chat::reticulum;
|
|
|
|
TransportRuntime transport{};
|
|
assert(transport.paths.empty());
|
|
assert(transport.packet_filter.empty());
|
|
assert(transport.reverse_table.empty());
|
|
assert(transport.pending_path_requests.empty());
|
|
assert(transport.pending_ping_receipts.empty());
|
|
assert(transport.pending_delivery_receipts.empty());
|
|
assert(transport.link_relays.empty());
|
|
|
|
const TransportRuntimeLimits limits{
|
|
4,
|
|
4,
|
|
4,
|
|
4,
|
|
4,
|
|
30000,
|
|
45000,
|
|
60000,
|
|
300000,
|
|
4,
|
|
1000,
|
|
500,
|
|
4,
|
|
750};
|
|
|
|
const auto destination = filled_hash<reticulum::kTruncatedHashSize>(0x10);
|
|
PathEntry& path = upsertPath(transport, destination.data(), limits.max_paths);
|
|
path.cached_announce_len = 42;
|
|
const auto first_announce = announce_blob(0xA0, 100);
|
|
assert(evaluatePathAnnounce(nullptr,
|
|
2,
|
|
first_announce.data(),
|
|
100,
|
|
limits.path_ttl_ms) ==
|
|
PathAnnounceDecision::AcceptNew);
|
|
applyPathAnnounce(path, 2, first_announce.data(), 100, 1234);
|
|
path.direct = false;
|
|
assert(transport.paths.size() == 1);
|
|
assert(same_hash(transport.paths.front().destination_hash, destination));
|
|
assert(transport.paths.front().cached_announce_len == 42);
|
|
assert(findPath(transport, destination.data()) == &transport.paths.front());
|
|
assert(!pathExpired(path, 1100, limits.path_ttl_ms));
|
|
assert(pathExpired(path, 1101, limits.path_ttl_ms));
|
|
assert(evaluatePathAnnounce(&path,
|
|
2,
|
|
first_announce.data(),
|
|
200,
|
|
limits.path_ttl_ms) ==
|
|
PathAnnounceDecision::RejectReplay);
|
|
const auto stale_better_announce = announce_blob(0xB0, 99);
|
|
assert(evaluatePathAnnounce(&path,
|
|
1,
|
|
stale_better_announce.data(),
|
|
200,
|
|
limits.path_ttl_ms) ==
|
|
PathAnnounceDecision::RejectStale);
|
|
const auto newer_worse_announce = announce_blob(0xC0, 101);
|
|
assert(evaluatePathAnnounce(&path,
|
|
3,
|
|
newer_worse_announce.data(),
|
|
200,
|
|
limits.path_ttl_ms) ==
|
|
PathAnnounceDecision::AcceptNewer);
|
|
applyPathAnnounce(path, 3, newer_worse_announce.data(), 200, 1235);
|
|
assert(path.hops == 3);
|
|
assert(path.announce_timebase == 101);
|
|
assert(evaluatePathAnnounce(&path,
|
|
1,
|
|
stale_better_announce.data(),
|
|
1201,
|
|
limits.path_ttl_ms) ==
|
|
PathAnnounceDecision::RejectStale);
|
|
assert(evaluatePathAnnounce(&path,
|
|
4,
|
|
stale_better_announce.data(),
|
|
1201,
|
|
limits.path_ttl_ms) ==
|
|
PathAnnounceDecision::AcceptExpired);
|
|
|
|
const auto packet_hash = filled_hash<reticulum::kFullHashSize>(0x20);
|
|
rememberPacket(transport, packet_hash.data(), 100, limits.max_packet_filter);
|
|
assert(same_hash(transport.packet_filter.front().packet_hash, packet_hash));
|
|
assert(isDuplicatePacket(transport, packet_hash.data()));
|
|
forgetPacket(transport, packet_hash.data());
|
|
assert(!isDuplicatePacket(transport, packet_hash.data()));
|
|
rememberPacket(transport, packet_hash.data(), 100, limits.max_packet_filter);
|
|
|
|
rememberReversePath(transport,
|
|
destination.data(),
|
|
7,
|
|
3,
|
|
200,
|
|
limits.max_reverse_entries);
|
|
ReverseEntry* reverse = findReversePath(transport, destination.data());
|
|
assert(reverse != nullptr);
|
|
assert(reverse->interface_id == 7);
|
|
assert(reverse->expected_hops == 3);
|
|
|
|
notePendingPathRequest(transport, destination.data(), 300, limits.max_pending_path_requests);
|
|
notePendingPathRequest(transport, destination.data(), 350, limits.max_pending_path_requests);
|
|
const PendingPathRequest* pending = findPendingPathRequest(transport, destination.data());
|
|
assert(pending != nullptr);
|
|
assert(pending->created_ms == 300);
|
|
assert(pending->last_attempt_ms == 350);
|
|
assert(pending->attempts == 2);
|
|
assert(!pending->resolved);
|
|
resolvePendingPathRequest(transport, destination.data());
|
|
assert(findPendingPathRequest(transport, destination.data()) == nullptr);
|
|
|
|
const auto peer_sig_pub =
|
|
filled_hash<chat::lxmf::LxmfIdentity::kSigPubKeySize>(0x55);
|
|
notePendingPingReceipt(transport,
|
|
packet_hash.data(),
|
|
destination.data(),
|
|
peer_sig_pub.data(),
|
|
350,
|
|
limits.max_pending_ping_receipts);
|
|
PendingPingReceipt* ping_receipt =
|
|
findPendingPingReceipt(transport, packet_hash.data());
|
|
assert(ping_receipt != nullptr);
|
|
assert(same_hash(ping_receipt->packet_hash, packet_hash));
|
|
assert(same_hash(ping_receipt->destination_hash, destination));
|
|
assert(same_hash(ping_receipt->peer_sig_pub, peer_sig_pub));
|
|
|
|
notePendingDeliveryReceipt(transport,
|
|
packet_hash.data(),
|
|
destination.data(),
|
|
peer_sig_pub.data(),
|
|
1234,
|
|
350,
|
|
limits.max_pending_delivery_receipts);
|
|
PendingDeliveryReceipt* delivery_receipt =
|
|
findPendingDeliveryReceipt(transport, packet_hash.data());
|
|
assert(delivery_receipt != nullptr);
|
|
assert(delivery_receipt->message_id == 1234);
|
|
assert(same_hash(delivery_receipt->packet_hash, packet_hash));
|
|
assert(same_hash(delivery_receipt->destination_hash, destination));
|
|
assert(same_hash(delivery_receipt->peer_sig_pub, peer_sig_pub));
|
|
|
|
LinkRelayEntry& relay = upsertLinkRelay(transport, destination.data(), limits.max_link_relays);
|
|
relay.initiator_hops = 1;
|
|
relay.responder_hops = 2;
|
|
relay.last_seen_ms = 400;
|
|
assert(transport.link_relays.front().responder_hops == 2);
|
|
assert(findLinkRelay(transport, destination.data()) == &transport.link_relays.front());
|
|
|
|
cullTransportRuntime(transport, 400 + limits.link_relay_ttl_ms + 1, limits);
|
|
assert(transport.packet_filter.empty());
|
|
assert(transport.reverse_table.empty());
|
|
assert(transport.pending_path_requests.empty());
|
|
assert(transport.pending_ping_receipts.empty());
|
|
assert(transport.pending_delivery_receipts.empty());
|
|
assert(transport.link_relays.empty());
|
|
assert(transport.paths.empty());
|
|
|
|
LinkRuntime links{};
|
|
assert(links.sessions.empty());
|
|
|
|
LinkSession session{};
|
|
copy_hash(session.link_id, destination);
|
|
copy_hash(session.remote_destination_hash, destination);
|
|
assert(session.destination == LocalDestinationKind::Delivery);
|
|
assert(session.state == LinkState::Pending);
|
|
assert(session.close_reason == LinkCloseReason::None);
|
|
assert(session.mtu == reticulum::kReticulumMtu);
|
|
assert(session.mdu == reticulum::kReticulumMdu);
|
|
assert(session.pending_requests.empty());
|
|
assert(session.deferred_payloads.empty());
|
|
assert(session.incoming_resources.empty());
|
|
assert(session.incoming_resource_assemblies.empty());
|
|
assert(session.outgoing_resources.empty());
|
|
|
|
LinkPendingRequest request{};
|
|
request.request_id = {0x01, 0x02, 0x03};
|
|
request.awaiting_resource = true;
|
|
session.pending_requests.push_back(request);
|
|
|
|
DeferredLinkPayload deferred{};
|
|
deferred.payload = {0xAA, 0xBB};
|
|
session.deferred_payloads.push_back(deferred);
|
|
|
|
LinkResourceTransfer resource{};
|
|
copy_hash(resource.resource_hash, packet_hash);
|
|
assert(resource.window_size == 4);
|
|
assert(resource.segment_index == 1);
|
|
assert(resource.total_segments == 1);
|
|
assert(resource.incoming);
|
|
assert(!resource.complete);
|
|
assert(!resource.waiting_for_proof);
|
|
session.incoming_resources.push_back(resource);
|
|
|
|
LinkResourceAssembly assembly{};
|
|
copy_hash(assembly.original_hash, packet_hash);
|
|
assembly.payload = {0x10, 0x11, 0x12};
|
|
session.incoming_resource_assemblies.push_back(assembly);
|
|
|
|
links.sessions.push_back(session);
|
|
assert(links.sessions.size() == 1);
|
|
assert(links.sessions.front().pending_requests.size() == 1);
|
|
assert(links.sessions.front().deferred_payloads.size() == 1);
|
|
assert(links.sessions.front().incoming_resources.size() == 1);
|
|
assert(links.sessions.front().incoming_resource_assemblies.size() == 1);
|
|
assert(findLinkSession(links, destination.data()) == &links.sessions.front());
|
|
assert(findActiveLinkSessionByDestination(links,
|
|
destination.data(),
|
|
LocalDestinationKind::Delivery) == nullptr);
|
|
|
|
const LinkRuntimeLimits link_limits{
|
|
2,
|
|
50,
|
|
100,
|
|
200,
|
|
10000,
|
|
100,
|
|
4,
|
|
5000};
|
|
const ResourceRuntimeLimits resource_limits{100};
|
|
|
|
links.sessions.front().state = LinkState::Active;
|
|
links.sessions.front().initiator = true;
|
|
links.sessions.front().last_inbound_ms = 100;
|
|
links.sessions.front().last_outbound_ms = 120;
|
|
links.sessions.front().keepalive_interval_ms = 50;
|
|
links.sessions.front().stale_timeout_ms = 200;
|
|
assert(findOpenLinkSessionByDestination(links,
|
|
destination.data(),
|
|
LocalDestinationKind::Delivery) ==
|
|
&links.sessions.front());
|
|
assert(findActiveLinkSessionByDestination(links,
|
|
destination.data(),
|
|
LocalDestinationKind::Delivery) ==
|
|
&links.sessions.front());
|
|
|
|
LinkRuntimeMaintenance maintenance =
|
|
advanceLinkSessionLifecycle(links.sessions.front(), 160, link_limits);
|
|
assert(maintenance.flush_deferred_payloads);
|
|
assert(maintenance.send_keepalive);
|
|
assert(!maintenance.close_timeout);
|
|
assert(!maintenance.marked_stale);
|
|
assert(links.sessions.front().state == LinkState::Active);
|
|
|
|
maintenance = advanceLinkSessionLifecycle(links.sessions.front(), 300, link_limits);
|
|
assert(maintenance.flush_deferred_payloads);
|
|
assert(maintenance.send_keepalive);
|
|
assert(!maintenance.close_timeout);
|
|
assert(maintenance.marked_stale);
|
|
assert(links.sessions.front().state == LinkState::Active);
|
|
markLinkSessionStale(links.sessions.front());
|
|
assert(links.sessions.front().state == LinkState::Stale);
|
|
|
|
maintenance = advanceLinkSessionLifecycle(links.sessions.front(), 401, link_limits);
|
|
assert(maintenance.close_timeout);
|
|
|
|
LinkSession& second = appendLinkSession(links, link_limits.max_link_sessions);
|
|
second.state = LinkState::Closed;
|
|
second.last_inbound_ms = 1;
|
|
second.last_outbound_ms = 1;
|
|
LinkSession& replacement = appendLinkSession(links, link_limits.max_link_sessions);
|
|
const auto replacement_link = filled_hash<reticulum::kTruncatedHashSize>(0x44);
|
|
copy_hash(replacement.link_id, replacement_link);
|
|
assert(links.sessions.size() == 2);
|
|
assert(findLinkSession(links, replacement_link.data()) == &links.sessions.back());
|
|
|
|
LinkSession closing{};
|
|
copy_hash(closing.link_id, destination);
|
|
copy_hash(closing.remote_destination_hash, destination);
|
|
closing.state = LinkState::Active;
|
|
closing.close_reason = LinkCloseReason::None;
|
|
closing.remote_identity_known = true;
|
|
closing.validated = true;
|
|
closing.propagation_offer_validated = true;
|
|
closing.rtt_s = 1.0f;
|
|
closing.last_keepalive_ms = 700;
|
|
std::memset(closing.local_enc_priv, 0xA1, sizeof(closing.local_enc_priv));
|
|
std::memset(closing.local_sig_priv, 0xA2, sizeof(closing.local_sig_priv));
|
|
std::memset(closing.derived_key, 0xA3, sizeof(closing.derived_key));
|
|
std::memset(closing.peer_enc_pub, 0xA4, sizeof(closing.peer_enc_pub));
|
|
std::memset(closing.peer_link_sig_pub, 0xA5, sizeof(closing.peer_link_sig_pub));
|
|
std::memset(closing.peer_identity_sig_pub, 0xA6, sizeof(closing.peer_identity_sig_pub));
|
|
closing.pending_requests.push_back(request);
|
|
closing.deferred_payloads.push_back(deferred);
|
|
closing.incoming_resources.push_back(resource);
|
|
closing.incoming_resource_assemblies.push_back(assembly);
|
|
closing.outgoing_resources.push_back(resource);
|
|
assert(closeLinkSession(closing, LinkCloseReason::Timeout, 900));
|
|
assert(closing.state == LinkState::Closed);
|
|
assert(closing.close_reason == LinkCloseReason::Timeout);
|
|
assert(closing.last_inbound_ms == 900);
|
|
assert(closing.last_outbound_ms == 900);
|
|
assert(closing.pending_requests.empty());
|
|
assert(closing.deferred_payloads.empty());
|
|
assert(closing.incoming_resources.empty());
|
|
assert(closing.incoming_resource_assemblies.empty());
|
|
assert(closing.outgoing_resources.empty());
|
|
assert(!closing.remote_identity_known);
|
|
assert(!closing.validated);
|
|
assert(!closing.propagation_offer_validated);
|
|
assert(closing.rtt_s == 0.0f);
|
|
assert(closing.last_keepalive_ms == 0);
|
|
assert(all_zero(closing.local_enc_priv));
|
|
assert(all_zero(closing.local_sig_priv));
|
|
assert(all_zero(closing.derived_key));
|
|
assert(all_zero(closing.peer_enc_pub));
|
|
assert(all_zero(closing.peer_link_sig_pub));
|
|
assert(all_zero(closing.peer_identity_sig_pub));
|
|
assert(!closeLinkSession(closing, LinkCloseReason::Error, 1000));
|
|
assert(closing.close_reason == LinkCloseReason::Timeout);
|
|
|
|
LinkSession culling{};
|
|
culling.pending_requests.push_back(request);
|
|
culling.pending_requests.back().created_ms = 1;
|
|
culling.incoming_resources.push_back(resource);
|
|
culling.incoming_resources.back().last_activity_ms = 1;
|
|
culling.outgoing_resources.push_back(resource);
|
|
culling.outgoing_resources.back().complete = true;
|
|
culling.incoming_resource_assemblies.push_back(assembly);
|
|
culling.incoming_resource_assemblies.back().last_activity_ms = 1;
|
|
cullLinkSessionTables(culling, 200, link_limits);
|
|
assert(culling.pending_requests.empty());
|
|
assert(culling.incoming_resources.size() == 1);
|
|
assert(culling.outgoing_resources.size() == 1);
|
|
assert(culling.incoming_resource_assemblies.size() == 1);
|
|
cullLinkResources(culling, 200, resource_limits);
|
|
assert(culling.incoming_resources.empty());
|
|
assert(culling.outgoing_resources.empty());
|
|
assert(culling.incoming_resource_assemblies.empty());
|
|
|
|
LinkRuntime removal{};
|
|
removal.sessions.push_back(closing);
|
|
removal.sessions.front().last_inbound_ms = 1;
|
|
removal.sessions.front().last_outbound_ms = 1;
|
|
removeExpiredLinkSessions(removal, 1 + link_limits.closed_retention_ms + 1, link_limits);
|
|
assert(removal.sessions.empty());
|
|
|
|
const auto original_hash = filled_hash<reticulum::kFullHashSize>(0x60);
|
|
const auto random_hash = filled_hash<kResourceMapHashLen>(0x70);
|
|
const std::array<uint8_t, kResourceMapHashLen> map_hash_0{0xA0, 0xA1, 0xA2, 0xA3};
|
|
const std::array<uint8_t, kResourceMapHashLen> map_hash_1{0xB0, 0xB1, 0xB2, 0xB3};
|
|
const std::array<uint8_t, kResourceMapHashLen> map_hash_2{0xC0, 0xC1, 0xC2, 0xC3};
|
|
std::vector<uint8_t> first_hashmap;
|
|
first_hashmap.insert(first_hashmap.end(), map_hash_0.begin(), map_hash_0.end());
|
|
first_hashmap.insert(first_hashmap.end(), map_hash_1.begin(), map_hash_1.end());
|
|
|
|
LinkResourceTransfer incoming_resource{};
|
|
assert(initialiseIncomingResourceTransfer(incoming_resource,
|
|
packet_hash.data(),
|
|
random_hash.data(),
|
|
original_hash.data(),
|
|
{0x31, 0x32},
|
|
std::move(first_hashmap),
|
|
6,
|
|
9,
|
|
3,
|
|
1,
|
|
1,
|
|
0x40,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
500,
|
|
2));
|
|
assert(same_hash(incoming_resource.resource_hash, packet_hash));
|
|
assert(same_hash(incoming_resource.original_hash, original_hash));
|
|
assert(incoming_resource.hashmap_height == 2);
|
|
assert(incoming_resource.parts.size() == 3);
|
|
assert(incoming_resource.received_bitmap.size() == 3);
|
|
assert(incoming_resource.map_hash_known[2] == 0);
|
|
assert(incoming_resource.waiting_for_hashmap);
|
|
|
|
ResourceWindowRequest window = buildNextResourceWindowRequest(incoming_resource);
|
|
assert(window.valid);
|
|
assert(!window.needs_more_hashmap);
|
|
assert(window.requested_hashes.size() == 2);
|
|
assert(window.requested_hashes[0] == map_hash_0);
|
|
assert(window.requested_hashes[1] == map_hash_1);
|
|
noteResourceWindowRequest(incoming_resource, window.needs_more_hashmap, 510);
|
|
assert(!incoming_resource.waiting_for_hashmap);
|
|
assert(incoming_resource.last_activity_ms == 510);
|
|
|
|
auto part_0_hash = filled_hash<reticulum::kFullHashSize>(0xA0);
|
|
for (std::size_t i = 0; i < map_hash_0.size(); ++i)
|
|
{
|
|
part_0_hash[i] = map_hash_0[i];
|
|
}
|
|
const uint8_t part_0[] = {0x01, 0x02};
|
|
std::size_t matched_index = 9;
|
|
bool resource_complete = true;
|
|
assert(recordResourcePart(incoming_resource,
|
|
part_0,
|
|
sizeof(part_0),
|
|
part_0_hash.data(),
|
|
520,
|
|
&matched_index,
|
|
&resource_complete));
|
|
assert(matched_index == 0);
|
|
assert(!resource_complete);
|
|
assert(incoming_resource.consecutive_complete_index == 0);
|
|
assert(incoming_resource.parts[0].size() == sizeof(part_0));
|
|
|
|
window = buildNextResourceWindowRequest(incoming_resource);
|
|
assert(window.valid);
|
|
assert(window.needs_more_hashmap);
|
|
assert(window.last_known_hash == map_hash_1);
|
|
assert(window.requested_hashes.size() == 1);
|
|
assert(window.requested_hashes[0] == map_hash_1);
|
|
noteResourceWindowRequest(incoming_resource, window.needs_more_hashmap, 530);
|
|
assert(incoming_resource.waiting_for_hashmap);
|
|
|
|
std::vector<uint8_t> second_hashmap(map_hash_2.begin(), map_hash_2.end());
|
|
assert(applyResourceHashmapUpdate(incoming_resource,
|
|
1,
|
|
second_hashmap,
|
|
2,
|
|
540));
|
|
assert(!incoming_resource.waiting_for_hashmap);
|
|
assert(incoming_resource.hashmap_height == 3);
|
|
assert(incoming_resource.map_hash_known[2] == 1);
|
|
|
|
window = buildNextResourceWindowRequest(incoming_resource);
|
|
assert(window.valid);
|
|
assert(!window.needs_more_hashmap);
|
|
assert(window.requested_hashes.size() == 2);
|
|
assert(window.requested_hashes[0] == map_hash_1);
|
|
assert(window.requested_hashes[1] == map_hash_2);
|
|
|
|
auto part_1_hash = filled_hash<reticulum::kFullHashSize>(0xB0);
|
|
auto part_2_hash = filled_hash<reticulum::kFullHashSize>(0xC0);
|
|
for (std::size_t i = 0; i < map_hash_1.size(); ++i)
|
|
{
|
|
part_1_hash[i] = map_hash_1[i];
|
|
part_2_hash[i] = map_hash_2[i];
|
|
}
|
|
const uint8_t part_1[] = {0x03, 0x04, 0x05};
|
|
const uint8_t part_2[] = {0x06};
|
|
resource_complete = true;
|
|
assert(recordResourcePart(incoming_resource,
|
|
part_1,
|
|
sizeof(part_1),
|
|
part_1_hash.data(),
|
|
550,
|
|
&matched_index,
|
|
&resource_complete));
|
|
assert(matched_index == 1);
|
|
assert(!resource_complete);
|
|
assert(recordResourcePart(incoming_resource,
|
|
part_2,
|
|
sizeof(part_2),
|
|
part_2_hash.data(),
|
|
560,
|
|
&matched_index,
|
|
&resource_complete));
|
|
assert(matched_index == 2);
|
|
assert(resource_complete);
|
|
markResourceComplete(incoming_resource, 570);
|
|
assert(incoming_resource.complete);
|
|
assert(incoming_resource.last_activity_ms == 570);
|
|
|
|
LinkSession resource_session{};
|
|
resource_session.incoming_resources.push_back(incoming_resource);
|
|
assert(findLinkResource(resource_session.incoming_resources, packet_hash.data()) ==
|
|
&resource_session.incoming_resources.front());
|
|
assert(eraseLinkResourceByHash(resource_session.incoming_resources, packet_hash.data()));
|
|
assert(!eraseLinkResourceByHash(resource_session.incoming_resources, packet_hash.data()));
|
|
|
|
LinkResourceTransfer split_segment_1{};
|
|
assert(initialiseIncomingResourceTransfer(split_segment_1,
|
|
packet_hash.data(),
|
|
random_hash.data(),
|
|
original_hash.data(),
|
|
{0x44},
|
|
std::vector<uint8_t>(map_hash_0.begin(),
|
|
map_hash_0.end()),
|
|
3,
|
|
3,
|
|
1,
|
|
1,
|
|
2,
|
|
0x80,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
600,
|
|
1));
|
|
ResourcePayloadBuffer split_payload_1{0x11, 0x12};
|
|
assert(appendResourceAssemblySegment(resource_session,
|
|
split_segment_1,
|
|
split_payload_1,
|
|
610) ==
|
|
ResourceAssemblyResult::WaitingForNextSegment);
|
|
assert(resource_session.incoming_resource_assemblies.size() == 1);
|
|
assert(split_segment_1.last_activity_ms == 0);
|
|
|
|
LinkResourceTransfer split_segment_2 = split_segment_1;
|
|
split_segment_2.segment_index = 2;
|
|
split_segment_2.last_activity_ms = 620;
|
|
ResourcePayloadBuffer split_payload_2{0x13};
|
|
assert(appendResourceAssemblySegment(resource_session,
|
|
split_segment_2,
|
|
split_payload_2,
|
|
630) ==
|
|
ResourceAssemblyResult::Complete);
|
|
assert(split_payload_2.size() == 3);
|
|
assert(split_payload_2[0] == 0x11);
|
|
assert(split_payload_2[2] == 0x13);
|
|
assert(resource_session.incoming_resource_assemblies.empty());
|
|
|
|
LinkResourceTransfer outgoing_resource{};
|
|
const uint8_t outgoing_request_id[] = {0x51, 0x52, 0x53};
|
|
assert(initialiseOutgoingResourceTransfer(outgoing_resource,
|
|
outgoing_request_id,
|
|
sizeof(outgoing_request_id),
|
|
12,
|
|
24,
|
|
2,
|
|
0x40,
|
|
700,
|
|
3));
|
|
assert(!outgoing_resource.incoming);
|
|
assert(outgoing_resource.encrypted);
|
|
assert(outgoing_resource.parts.size() == 2);
|
|
assert(outgoing_resource.map_hash_known.size() == 2);
|
|
assert(outgoing_resource.request_id.size() == sizeof(outgoing_request_id));
|
|
assert(!markResourceProofReceived(outgoing_resource, packet_hash.data(), 710));
|
|
copy_hash(outgoing_resource.expected_proof, packet_hash);
|
|
assert(markResourceProofReceived(outgoing_resource, packet_hash.data(), 720));
|
|
assert(outgoing_resource.complete);
|
|
|
|
PropagationRuntime propagation{};
|
|
assert(propagation.entries.empty());
|
|
assert(propagation.transients.empty());
|
|
assert(propagation.peers.empty());
|
|
|
|
const PropagationRuntimeLimits propagation_limits{
|
|
2,
|
|
2,
|
|
2,
|
|
10,
|
|
20,
|
|
10};
|
|
const auto propagation_hash = filled_hash<reticulum::kTruncatedHashSize>(0x80);
|
|
const auto delivery_hash = filled_hash<reticulum::kTruncatedHashSize>(0x90);
|
|
const auto identity_hash = filled_hash<reticulum::kTruncatedHashSize>(0xA0);
|
|
PropagationPeerState& propagation_peer =
|
|
upsertPropagationPeer(propagation,
|
|
propagation_hash.data(),
|
|
delivery_hash.data(),
|
|
identity_hash.data(),
|
|
propagation_limits.max_peers);
|
|
assert(same_hash(propagation_peer.propagation_hash, propagation_hash));
|
|
assert(same_hash(propagation_peer.delivery_hash, delivery_hash));
|
|
assert(same_hash(propagation_peer.identity_hash, identity_hash));
|
|
markPropagationPeerSeen(propagation_peer, 800);
|
|
notePropagationPeerIncomingMessage(propagation_peer);
|
|
notePropagationPeerServedMessages(propagation_peer, 3);
|
|
assert(propagation_peer.last_seen_s == 800);
|
|
assert(propagation_peer.incoming_messages == 1);
|
|
assert(propagation_peer.served_messages == 3);
|
|
assert(findPropagationPeer(propagation, propagation_hash.data()) == &propagation_peer);
|
|
|
|
const auto propagation_hash_2 = filled_hash<reticulum::kTruncatedHashSize>(0xB0);
|
|
const auto propagation_hash_3 = filled_hash<reticulum::kTruncatedHashSize>(0xC0);
|
|
(void)upsertPropagationPeer(propagation,
|
|
propagation_hash_2.data(),
|
|
nullptr,
|
|
nullptr,
|
|
propagation_limits.max_peers);
|
|
(void)upsertPropagationPeer(propagation,
|
|
propagation_hash_3.data(),
|
|
nullptr,
|
|
nullptr,
|
|
propagation_limits.max_peers);
|
|
assert(propagation.peers.size() == 2);
|
|
assert(findPropagationPeer(propagation, propagation_hash.data()) == nullptr);
|
|
assert(findPropagationPeer(propagation, propagation_hash_3.data()) != nullptr);
|
|
|
|
const auto transient_a = filled_hash<reticulum::kFullHashSize>(0xD0);
|
|
const auto transient_b = filled_hash<reticulum::kFullHashSize>(0xE0);
|
|
const auto transient_c = filled_hash<reticulum::kFullHashSize>(0xF0);
|
|
const uint8_t propagated_a[] = {0x90, 0x91, 0xA0, 0xA1};
|
|
const uint8_t propagated_b[] = {0x90, 0x92, 0xB0};
|
|
assert(rememberPropagationEntry(propagation,
|
|
transient_a.data(),
|
|
delivery_hash.data(),
|
|
propagated_a,
|
|
sizeof(propagated_a),
|
|
900,
|
|
propagation_limits.max_entries));
|
|
assert(findPropagationEntry(propagation, transient_a.data()) != nullptr);
|
|
assert(findPropagationEntry(propagation, transient_b.data()) == nullptr);
|
|
|
|
std::vector<std::vector<uint8_t>> offer_ids;
|
|
offer_ids.emplace_back(transient_a.begin(), transient_a.end());
|
|
offer_ids.emplace_back(transient_b.begin(), transient_b.end());
|
|
offer_ids.push_back({0x01, 0x02});
|
|
std::vector<std::vector<uint8_t>> missing_ids =
|
|
collectMissingPropagationTransientIds(propagation, offer_ids);
|
|
assert(missing_ids.size() == 1);
|
|
assert(missing_ids.front().size() == transient_b.size());
|
|
assert(std::memcmp(missing_ids.front().data(), transient_b.data(), transient_b.size()) == 0);
|
|
|
|
rememberPropagationTransient(propagation,
|
|
transient_b.data(),
|
|
false,
|
|
910,
|
|
propagation_limits.max_transients);
|
|
bool delivered = true;
|
|
assert(hasSeenPropagationTransient(propagation, transient_b.data(), &delivered));
|
|
assert(!delivered);
|
|
rememberPropagationTransient(propagation,
|
|
transient_b.data(),
|
|
true,
|
|
920,
|
|
propagation_limits.max_transients);
|
|
assert(hasSeenPropagationTransient(propagation, transient_b.data(), &delivered));
|
|
assert(delivered);
|
|
assert(propagation.transients.front().seen_s == 920);
|
|
missing_ids = collectMissingPropagationTransientIds(propagation, offer_ids);
|
|
assert(missing_ids.empty());
|
|
|
|
assert(rememberPropagationEntry(propagation,
|
|
transient_b.data(),
|
|
delivery_hash.data(),
|
|
propagated_b,
|
|
sizeof(propagated_b),
|
|
930,
|
|
propagation_limits.max_entries));
|
|
std::vector<std::vector<uint8_t>> destination_ids =
|
|
collectPropagationEntryIdsForDestination(propagation, delivery_hash.data());
|
|
assert(destination_ids.size() == 2);
|
|
|
|
std::vector<std::vector<uint8_t>> want_ids;
|
|
want_ids.emplace_back(transient_a.begin(), transient_a.end());
|
|
want_ids.emplace_back(transient_b.begin(), transient_b.end());
|
|
PropagationMessageSelection selection =
|
|
collectPropagationMessagesForWants(propagation,
|
|
want_ids,
|
|
delivery_hash.data(),
|
|
0,
|
|
24,
|
|
16);
|
|
assert(selection.messages.size() == 2);
|
|
assert(selection.served_count == 2);
|
|
assert(findPropagationEntry(propagation, transient_a.data())->served_count == 1);
|
|
assert(findPropagationEntry(propagation, transient_b.data())->served_count == 1);
|
|
|
|
selection = collectPropagationMessagesForWants(propagation,
|
|
want_ids,
|
|
delivery_hash.data(),
|
|
24 + sizeof(propagated_a) + 16,
|
|
24,
|
|
16);
|
|
assert(selection.messages.size() == 1);
|
|
assert(selection.served_count == 1);
|
|
|
|
assert(removePropagationEntriesForDestination(propagation,
|
|
transient_a.data(),
|
|
delivery_hash.data()) == 1);
|
|
assert(findPropagationEntry(propagation, transient_a.data()) == nullptr);
|
|
|
|
assert(rememberPropagationEntry(propagation,
|
|
transient_c.data(),
|
|
delivery_hash.data(),
|
|
propagated_a,
|
|
sizeof(propagated_a),
|
|
940,
|
|
propagation_limits.max_entries));
|
|
assert(propagation.entries.size() == 2);
|
|
|
|
PropagationRuntime stale_propagation{};
|
|
assert(rememberPropagationEntry(stale_propagation,
|
|
transient_a.data(),
|
|
delivery_hash.data(),
|
|
propagated_a,
|
|
sizeof(propagated_a),
|
|
100,
|
|
propagation_limits.max_entries));
|
|
rememberPropagationTransient(stale_propagation,
|
|
transient_b.data(),
|
|
false,
|
|
100,
|
|
propagation_limits.max_transients);
|
|
PropagationPeerState& stale_peer =
|
|
upsertPropagationPeer(stale_propagation,
|
|
propagation_hash.data(),
|
|
delivery_hash.data(),
|
|
identity_hash.data(),
|
|
propagation_limits.max_peers);
|
|
markPropagationPeerSeen(stale_peer, 100);
|
|
cullPropagationRuntime(stale_propagation, 119, propagation_limits);
|
|
assert(stale_propagation.entries.empty());
|
|
assert(stale_propagation.transients.size() == 1);
|
|
assert(stale_propagation.peers.empty());
|
|
cullPropagationRuntime(stale_propagation, 121, propagation_limits);
|
|
assert(stale_propagation.transients.empty());
|
|
|
|
PropagationRuntime message_propagation{};
|
|
PropagationPeerState& message_peer =
|
|
upsertPropagationPeer(message_propagation,
|
|
propagation_hash.data(),
|
|
delivery_hash.data(),
|
|
identity_hash.data(),
|
|
propagation_limits.max_peers);
|
|
markPropagationPeerSeen(message_peer, 1000);
|
|
|
|
PropagationMessageContext message_context{};
|
|
message_context.local_delivery_hash_known = true;
|
|
copy_hash(message_context.local_delivery_hash, delivery_hash);
|
|
message_context.remote_propagation_hash_known = true;
|
|
copy_hash(message_context.remote_propagation_hash, propagation_hash);
|
|
message_context.now_s = 1200;
|
|
message_context.max_entries = propagation_limits.max_entries;
|
|
message_context.max_transients = propagation_limits.max_transients;
|
|
|
|
const uint8_t short_propagated[] = {0x01, 0x02, 0x03};
|
|
PropagationMessageAcceptance message_acceptance{};
|
|
assert(!planPropagationMessageAcceptance(message_propagation,
|
|
short_propagated,
|
|
sizeof(short_propagated),
|
|
message_context,
|
|
&message_acceptance));
|
|
assert(message_acceptance.action == PropagationMessageAction::Rejected);
|
|
|
|
std::vector<uint8_t> local_propagated;
|
|
local_propagated.insert(local_propagated.end(),
|
|
delivery_hash.begin(),
|
|
delivery_hash.end());
|
|
local_propagated.push_back(0xA1);
|
|
local_propagated.push_back(0xA2);
|
|
local_propagated.push_back(0xA3);
|
|
assert(planPropagationMessageAcceptance(message_propagation,
|
|
local_propagated.data(),
|
|
local_propagated.size(),
|
|
message_context,
|
|
&message_acceptance));
|
|
assert(message_acceptance.action == PropagationMessageAction::DeliverLocal);
|
|
assert(same_hash(message_acceptance.destination_hash, delivery_hash));
|
|
assert(message_acceptance.local_delivery_payload.size() == 3);
|
|
assert(message_acceptance.local_delivery_payload[0] == 0xA1);
|
|
assert(!hasSeenPropagationTransient(message_propagation,
|
|
message_acceptance.transient_id,
|
|
nullptr));
|
|
notePropagationLocalDeliveryResult(message_propagation,
|
|
message_acceptance.transient_id,
|
|
true,
|
|
1201,
|
|
propagation_limits.max_transients);
|
|
assert(hasSeenPropagationTransient(message_propagation,
|
|
message_acceptance.transient_id,
|
|
&delivered));
|
|
assert(delivered);
|
|
assert(planPropagationMessageAcceptance(message_propagation,
|
|
local_propagated.data(),
|
|
local_propagated.size(),
|
|
message_context,
|
|
&message_acceptance));
|
|
assert(message_acceptance.action == PropagationMessageAction::Duplicate);
|
|
|
|
PropagationRuntime forwarded_propagation{};
|
|
PropagationPeerState& forwarded_peer =
|
|
upsertPropagationPeer(forwarded_propagation,
|
|
propagation_hash.data(),
|
|
delivery_hash.data(),
|
|
identity_hash.data(),
|
|
propagation_limits.max_peers);
|
|
markPropagationPeerSeen(forwarded_peer, 1000);
|
|
PropagationMessageContext forwarded_context = message_context;
|
|
forwarded_context.now_s = 1210;
|
|
|
|
const auto nonlocal_delivery_hash =
|
|
filled_hash<reticulum::kTruncatedHashSize>(0x55);
|
|
std::vector<uint8_t> nonlocal_propagated;
|
|
nonlocal_propagated.insert(nonlocal_propagated.end(),
|
|
nonlocal_delivery_hash.begin(),
|
|
nonlocal_delivery_hash.end());
|
|
nonlocal_propagated.push_back(0xB1);
|
|
nonlocal_propagated.push_back(0xB2);
|
|
assert(planPropagationMessageAcceptance(forwarded_propagation,
|
|
nonlocal_propagated.data(),
|
|
nonlocal_propagated.size(),
|
|
forwarded_context,
|
|
&message_acceptance));
|
|
assert(message_acceptance.action == PropagationMessageAction::Stored);
|
|
const PropagationEntry* stored_message =
|
|
findPropagationEntry(forwarded_propagation, message_acceptance.transient_id);
|
|
assert(stored_message != nullptr);
|
|
assert(same_hash(stored_message->destination_hash, nonlocal_delivery_hash));
|
|
assert(stored_message->lxmf_data.size() == nonlocal_propagated.size());
|
|
assert(hasSeenPropagationTransient(forwarded_propagation,
|
|
message_acceptance.transient_id,
|
|
&delivered));
|
|
assert(!delivered);
|
|
PropagationPeerState* stored_peer =
|
|
findPropagationPeer(forwarded_propagation, propagation_hash.data());
|
|
assert(stored_peer != nullptr);
|
|
assert(stored_peer->last_seen_s == 1210);
|
|
assert(planPropagationMessageAcceptance(forwarded_propagation,
|
|
nonlocal_propagated.data(),
|
|
nonlocal_propagated.size(),
|
|
forwarded_context,
|
|
&message_acceptance));
|
|
assert(message_acceptance.action == PropagationMessageAction::Duplicate);
|
|
|
|
PropagationRuntime batch_propagation{};
|
|
PropagationBatchContext batch_context{};
|
|
batch_context.local_delivery_hash_known = true;
|
|
copy_hash(batch_context.local_delivery_hash, delivery_hash);
|
|
batch_context.peer_context.remote_identity_known = true;
|
|
copy_hash(batch_context.peer_context.remote_propagation_hash, propagation_hash);
|
|
copy_hash(batch_context.peer_context.remote_delivery_hash, delivery_hash);
|
|
copy_hash(batch_context.peer_context.remote_identity_hash, identity_hash);
|
|
batch_context.now_s = 1300;
|
|
const PropagationBatchLimits batch_limits{
|
|
propagation_limits.max_entries,
|
|
propagation_limits.max_transients,
|
|
propagation_limits.max_peers,
|
|
1};
|
|
|
|
std::vector<std::vector<uint8_t>> batch_messages;
|
|
batch_messages.push_back(local_propagated);
|
|
batch_messages.push_back(nonlocal_propagated);
|
|
std::vector<uint8_t> encoded_batch(128, 0);
|
|
std::size_t encoded_batch_len = encoded_batch.size();
|
|
assert(::chat::lxmf::encodePropagationBatch(1300.5,
|
|
batch_messages,
|
|
encoded_batch.data(),
|
|
&encoded_batch_len));
|
|
encoded_batch.resize(encoded_batch_len);
|
|
|
|
PropagationBatchAcceptance batch_acceptance{};
|
|
assert(!planPropagationBatchAcceptance(batch_propagation,
|
|
encoded_batch.data(),
|
|
encoded_batch.size(),
|
|
batch_context,
|
|
batch_limits,
|
|
&batch_acceptance));
|
|
assert(batch_acceptance.messages.empty());
|
|
|
|
batch_context.offer_validated = true;
|
|
assert(planPropagationBatchAcceptance(batch_propagation,
|
|
encoded_batch.data(),
|
|
encoded_batch.size(),
|
|
batch_context,
|
|
batch_limits,
|
|
&batch_acceptance));
|
|
assert(batch_acceptance.remote_propagation_hash_known);
|
|
assert(same_hash(batch_acceptance.remote_propagation_hash, propagation_hash));
|
|
assert(batch_acceptance.messages.size() == 2);
|
|
assert(batch_acceptance.messages[0].action ==
|
|
PropagationMessageAction::DeliverLocal);
|
|
assert(batch_acceptance.messages[1].action == PropagationMessageAction::Stored);
|
|
PropagationPeerState* batch_peer =
|
|
findPropagationPeer(batch_propagation, propagation_hash.data());
|
|
assert(batch_peer != nullptr);
|
|
assert(batch_peer->last_seen_s == 1300);
|
|
assert(findPropagationEntry(batch_propagation,
|
|
batch_acceptance.messages[1].transient_id) != nullptr);
|
|
notePropagationLocalDeliveryResult(batch_propagation,
|
|
batch_acceptance.messages[0].transient_id,
|
|
true,
|
|
1301,
|
|
propagation_limits.max_transients);
|
|
notePropagationBatchMessageHandled(batch_propagation, batch_acceptance);
|
|
notePropagationBatchMessageHandled(batch_propagation, batch_acceptance);
|
|
assert(batch_peer->incoming_messages == 2);
|
|
|
|
PropagationRuntime service_propagation{};
|
|
PropagationServicePeerContext service_peer{};
|
|
service_peer.remote_identity_known = true;
|
|
copy_hash(service_peer.remote_propagation_hash, propagation_hash);
|
|
copy_hash(service_peer.remote_delivery_hash, delivery_hash);
|
|
copy_hash(service_peer.remote_identity_hash, identity_hash);
|
|
const PropagationServiceLimits service_limits{
|
|
propagation_limits.max_transients,
|
|
propagation_limits.max_peers,
|
|
24,
|
|
16};
|
|
|
|
std::vector<std::vector<uint8_t>> service_offer_ids;
|
|
service_offer_ids.emplace_back(transient_a.begin(), transient_a.end());
|
|
std::vector<uint8_t> encoded_offer_ids(64, 0);
|
|
std::size_t encoded_offer_ids_len = encoded_offer_ids.size();
|
|
assert(::chat::lxmf::encodePropagationIdListPayload(service_offer_ids,
|
|
encoded_offer_ids.data(),
|
|
&encoded_offer_ids_len));
|
|
encoded_offer_ids.resize(encoded_offer_ids_len);
|
|
|
|
std::vector<uint8_t> offer_payload;
|
|
offer_payload.push_back(0x92);
|
|
offer_payload.push_back(0xC4);
|
|
offer_payload.push_back(0x01);
|
|
offer_payload.push_back(0xA5);
|
|
offer_payload.insert(offer_payload.end(),
|
|
encoded_offer_ids.begin(),
|
|
encoded_offer_ids.end());
|
|
|
|
::chat::lxmf::DecodedLinkRequest service_request{};
|
|
propagationServicePathHash(PropagationServicePath::Offer,
|
|
service_request.path_hash);
|
|
service_request.packed_data = offer_payload;
|
|
PropagationServiceResponse service_response{};
|
|
assert(planPropagationServiceResponse(service_propagation,
|
|
service_request,
|
|
service_peer,
|
|
1100,
|
|
service_limits,
|
|
&service_response));
|
|
assert(service_response.send_response);
|
|
assert(service_response.offer_validated);
|
|
assert(!service_response.response_data_is_nil);
|
|
assert(service_response.packed_response.size() == 1);
|
|
assert(service_response.packed_response[0] == 0xC3);
|
|
PropagationPeerState* service_peer_state =
|
|
findPropagationPeer(service_propagation, propagation_hash.data());
|
|
assert(service_peer_state != nullptr);
|
|
assert(service_peer_state->last_seen_s == 1100);
|
|
|
|
assert(rememberPropagationEntry(service_propagation,
|
|
transient_a.data(),
|
|
delivery_hash.data(),
|
|
propagated_a,
|
|
sizeof(propagated_a),
|
|
1110,
|
|
propagation_limits.max_entries));
|
|
|
|
::chat::lxmf::DecodedLinkRequest get_list_request{};
|
|
propagationServicePathHash(PropagationServicePath::Get,
|
|
get_list_request.path_hash);
|
|
get_list_request.packed_data = {0x92, 0xC0, 0xC0};
|
|
service_response = {};
|
|
assert(planPropagationServiceResponse(service_propagation,
|
|
get_list_request,
|
|
service_peer,
|
|
1120,
|
|
service_limits,
|
|
&service_response));
|
|
assert(service_response.send_response);
|
|
assert(service_response.packed_response.size() ==
|
|
3 + reticulum::kFullHashSize);
|
|
assert(service_response.packed_response[0] == 0x91);
|
|
assert(service_response.packed_response[1] == 0xC4);
|
|
assert(service_response.packed_response[2] == reticulum::kFullHashSize);
|
|
assert(std::memcmp(service_response.packed_response.data() + 3,
|
|
transient_a.data(),
|
|
reticulum::kFullHashSize) == 0);
|
|
|
|
std::vector<uint8_t> encoded_wants(64, 0);
|
|
std::size_t encoded_wants_len = encoded_wants.size();
|
|
assert(::chat::lxmf::encodePropagationIdListPayload(service_offer_ids,
|
|
encoded_wants.data(),
|
|
&encoded_wants_len));
|
|
encoded_wants.resize(encoded_wants_len);
|
|
::chat::lxmf::DecodedLinkRequest get_messages_request{};
|
|
propagationServicePathHash(PropagationServicePath::Get,
|
|
get_messages_request.path_hash);
|
|
get_messages_request.packed_data.push_back(0x93);
|
|
get_messages_request.packed_data.insert(get_messages_request.packed_data.end(),
|
|
encoded_wants.begin(),
|
|
encoded_wants.end());
|
|
get_messages_request.packed_data.push_back(0xC0);
|
|
get_messages_request.packed_data.push_back(0x01);
|
|
service_response = {};
|
|
assert(planPropagationServiceResponse(service_propagation,
|
|
get_messages_request,
|
|
service_peer,
|
|
1130,
|
|
service_limits,
|
|
&service_response));
|
|
assert(service_response.send_response);
|
|
assert(service_response.packed_response.size() == 3 + sizeof(propagated_a));
|
|
assert(service_response.packed_response[0] == 0x91);
|
|
assert(service_response.packed_response[1] == 0xC4);
|
|
assert(service_response.packed_response[2] == sizeof(propagated_a));
|
|
assert(std::memcmp(service_response.packed_response.data() + 3,
|
|
propagated_a,
|
|
sizeof(propagated_a)) == 0);
|
|
service_peer_state = findPropagationPeer(service_propagation,
|
|
propagation_hash.data());
|
|
assert(service_peer_state != nullptr);
|
|
assert(service_peer_state->served_messages == 1);
|
|
|
|
LxmfDeliveryContext delivery_context{};
|
|
delivery_context.peer_node_id = 0x01020304;
|
|
delivery_context.local_node_id = 0x11121314;
|
|
delivery_context.message_id = 0xAABBCCDD;
|
|
delivery_context.has_message_hash = true;
|
|
for (std::size_t index = 0; index < sizeof(delivery_context.message_hash); ++index)
|
|
{
|
|
delivery_context.message_hash[index] = static_cast<uint8_t>(0x80U + index);
|
|
}
|
|
delivery_context.timestamp_s = 1234567890;
|
|
delivery_context.peer_identity =
|
|
::chat::makeReticulumPeerIdentity(delivery_hash.data(), identity_hash.data());
|
|
delivery_context.rx_meta.origin = ::chat::RxOrigin::Mesh;
|
|
delivery_context.rx_meta.direct = true;
|
|
delivery_context.rx_meta.rssi_dbm_x10 = -710;
|
|
|
|
::chat::lxmf::DecodedTextPayload text_payload{};
|
|
text_payload.content = "verified reticulum delivery";
|
|
uint8_t encoded_text[128] = {};
|
|
std::size_t encoded_text_len = sizeof(encoded_text);
|
|
assert(::chat::lxmf::encodeTextPayload(1.0,
|
|
"",
|
|
text_payload.content.c_str(),
|
|
encoded_text,
|
|
&encoded_text_len));
|
|
LxmfVerifiedDelivery verified_text{};
|
|
assert(materialiseVerifiedLxmfDelivery(encoded_text,
|
|
encoded_text_len,
|
|
delivery_context,
|
|
&verified_text));
|
|
assert(verified_text.kind == LxmfDeliveryKind::Text);
|
|
const LxmfMaterialisedText& text_delivery = verified_text.text;
|
|
assert(text_delivery.text == text_payload.content);
|
|
assert(text_delivery.incoming.channel == ::chat::ChannelId::PRIMARY);
|
|
assert(text_delivery.incoming.from == delivery_context.peer_node_id);
|
|
assert(text_delivery.incoming.to == delivery_context.local_node_id);
|
|
assert(text_delivery.incoming.msg_id == delivery_context.message_id);
|
|
assert(text_delivery.incoming.has_reticulum_lxmf_hash);
|
|
assert(std::memcmp(text_delivery.incoming.reticulum_lxmf_hash,
|
|
delivery_context.message_hash,
|
|
sizeof(delivery_context.message_hash)) == 0);
|
|
assert(text_delivery.incoming.timestamp == delivery_context.timestamp_s);
|
|
assert(text_delivery.incoming.hop_limit == 0xFF);
|
|
assert(text_delivery.incoming.encrypted);
|
|
assert(!text_delivery.incoming.source_unverified);
|
|
assert(text_delivery.incoming.text == text_payload.content);
|
|
assert(::chat::sameReticulumPeerIdentity(text_delivery.incoming.reticulum_identity,
|
|
delivery_context.peer_identity));
|
|
assert(text_delivery.incoming.rx_meta.rssi_dbm_x10 == -710);
|
|
|
|
::chat::infra::IncomingTextQueue<1, 64> text_queue{};
|
|
::chat::infra::IncomingQueuePushReport text_report{};
|
|
assert(text_queue.push(text_delivery.incoming,
|
|
text_delivery.text.data(),
|
|
text_delivery.text.size(),
|
|
::chat::infra::IncomingQueuePriority::P1User,
|
|
&text_report));
|
|
::chat::MeshIncomingText popped_text{};
|
|
assert(text_queue.pop(&popped_text));
|
|
assert(popped_text.text == text_payload.content);
|
|
assert(popped_text.has_reticulum_lxmf_hash);
|
|
assert(std::memcmp(popped_text.reticulum_lxmf_hash,
|
|
delivery_context.message_hash,
|
|
sizeof(delivery_context.message_hash)) == 0);
|
|
assert(!popped_text.source_unverified);
|
|
assert(::chat::sameReticulumPeerIdentity(popped_text.reticulum_identity,
|
|
delivery_context.peer_identity));
|
|
assert(popped_text.rx_meta.rssi_dbm_x10 == -710);
|
|
|
|
LxmfDeliveryContext unverified_context = delivery_context;
|
|
unverified_context.source_unverified = true;
|
|
unverified_context.peer_identity =
|
|
::chat::makeReticulumDestinationIdentity(delivery_hash.data());
|
|
LxmfMaterialisedText unverified_text{};
|
|
assert(materialiseLxmfTextDelivery(text_payload,
|
|
unverified_context,
|
|
&unverified_text));
|
|
assert(unverified_text.incoming.source_unverified);
|
|
::chat::infra::IncomingTextQueue<1, 64> unverified_text_queue{};
|
|
assert(unverified_text_queue.push(unverified_text.incoming,
|
|
unverified_text.text.data(),
|
|
unverified_text.text.size(),
|
|
::chat::infra::IncomingQueuePriority::P0Critical));
|
|
assert(unverified_text_queue.pop(&popped_text));
|
|
assert(popped_text.source_unverified);
|
|
assert(::chat::sameReticulumPeerIdentity(
|
|
popped_text.reticulum_identity,
|
|
unverified_context.peer_identity));
|
|
|
|
::chat::infra::IncomingTextQueue<1, 64> accepted_text_queue{};
|
|
assert(accepted_text_queue.push(text_delivery.incoming,
|
|
text_delivery.text.data(),
|
|
text_delivery.text.size(),
|
|
::chat::infra::IncomingQueuePriority::P0Critical,
|
|
&text_report));
|
|
assert(!accepted_text_queue.push(text_delivery.incoming,
|
|
text_delivery.text.data(),
|
|
text_delivery.text.size(),
|
|
::chat::infra::IncomingQueuePriority::P0Critical,
|
|
&text_report));
|
|
assert(text_report.dropped_new);
|
|
assert(!text_report.dropped_existing);
|
|
assert(accepted_text_queue.pop(&popped_text));
|
|
assert(popped_text.text == text_payload.content);
|
|
|
|
::chat::lxmf::DecodedAppData app_payload{};
|
|
app_payload.portnum = 77;
|
|
app_payload.packet_id = 0x0102;
|
|
app_payload.request_id = 0x03040506;
|
|
app_payload.want_response = true;
|
|
app_payload.payload = {0x21, 0x22, 0x23};
|
|
uint8_t encoded_app_data[64] = {};
|
|
std::size_t encoded_app_data_len = sizeof(encoded_app_data);
|
|
assert(::chat::lxmf::encodeAppDataPayload(app_payload.portnum,
|
|
app_payload.packet_id,
|
|
app_payload.request_id,
|
|
app_payload.want_response,
|
|
app_payload.payload.data(),
|
|
app_payload.payload.size(),
|
|
encoded_app_data,
|
|
&encoded_app_data_len));
|
|
LxmfVerifiedDelivery verified_app_data{};
|
|
assert(materialiseVerifiedLxmfDelivery(encoded_app_data,
|
|
encoded_app_data_len,
|
|
delivery_context,
|
|
&verified_app_data));
|
|
assert(verified_app_data.kind == LxmfDeliveryKind::AppData);
|
|
const LxmfMaterialisedAppData& app_delivery = verified_app_data.app_data;
|
|
assert(app_delivery.incoming.portnum == app_payload.portnum);
|
|
assert(app_delivery.incoming.from == delivery_context.peer_node_id);
|
|
assert(app_delivery.incoming.to == delivery_context.local_node_id);
|
|
assert(app_delivery.incoming.packet_id == app_payload.packet_id);
|
|
assert(app_delivery.incoming.request_id == app_payload.request_id);
|
|
assert(app_delivery.incoming.channel == ::chat::ChannelId::PRIMARY);
|
|
assert(app_delivery.incoming.want_response);
|
|
assert(app_delivery.incoming.rx_meta.rssi_dbm_x10 == -710);
|
|
assert(app_delivery.payload == app_payload.payload);
|
|
|
|
const uint32_t team_location_ports[] = {
|
|
team::proto::TEAM_POSITION_APP,
|
|
team::proto::TEAM_TRACK_APP,
|
|
};
|
|
for (uint32_t portnum : team_location_ports)
|
|
{
|
|
::chat::lxmf::DecodedAppData team_payload{};
|
|
team_payload.portnum = portnum;
|
|
team_payload.packet_id = 0x11223344;
|
|
team_payload.payload = {0x31, 0x32, 0x33, 0x34};
|
|
uint8_t encoded_team_app_data[64] = {};
|
|
std::size_t encoded_team_app_data_len = sizeof(encoded_team_app_data);
|
|
assert(::chat::lxmf::encodeAppDataPayload(team_payload.portnum,
|
|
team_payload.packet_id,
|
|
team_payload.request_id,
|
|
team_payload.want_response,
|
|
team_payload.payload.data(),
|
|
team_payload.payload.size(),
|
|
encoded_team_app_data,
|
|
&encoded_team_app_data_len));
|
|
LxmfVerifiedDelivery verified_team_app_data{};
|
|
assert(materialiseVerifiedLxmfDelivery(encoded_team_app_data,
|
|
encoded_team_app_data_len,
|
|
delivery_context,
|
|
&verified_team_app_data));
|
|
assert(verified_team_app_data.kind == LxmfDeliveryKind::AppData);
|
|
const LxmfMaterialisedAppData& team_delivery =
|
|
verified_team_app_data.app_data;
|
|
assert(team_delivery.incoming.portnum == team_payload.portnum);
|
|
assert(team_delivery.incoming.from == delivery_context.peer_node_id);
|
|
assert(team_delivery.incoming.to == delivery_context.local_node_id);
|
|
assert(team_delivery.incoming.packet_id == team_payload.packet_id);
|
|
assert(team_delivery.incoming.channel == ::chat::ChannelId::PRIMARY);
|
|
assert(!team_delivery.incoming.want_response);
|
|
assert(team_delivery.payload == team_payload.payload);
|
|
}
|
|
|
|
const uint8_t invalid_delivery_payload[] = {0x01, 0x02, 0x03};
|
|
LxmfVerifiedDelivery invalid_delivery{};
|
|
assert(!materialiseVerifiedLxmfDelivery(invalid_delivery_payload,
|
|
sizeof(invalid_delivery_payload),
|
|
delivery_context,
|
|
&invalid_delivery));
|
|
assert(invalid_delivery.kind == LxmfDeliveryKind::None);
|
|
|
|
return 0;
|
|
}
|