171 lines
7.6 KiB
C++
171 lines
7.6 KiB
C++
// Execute production cache reads, packet restoration, pool reuse and sharing.
|
|
// Only storage peripherals and the final radio queue boundary are isolated.
|
|
#include "FakeFilesystem.h"
|
|
#include <Packet.h>
|
|
#include <helpers/StaticPoolPacketManager.h>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
|
|
#define FILESYSTEM FakeFilesystem
|
|
using mesh::FLOOD_RETRY_POLICY_DEFAULT;
|
|
using mesh::RADIO_TX_AUTO;
|
|
|
|
class DataStore {
|
|
public:
|
|
FakeFilesystem* _fs;
|
|
#include "advert_state.h"
|
|
explicit DataStore(FakeFilesystem& fs) : _fs(&fs) {}
|
|
File openRead(FakeFilesystem* fs, const char* path) { return fs->open(path); }
|
|
uint8_t getBlobByKey(const uint8_t[], int, uint8_t[]);
|
|
bool putBlobByKey(const uint8_t[], int, const uint8_t[], uint8_t);
|
|
bool queueAdvertByKey(const uint8_t[], int, const uint8_t[], uint8_t);
|
|
};
|
|
#include "share_store.h"
|
|
|
|
struct ContactInfo {
|
|
struct { uint8_t pub_key[PUB_KEY_SIZE] = {}; } id;
|
|
};
|
|
|
|
class BaseChatMesh {
|
|
public:
|
|
DataStore& store;
|
|
StaticPoolPacketManager* _mgr;
|
|
unsigned _err_flags = 0;
|
|
bool receive_context_active = false;
|
|
uint8_t receive_context_profile = 0;
|
|
uint32_t receive_context_generation = 0;
|
|
uint8_t temp_buf[MAX_TRANS_UNIT] = {};
|
|
unsigned released = 0, sent = 0;
|
|
mesh::Packet last;
|
|
struct Tables {
|
|
unsigned marked = 0;
|
|
void markSent(const mesh::Packet*) { ++marked; }
|
|
} table, *_tables = &table;
|
|
BaseChatMesh(DataStore& s, StaticPoolPacketManager& p) : store(s), _mgr(&p) {}
|
|
int getBlobByKey(const uint8_t* key, int length, uint8_t* bytes) {
|
|
return store.getBlobByKey(key, length, bytes);
|
|
}
|
|
mesh::Packet* obtainNewPacket();
|
|
void releasePacket(mesh::Packet* packet) { ++released; _mgr->free(packet); }
|
|
bool shareContactZeroHop(const ContactInfo&);
|
|
void sendZeroHop(mesh::Packet*, uint16_t*, uint32_t = 0);
|
|
bool sendPacket(mesh::Packet* packet, uint8_t, uint32_t) {
|
|
// The actual Dispatcher shape guards still apply at this isolated final
|
|
// boundary; no radio, crypto verification or RF delivery is simulated.
|
|
const bool accepted = !packet->violatesRoutePolicy()
|
|
&& mesh::Packet::isValidPathLen(packet->path_len)
|
|
&& packet->payload_len <= MAX_PACKET_PAYLOAD;
|
|
if (accepted) { ++sent; last = *packet; }
|
|
releasePacket(packet);
|
|
return accepted;
|
|
}
|
|
};
|
|
#include "share_methods.h"
|
|
|
|
static void require(bool condition, const char* message) {
|
|
if (condition) return;
|
|
fprintf(stderr, "CHECK: %s\n", message);
|
|
abort();
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
require(argc == 2, "one share scenario is required");
|
|
const std::string scenario = argv[1];
|
|
FakeFilesystem fs;
|
|
DataStore store(fs);
|
|
StaticPoolPacketManager pool(1);
|
|
BaseChatMesh chat(store, pool);
|
|
ContactInfo contact;
|
|
contact.id.pub_key[0] = 0x42;
|
|
mesh::Packet advert;
|
|
advert.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_ADVERT << PH_TYPE_SHIFT);
|
|
advert.setPathHashSizeAndCount(1, 2);
|
|
advert.path[0] = 0x12;
|
|
advert.path[1] = 0x34;
|
|
if (scenario == "short3") advert.path_len = 0;
|
|
const size_t fixed = PUB_KEY_SIZE + 4 + SIGNATURE_SIZE;
|
|
const char name[] = "Probe node";
|
|
advert.payload_len = fixed + sizeof(name);
|
|
memset(advert.payload, 0xA5, advert.payload_len);
|
|
memcpy(advert.payload, contact.id.pub_key, PUB_KEY_SIZE);
|
|
advert.payload[fixed] = 0x81; // ADV_TYPE_CHAT | ADV_NAME_MASK.
|
|
memcpy(advert.payload + fixed + 1, name, sizeof(name) - 1);
|
|
std::vector<uint8_t> expected_payload(advert.payload, advert.payload + advert.payload_len);
|
|
uint8_t bytes[MAX_TRANS_UNIT];
|
|
const auto length = advert.writeTo(bytes);
|
|
require(store.putBlobByKey(contact.id.pub_key, PUB_KEY_SIZE, bytes, length),
|
|
"valid cache preparation failed");
|
|
char path[64];
|
|
makeBlobPath(contact.id.pub_key, PUB_KEY_SIZE, path, sizeof(path));
|
|
bool expected = scenario == "valid" || scenario == "valid-reused"
|
|
|| scenario == "valid-min" || scenario == "valid-max" || scenario == "ram-valid";
|
|
bool allocation_failed = false;
|
|
mesh::Packet* held = nullptr;
|
|
|
|
if (scenario == "short1") fs.read_limit = 1;
|
|
else if (scenario == "short3") fs.read_limit = 3;
|
|
else if (scenario == "truncated-path") fs.files.at(path).resize(3);
|
|
else if (scenario == "reserved-path") fs.files.at(path)[1] = 0xC0;
|
|
else if (scenario == "bad-version") fs.files.at(path)[0] |= 0x40;
|
|
else if (scenario == "truncated-transport") {
|
|
fs.files.at(path)[0] = ROUTE_TYPE_TRANSPORT_FLOOD | (PAYLOAD_TYPE_ADVERT << PH_TYPE_SHIFT);
|
|
fs.files.at(path).resize(3);
|
|
} else if (scenario == "wrong-type") {
|
|
fs.files.at(path)[0] = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_GRP_TXT << PH_TYPE_SHIFT);
|
|
} else if (scenario == "short-advert") fs.files.at(path).resize(4 + fixed - 1);
|
|
else if (scenario == "oversize-advert") fs.files.at(path).resize(4 + fixed + MAX_ADVERT_DATA_SIZE + 1);
|
|
else if (scenario == "wrong-key") fs.files.at(path)[4 + PUB_KEY_SIZE - 1] ^= 0x80;
|
|
else if (scenario == "empty-cache") fs.files.at(path).clear();
|
|
else if (scenario == "missing-cache") fs.files.erase(path);
|
|
else if (scenario == "no-allocation") { held = pool.allocNew(); allocation_failed = true; }
|
|
else if (scenario == "valid-min") {
|
|
fs.files.at(path).resize(4 + fixed);
|
|
expected_payload.resize(fixed);
|
|
} else if (scenario == "valid-max") {
|
|
fs.files.at(path).resize(4 + fixed + MAX_ADVERT_DATA_SIZE);
|
|
expected_payload.resize(fixed + MAX_ADVERT_DATA_SIZE);
|
|
}
|
|
else if (scenario == "ram-valid" || scenario == "ram-wrong-key") {
|
|
ContactInfo cached = contact;
|
|
if (scenario == "ram-wrong-key") bytes[4 + PUB_KEY_SIZE - 1] ^= 0x80;
|
|
require(store.queueAdvertByKey(cached.id.pub_key, PUB_KEY_SIZE, bytes, length),
|
|
"RAM advert preparation failed");
|
|
fs.files.erase(path);
|
|
} else require(scenario == "valid" || scenario == "valid-reused", "unknown share scenario");
|
|
|
|
if (scenario == "valid-reused" || scenario == "short1") {
|
|
// Actual pool reuse retains the prior header. obtainNewPacket resets
|
|
// lengths, so ignoring readFrom's early failure can send an empty ACK.
|
|
auto* previous = pool.allocNew();
|
|
previous->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT);
|
|
pool.free(previous);
|
|
}
|
|
|
|
const bool result = chat.shareContactZeroHop(contact);
|
|
if (expected) {
|
|
require(result && chat.sent == 1, "valid cached advert was not shared");
|
|
require(chat.last.getPayloadType() == PAYLOAD_TYPE_ADVERT,
|
|
"shared packet type changed");
|
|
require(chat.last.getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT && chat.last.path_len == 0,
|
|
"valid share must remain zero-hop transport-direct");
|
|
require(chat.last.transport_codes[0] == 0 && chat.last.transport_codes[1] == 0,
|
|
"valid share must retain zero transport codes");
|
|
require(!memcmp(chat.last.payload, contact.id.pub_key, PUB_KEY_SIZE),
|
|
"valid share identity changed");
|
|
require(chat.last.payload_len == expected_payload.size()
|
|
&& !memcmp(chat.last.payload, expected_payload.data(), expected_payload.size()),
|
|
"valid share must preserve every cached payload byte");
|
|
require(chat.table.marked == 1 && chat.released == 1,
|
|
"valid share must mark sent once and retire once");
|
|
} else {
|
|
require(!result && chat.sent == 0, "invalid cached advert must fail without transmission");
|
|
require(chat.table.marked == 0, "rejected advert must not be marked sent");
|
|
const unsigned releases = scenario == "empty-cache" || scenario == "missing-cache"
|
|
|| allocation_failed ? 0 : 1;
|
|
require(chat.released == releases, "failed share must release exactly its owned packet");
|
|
}
|
|
if (held) pool.free(held);
|
|
require(pool.getFreeCount() == 1, "share leaked or double-freed its packet");
|
|
printf("PASS: cached advert share %s\n", scenario.c_str());
|
|
}
|