Browse Source

Test advert parsing via mesh receive path

pull/2459/head
Michael Lynch 2 months ago
parent
commit
c736435d70
  1. 6
      platformio.ini
  2. 10
      test/mocks/Arduino.h
  3. 18
      test/mocks/Ed25519.h
  4. 40
      test/mocks/SHA256.h
  5. 11
      test/mocks/Stream.h
  6. 551
      test/test_utils/test_advert_data.cpp

6
platformio.ini

@ -163,7 +163,13 @@ build_flags = -std=c++17
test_build_src = yes
build_src_filter =
-<*>
+<../src/Dispatcher.cpp>
+<../src/Identity.cpp>
+<../src/Mesh.cpp>
+<../src/Packet.cpp>
+<../src/Utils.cpp>
+<../src/helpers/AdvertDataHelpers.cpp>
+<../src/helpers/BaseChatMesh.cpp>
+<../src/helpers/TxtDataHelpers.cpp>
lib_deps =
google/googletest @ 1.17.0

10
test/mocks/Arduino.h

@ -1,6 +1,16 @@
#pragma once
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
inline char* ltoa(long value, char* buffer, int base) {
if (base == 10) {
snprintf(buffer, 32, "%ld", value);
} else {
buffer[0] = 0;
}
return buffer;
}

18
test/mocks/Ed25519.h

@ -0,0 +1,18 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <ed_25519.h>
#ifdef __cplusplus
}
#endif
#include <stdint.h>
class Ed25519 {
public:
static bool verify(const uint8_t* sig, const uint8_t* pub_key, const uint8_t* message, int msg_len) {
return ed25519_verify(sig, message, msg_len, pub_key) != 0;
}
};

40
test/mocks/SHA256.h

@ -2,13 +2,41 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
// Mock SHA256 class for testing
// Provides minimal interface to allow Utils.cpp to compile
class SHA256 {
uint32_t state_ = 2166136261u;
public:
void update(const uint8_t* data, size_t len) {}
void finalize(uint8_t* hash, size_t hashLen) {}
void resetHMAC(const uint8_t* key, size_t keyLen) {}
void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {}
void update(const void* data, size_t len) {
update(static_cast<const uint8_t*>(data), len);
}
void update(const uint8_t* data, size_t len) {
for (size_t i = 0; i < len; ++i) {
state_ ^= data[i];
state_ *= 16777619u;
state_ += 0x9E3779B9u;
}
}
void finalize(uint8_t* hash, size_t hashLen) {
uint32_t value = state_;
for (size_t i = 0; i < hashLen; ++i) {
value ^= value >> 13;
value *= 1274126177u;
hash[i] = static_cast<uint8_t>((value >> ((i & 3) * 8)) & 0xFF);
}
}
void resetHMAC(const uint8_t* key, size_t keyLen) {
state_ = 2166136261u;
update(key, keyLen);
state_ ^= 0x36363636u;
}
void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {
update(key, keyLen);
finalize(hash, hashLen);
}
};

11
test/mocks/Stream.h

@ -1,10 +1,13 @@
#pragma once
// Mock Stream class for native testing
// Provides minimal interface needed by Utils.h
#include <stddef.h>
#include <stdint.h>
class Stream {
public:
virtual void print(char c) {}
virtual void print(const char* str) {}
virtual size_t readBytes(uint8_t*, size_t) { return 0; }
virtual size_t write(const uint8_t*, size_t len) { return len; }
virtual void print(char) {}
virtual void print(const char*) {}
virtual void println() {}
};

551
test/test_utils/test_advert_data.cpp

@ -1,172 +1,475 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <gtest/gtest.h>
#include "helpers/AdvertDataHelpers.h"
#include "helpers/BaseChatMesh.h"
#include "helpers/SimpleMeshTables.h"
namespace {
constexpr char kSenderPrivateKeyHex[] =
"70"
"65e18fd9fabb70c1ed90dca19907de698c88b709ea146eafd93d9b830c7b60"
"c4681193c79bbc39945ba8064104bb618f8fd7a84a0af6f57033d6e8ddcd6471";
constexpr char kSenderPublicKeyHex[] =
"1ec77175b0918ed206f9ae04ec136d6d5d4315bb26305427f645b492e9350c10";
void WriteU8(uint8_t* dest, size_t* offset, uint8_t value) {
dest[(*offset)++] = value;
dest[(*offset)++] = value;
}
void WriteI32Le(uint8_t* dest, size_t* offset, int32_t value) {
const uint32_t raw = static_cast<uint32_t>(value);
dest[(*offset)++] = static_cast<uint8_t>(raw & 0xFF);
dest[(*offset)++] = static_cast<uint8_t>((raw >> 8) & 0xFF);
dest[(*offset)++] = static_cast<uint8_t>((raw >> 16) & 0xFF);
dest[(*offset)++] = static_cast<uint8_t>((raw >> 24) & 0xFF);
const uint32_t raw = static_cast<uint32_t>(value);
dest[(*offset)++] = static_cast<uint8_t>(raw & 0xFF);
dest[(*offset)++] = static_cast<uint8_t>((raw >> 8) & 0xFF);
dest[(*offset)++] = static_cast<uint8_t>((raw >> 16) & 0xFF);
dest[(*offset)++] = static_cast<uint8_t>((raw >> 24) & 0xFF);
}
void WriteBytes(uint8_t* dest, size_t* offset, const uint8_t* bytes, size_t length) {
for (size_t i = 0; i < length; ++i) {
dest[*offset + i] = bytes[i];
}
*offset += length;
memcpy(dest + *offset, bytes, length);
*offset += length;
}
template <size_t N>
void WriteStringLiteral(uint8_t* dest, size_t* offset, const char (&value)[N]) {
static_assert(N > 0, "string literal must include a null terminator");
WriteBytes(dest, offset, reinterpret_cast<const uint8_t*>(value), N - 1);
static_assert(N > 0, "string literal must include a null terminator");
WriteBytes(dest, offset, reinterpret_cast<const uint8_t*>(value), N - 1);
}
TEST(AdvertData, RoundTripsNameOnly) {
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
class FakeMillis final : public mesh::MillisecondClock {
public:
unsigned long getMillis() override {
return 0;
}
};
class FakeRtc final : public mesh::RTCClock {
public:
explicit FakeRtc(uint32_t initial_time) : _time(initial_time) {}
uint32_t getCurrentTime() override {
return _time;
}
void setCurrentTime(uint32_t time) override {
_time = time;
}
private:
uint32_t _time;
};
// flags/type byte: chat advert with a trailing name field.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK);
// name field: raw bytes for "alice", consuming the rest of app_data.
WriteStringLiteral(app_data, &offset, "alice");
class FakeRng final : public mesh::RNG {
public:
void random(uint8_t* dest, size_t sz) override {
memset(dest, 0x5A, sz);
}
};
AdvertDataParser parser(app_data, offset);
class FakeRadio final : public mesh::Radio {
public:
int recvRaw(uint8_t*, int) override {
return 0;
}
ASSERT_TRUE(parser.isValid());
EXPECT_EQ(ADV_TYPE_CHAT, parser.getType());
EXPECT_TRUE(parser.hasName());
EXPECT_STREQ("alice", parser.getName());
EXPECT_FALSE(parser.hasLatLon());
}
uint32_t getEstAirtimeFor(int) override {
return 1;
}
TEST(AdvertData, RoundTripsNameAndCoordinates) {
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: repeater advert with lat/lon followed by a name.
WriteU8(app_data, &offset, ADV_TYPE_REPEATER | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: signed little-endian microdegrees for 37.7749.
WriteI32Le(app_data, &offset, 37774900);
// longitude field: signed little-endian microdegrees for -122.4194.
WriteI32Le(app_data, &offset, -122419400);
// name field: raw bytes for "node" after the coordinate fields.
WriteStringLiteral(app_data, &offset, "node");
AdvertDataParser parser(app_data, offset);
ASSERT_TRUE(parser.isValid());
EXPECT_EQ(ADV_TYPE_REPEATER, parser.getType());
EXPECT_TRUE(parser.hasName());
EXPECT_STREQ("node", parser.getName());
EXPECT_TRUE(parser.hasLatLon());
EXPECT_EQ(37774900, parser.getIntLat());
EXPECT_EQ(-122419400, parser.getIntLon());
EXPECT_DOUBLE_EQ(37.7749, parser.getLat());
EXPECT_DOUBLE_EQ(-122.4194, parser.getLon());
}
float packetScore(float, int) override {
return 1.0f;
}
TEST(AdvertData, RoundTripsCoordinateExtremes) {
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: sensor advert with both location fields and a name.
WriteU8(app_data, &offset, ADV_TYPE_SENSOR | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: minimum supported latitude, -90.000000 degrees.
WriteI32Le(app_data, &offset, -90000000);
// longitude field: maximum supported longitude, 180.000000 degrees.
WriteI32Le(app_data, &offset, 180000000);
// name field: raw bytes for "edge".
WriteStringLiteral(app_data, &offset, "edge");
AdvertDataParser parser(app_data, offset);
ASSERT_TRUE(parser.isValid());
EXPECT_TRUE(parser.hasLatLon());
EXPECT_EQ(-90000000, parser.getIntLat());
EXPECT_EQ(180000000, parser.getIntLon());
}
bool startSendRaw(const uint8_t*, int) override {
return true;
}
bool isSendComplete() override {
return true;
}
void onSendFinished() override {}
bool isInRecvMode() const override {
return true;
}
};
class NoopPacketManager final : public mesh::PacketManager {
public:
mesh::Packet* allocNew() override {
return nullptr;
}
void free(mesh::Packet*) override {}
void queueOutbound(mesh::Packet*, uint8_t, uint32_t) override {}
mesh::Packet* getNextOutbound(uint32_t) override {
return nullptr;
}
int getOutboundCount(uint32_t) const override {
return 0;
}
int getOutboundTotal() const override {
return 0;
}
int getFreeCount() const override {
return 0;
}
mesh::Packet* getOutboundByIdx(int) override {
return nullptr;
}
mesh::Packet* removeOutboundByIdx(int) override {
return nullptr;
}
void queueInbound(mesh::Packet*, uint32_t) override {}
mesh::Packet* getNextInbound(uint32_t) override {
return nullptr;
}
};
class TestChatMesh final : public BaseChatMesh {
public:
TestChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc,
mesh::PacketManager& mgr, mesh::MeshTables& tables)
: BaseChatMesh(radio, ms, rng, rtc, mgr, tables) {}
mesh::DispatcherAction recv(mesh::Packet* pkt) {
return onRecvPacket(pkt);
}
TEST(AdvertData, RejectsLongitudeOutsideValidRange) {
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
std::optional<ContactInfo> discovered_contact;
// flags/type byte: chat advert with location and name fields present.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: valid latitude so the failure comes from longitude.
WriteI32Le(app_data, &offset, 37774900);
// longitude field: one microdegree above +180.0, which is invalid.
WriteI32Le(app_data, &offset, 180000001);
// name field: parser should reject before the trailing name matters.
WriteStringLiteral(app_data, &offset, "node");
protected:
void onDiscoveredContact(ContactInfo& contact, bool, uint8_t, const uint8_t*) override {
discovered_contact = contact;
}
AdvertDataParser parser(app_data, offset);
ContactInfo* processAck(const uint8_t*) override {
return nullptr;
}
EXPECT_FALSE(parser.isValid());
void onContactPathUpdated(const ContactInfo&) override {}
void onMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {}
void onCommandDataRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {}
void onSignedMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const uint8_t*, const char*) override {}
uint32_t calcFloodTimeoutMillisFor(uint32_t) const override {
return 0;
}
uint32_t calcDirectTimeoutMillisFor(uint32_t, uint8_t) const override {
return 0;
}
void onSendTimeout() override {}
void onChannelMessageRecv(const mesh::GroupChannel&, mesh::Packet*, uint32_t, const char*) override {}
uint8_t onContactRequest(const ContactInfo&, uint32_t, const uint8_t*, uint8_t, uint8_t*) override {
return 0;
}
void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {}
};
mesh::LocalIdentity MakeSenderIdentity() {
return mesh::LocalIdentity(kSenderPrivateKeyHex, kSenderPublicKeyHex);
}
TEST(AdvertData, RejectsLongitudeBelowValidRange) {
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
mesh::Packet BuildSignedAdvertPacket(const mesh::LocalIdentity& sender, uint32_t timestamp,
const uint8_t* app_data, uint8_t app_data_len) {
mesh::Packet packet;
// Wire header: flood-routed advert packet with no path hashes yet.
packet.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_ADVERT << PH_TYPE_SHIFT);
packet.path_len = 0;
int offset = 0;
// Sender public key: used by the receiver to identify who signed the advert.
memcpy(&packet.payload[offset], sender.pub_key, PUB_KEY_SIZE);
offset += PUB_KEY_SIZE;
// Advert timestamp: the sender's monotonic advert time used for replay checks.
memcpy(&packet.payload[offset], &timestamp, sizeof(timestamp));
offset += sizeof(timestamp);
// Signature field: filled after the signed message bytes are assembled below.
uint8_t* signature = &packet.payload[offset];
offset += SIGNATURE_SIZE;
// Raw advert app_data: arbitrary bytes authored by the test, not by createAdvert().
memcpy(&packet.payload[offset], app_data, app_data_len);
offset += app_data_len;
packet.payload_len = offset;
uint8_t message[PUB_KEY_SIZE + 4 + MAX_ADVERT_DATA_SIZE];
int message_len = 0;
memcpy(&message[message_len], sender.pub_key, PUB_KEY_SIZE);
message_len += PUB_KEY_SIZE;
memcpy(&message[message_len], &timestamp, sizeof(timestamp));
message_len += sizeof(timestamp);
memcpy(&message[message_len], app_data, app_data_len);
message_len += app_data_len;
sender.sign(signature, message, message_len);
return packet;
}
// flags/type byte: chat advert with location and name fields present.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: valid latitude so the failure comes from longitude.
WriteI32Le(app_data, &offset, 37774900);
// longitude field: one microdegree below -180.0, which is invalid.
WriteI32Le(app_data, &offset, -180000001);
// name field: included to keep the payload shape consistent.
WriteStringLiteral(app_data, &offset, "node");
TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) {
FakeRadio radio;
FakeMillis ms;
FakeRng rng;
FakeRtc rtc(1704067200U);
NoopPacketManager packet_manager;
SimpleMeshTables tables;
TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables);
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: chat advert with a trailing name field.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK);
// name field: raw bytes for "alice", consuming the rest of app_data.
WriteStringLiteral(app_data, &offset, "alice");
mesh::LocalIdentity sender = MakeSenderIdentity();
mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset);
mesh.recv(&packet);
ASSERT_TRUE(mesh.discovered_contact.has_value());
EXPECT_EQ(ADV_TYPE_CHAT, mesh.discovered_contact->type);
EXPECT_STREQ("alice", mesh.discovered_contact->name);
EXPECT_EQ(1704067201U, mesh.discovered_contact->last_advert_timestamp);
EXPECT_EQ(1704067200U, mesh.discovered_contact->lastmod);
EXPECT_EQ(0, mesh.discovered_contact->gps_lat);
EXPECT_EQ(0, mesh.discovered_contact->gps_lon);
}
AdvertDataParser parser(app_data, offset);
TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) {
FakeRadio radio;
FakeMillis ms;
FakeRng rng;
FakeRtc rtc(1704067200U);
NoopPacketManager packet_manager;
SimpleMeshTables tables;
TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables);
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: repeater advert with lat/lon followed by a name.
WriteU8(app_data, &offset, ADV_TYPE_REPEATER | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: signed little-endian microdegrees for 37.7749.
WriteI32Le(app_data, &offset, 37774900);
// longitude field: signed little-endian microdegrees for -122.4194.
WriteI32Le(app_data, &offset, -122419400);
// name field: raw bytes for "node" after the coordinate fields.
WriteStringLiteral(app_data, &offset, "node");
mesh::LocalIdentity sender = MakeSenderIdentity();
mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset);
mesh.recv(&packet);
ASSERT_TRUE(mesh.discovered_contact.has_value());
EXPECT_EQ(ADV_TYPE_REPEATER, mesh.discovered_contact->type);
EXPECT_STREQ("node", mesh.discovered_contact->name);
EXPECT_EQ(37774900, mesh.discovered_contact->gps_lat);
EXPECT_EQ(-122419400, mesh.discovered_contact->gps_lon);
}
EXPECT_FALSE(parser.isValid());
TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) {
FakeRadio radio;
FakeMillis ms;
FakeRng rng;
FakeRtc rtc(1704067200U);
NoopPacketManager packet_manager;
SimpleMeshTables tables;
TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables);
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: sensor advert with both location fields and a name.
WriteU8(app_data, &offset, ADV_TYPE_SENSOR | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: minimum supported latitude, -90.000000 degrees.
WriteI32Le(app_data, &offset, -90000000);
// longitude field: maximum supported longitude, 180.000000 degrees.
WriteI32Le(app_data, &offset, 180000000);
// name field: raw bytes for "edge".
WriteStringLiteral(app_data, &offset, "edge");
mesh::LocalIdentity sender = MakeSenderIdentity();
mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset);
mesh.recv(&packet);
ASSERT_TRUE(mesh.discovered_contact.has_value());
EXPECT_EQ(ADV_TYPE_SENSOR, mesh.discovered_contact->type);
EXPECT_STREQ("edge", mesh.discovered_contact->name);
EXPECT_EQ(-90000000, mesh.discovered_contact->gps_lat);
EXPECT_EQ(180000000, mesh.discovered_contact->gps_lon);
}
TEST(AdvertData, RejectsLatitudeOutsideValidRange) {
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) {
FakeRadio radio;
FakeMillis ms;
FakeRng rng;
FakeRtc rtc(1704067200U);
NoopPacketManager packet_manager;
SimpleMeshTables tables;
TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables);
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: chat advert with location and name fields present.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: valid latitude so the failure comes from longitude.
WriteI32Le(app_data, &offset, 37774900);
// longitude field: one microdegree above +180.0, which is invalid.
WriteI32Le(app_data, &offset, 180000001);
// name field: parser should reject before the trailing name matters.
WriteStringLiteral(app_data, &offset, "node");
mesh::LocalIdentity sender = MakeSenderIdentity();
mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset);
mesh.recv(&packet);
EXPECT_FALSE(mesh.discovered_contact.has_value());
}
// flags/type byte: chat advert with location and name fields present.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: one microdegree above +90.0, which is invalid.
WriteI32Le(app_data, &offset, 90000001);
// longitude field: valid longitude so the failure comes from latitude.
WriteI32Le(app_data, &offset, -122419400);
// name field: included to keep the payload shape consistent.
WriteStringLiteral(app_data, &offset, "node");
TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) {
FakeRadio radio;
FakeMillis ms;
FakeRng rng;
FakeRtc rtc(1704067200U);
NoopPacketManager packet_manager;
SimpleMeshTables tables;
TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables);
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: chat advert with location and name fields present.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: valid latitude so the failure comes from longitude.
WriteI32Le(app_data, &offset, 37774900);
// longitude field: one microdegree below -180.0, which is invalid.
WriteI32Le(app_data, &offset, -180000001);
// name field: included to keep the payload shape consistent.
WriteStringLiteral(app_data, &offset, "node");
mesh::LocalIdentity sender = MakeSenderIdentity();
mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset);
mesh.recv(&packet);
EXPECT_FALSE(mesh.discovered_contact.has_value());
}
AdvertDataParser parser(app_data, offset);
TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) {
FakeRadio radio;
FakeMillis ms;
FakeRng rng;
FakeRtc rtc(1704067200U);
NoopPacketManager packet_manager;
SimpleMeshTables tables;
TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables);
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: chat advert with location and name fields present.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: one microdegree above +90.0, which is invalid.
WriteI32Le(app_data, &offset, 90000001);
// longitude field: valid longitude so the failure comes from latitude.
WriteI32Le(app_data, &offset, -122419400);
// name field: included to keep the payload shape consistent.
WriteStringLiteral(app_data, &offset, "node");
mesh::LocalIdentity sender = MakeSenderIdentity();
mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset);
mesh.recv(&packet);
EXPECT_FALSE(mesh.discovered_contact.has_value());
}
EXPECT_FALSE(parser.isValid());
TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) {
FakeRadio radio;
FakeMillis ms;
FakeRng rng;
FakeRtc rtc(1704067200U);
NoopPacketManager packet_manager;
SimpleMeshTables tables;
TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables);
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: chat advert with location and name fields present.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: one microdegree below -90.0, which is invalid.
WriteI32Le(app_data, &offset, -90000001);
// longitude field: valid longitude so the failure comes from latitude.
WriteI32Le(app_data, &offset, -122419400);
// name field: included to keep the payload shape consistent.
WriteStringLiteral(app_data, &offset, "node");
mesh::LocalIdentity sender = MakeSenderIdentity();
mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset);
mesh.recv(&packet);
EXPECT_FALSE(mesh.discovered_contact.has_value());
}
TEST(AdvertData, RejectsLatitudeBelowValidRange) {
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
TEST(AdvertData, RejectsForgedSignatureFromNetworkPacket) {
FakeRadio radio;
FakeMillis ms;
FakeRng rng;
FakeRtc rtc(1704067200U);
NoopPacketManager packet_manager;
SimpleMeshTables tables;
TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables);
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {};
size_t offset = 0;
// flags/type byte: chat advert with a trailing name field.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK);
// name field: raw bytes for "mallory".
WriteStringLiteral(app_data, &offset, "mallory");
mesh::LocalIdentity sender = MakeSenderIdentity();
mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset);
// flags/type byte: chat advert with location and name fields present.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: one microdegree below -90.0, which is invalid.
WriteI32Le(app_data, &offset, -90000001);
// longitude field: valid longitude so the failure comes from latitude.
WriteI32Le(app_data, &offset, -122419400);
// name field: included to keep the payload shape consistent.
WriteStringLiteral(app_data, &offset, "node");
// Corrupt the signature bytes after signing so verification must fail in Mesh::onRecvPacket().
packet.payload[PUB_KEY_SIZE + 4] ^= 0xFF;
AdvertDataParser parser(app_data, offset);
mesh.recv(&packet);
EXPECT_FALSE(parser.isValid());
EXPECT_FALSE(mesh.discovered_contact.has_value());
}
} // namespace

Loading…
Cancel
Save