Browse Source

Clarify native mocks and advert truncation tests

pull/2459/head
Michael Lynch 2 months ago
parent
commit
5b2b577c6a
  1. 2
      test/mocks/Arduino.h
  2. 2
      test/mocks/Ed25519.h
  3. 2
      test/mocks/SHA256.h
  4. 2
      test/mocks/Stream.h
  5. 86
      test/test_helpers/test_advert_data.cpp

2
test/mocks/Arduino.h

@ -6,6 +6,8 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
// Mock Arduino compatibility header for native testing.
// Provides the small libc-backed helpers needed by Arduino-oriented code.
inline char* ltoa(long value, char* buffer, int base) { inline char* ltoa(long value, char* buffer, int base) {
if (base == 10) { if (base == 10) {
snprintf(buffer, 32, "%ld", value); snprintf(buffer, 32, "%ld", value);

2
test/mocks/Ed25519.h

@ -10,6 +10,8 @@ extern "C" {
#include <stdint.h> #include <stdint.h>
// Mock Ed25519 wrapper for native testing.
// Adapts the test-only C ed25519 implementation to the Arduino-style class API.
class Ed25519 { class Ed25519 {
public: public:
static bool verify(const uint8_t* sig, const uint8_t* pub_key, const uint8_t* message, int msg_len) { static bool verify(const uint8_t* sig, const uint8_t* pub_key, const uint8_t* message, int msg_len) {

2
test/mocks/SHA256.h

@ -4,6 +4,8 @@
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
// Mock SHA256 class for native testing.
// Provides a deterministic stand-in so code can hash data without Arduino crypto deps.
class SHA256 { class SHA256 {
uint32_t state_ = 2166136261u; uint32_t state_ = 2166136261u;

2
test/mocks/Stream.h

@ -3,6 +3,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
// Mock Stream class for native testing.
// Provides the minimal interface needed by code that depends on Arduino Stream.
class Stream { class Stream {
public: public:
virtual size_t readBytes(uint8_t*, size_t) { return 0; } virtual size_t readBytes(uint8_t*, size_t) { return 0; }

86
test/test_helpers/test_advert_data.cpp

@ -329,6 +329,7 @@ TEST(AdvertData, ParsesPositiveLatitudeAndNegativeLongitudeBoundariesFromNetwork
WriteI32Le(app_data, &offset, 90000000); WriteI32Le(app_data, &offset, 90000000);
// longitude field: minimum supported longitude, -180.000000 degrees. // longitude field: minimum supported longitude, -180.000000 degrees.
WriteI32Le(app_data, &offset, -180000000); WriteI32Le(app_data, &offset, -180000000);
// name field: raw bytes for "dummy-node-name".
WriteStringLiteral(app_data, &offset, "dummy-node-name"); WriteStringLiteral(app_data, &offset, "dummy-node-name");
constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t current_timestamp = 1704067200U;
@ -349,8 +350,11 @@ TEST(AdvertData, ParsesNullIslandCoordinatesFromNetworkPacket) {
// flags/type byte: chat advert with zero-valued coordinates and a name. // flags/type byte: chat advert with zero-valued coordinates and a name.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: Null Island latitude at exactly 0.000000 degrees.
WriteI32Le(app_data, &offset, 0); WriteI32Le(app_data, &offset, 0);
// longitude field: Null Island longitude at exactly 0.000000 degrees.
WriteI32Le(app_data, &offset, 0); WriteI32Le(app_data, &offset, 0);
// name field: raw bytes for "dummy-node-name".
WriteStringLiteral(app_data, &offset, "dummy-node-name"); WriteStringLiteral(app_data, &offset, "dummy-node-name");
constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t current_timestamp = 1704067200U;
@ -411,19 +415,15 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) {
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); EXPECT_FALSE(test_mesh->discovered_contact.has_value());
} }
void ExpectTruncatedGpsPayloadIsRejected(uint8_t app_data_len) { TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) {
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; // Backing storage is unused because the advertised app_data length is zero.
uint8_t app_data[1] = {};
size_t offset = 0; size_t offset = 0;
// Advert claims to carry GPS coordinates and a name, but the payload is truncated.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
WriteI32Le(app_data, &offset, 37774900);
WriteI32Le(app_data, &offset, -122419400);
WriteStringLiteral(app_data, &offset, "dummy-node-name");
constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t current_timestamp = 1704067200U;
constexpr uint32_t advert_timestamp = current_timestamp + 1; constexpr uint32_t advert_timestamp = current_timestamp + 1;
mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, app_data_len); // Leave the app_data length at zero so the parser never sees the flags byte.
mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset);
auto test_mesh = MakeTestMesh(current_timestamp); auto test_mesh = MakeTestMesh(current_timestamp);
test_mesh->recv(&packet); test_mesh->recv(&packet);
@ -431,20 +431,67 @@ void ExpectTruncatedGpsPayloadIsRejected(uint8_t app_data_len) {
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); EXPECT_FALSE(test_mesh->discovered_contact.has_value());
} }
TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) {
ExpectTruncatedGpsPayloadIsRejected(0);
}
TEST(AdvertData, RejectsGpsPayloadWithOnlyFlagsByte) { TEST(AdvertData, RejectsGpsPayloadWithOnlyFlagsByte) {
ExpectTruncatedGpsPayloadIsRejected(1); uint8_t app_data[1] = {};
size_t offset = 0;
// flags/type byte: chat advert that claims to carry coordinates and a name.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
constexpr uint32_t current_timestamp = 1704067200U;
constexpr uint32_t advert_timestamp = current_timestamp + 1;
// Pass only the flags byte so no latitude bytes remain.
mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset);
auto test_mesh = MakeTestMesh(current_timestamp);
test_mesh->recv(&packet);
EXPECT_FALSE(test_mesh->discovered_contact.has_value());
} }
TEST(AdvertData, RejectsGpsPayloadWithLatitudeButMissingLongitude) { TEST(AdvertData, RejectsGpsPayloadWithLatitudeButMissingLongitude) {
ExpectTruncatedGpsPayloadIsRejected(5); uint8_t app_data[5] = {};
size_t offset = 0;
// flags/type byte: chat advert that claims to carry coordinates and a name.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: complete latitude bytes are present before truncation.
WriteI32Le(app_data, &offset, 37774900);
constexpr uint32_t current_timestamp = 1704067200U;
constexpr uint32_t advert_timestamp = current_timestamp + 1;
// Pass only the flags byte and latitude field so longitude is missing.
mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset);
auto test_mesh = MakeTestMesh(current_timestamp);
test_mesh->recv(&packet);
EXPECT_FALSE(test_mesh->discovered_contact.has_value());
} }
TEST(AdvertData, RejectsGpsPayloadOneByteShortOfFullCoordinates) { TEST(AdvertData, RejectsGpsPayloadOneByteShortOfFullCoordinates) {
ExpectTruncatedGpsPayloadIsRejected(8); uint8_t app_data[8] = {};
size_t offset = 0;
uint8_t lon_bytes[sizeof(int32_t)] = {};
size_t lon_offset = 0;
// flags/type byte: chat advert that claims to carry coordinates and a name.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: complete latitude bytes are present before truncation.
WriteI32Le(app_data, &offset, 37774900);
// longitude field: only the first three longitude bytes are present before truncation.
WriteI32Le(lon_bytes, &lon_offset, -122419400);
WriteBytes(app_data, &offset, lon_bytes, 3);
constexpr uint32_t current_timestamp = 1704067200U;
constexpr uint32_t advert_timestamp = current_timestamp + 1;
// Pass only the flags byte, latitude field, and three longitude bytes.
mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset);
auto test_mesh = MakeTestMesh(current_timestamp);
test_mesh->recv(&packet);
EXPECT_FALSE(test_mesh->discovered_contact.has_value());
} }
TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) {
@ -499,6 +546,7 @@ TEST(AdvertData, KeepsExistingGpsWhenUpdatedAdvertOmitsCoordinates) {
// flags/type byte: chat advert with a new name but no GPS fields. // flags/type byte: chat advert with a new name but no GPS fields.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK);
// name field: replacement contact name with no coordinate payload following it.
WriteStringLiteral(app_data, &offset, "updated-name"); WriteStringLiteral(app_data, &offset, "updated-name");
constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t current_timestamp = 1704067200U;
@ -527,8 +575,11 @@ TEST(AdvertData, OverwritesExistingGpsWhenUpdatedAdvertIncludesCoordinates) {
// flags/type byte: chat advert with replacement GPS coordinates and a new name. // flags/type byte: chat advert with replacement GPS coordinates and a new name.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: replacement latitude for 40.712800 degrees.
WriteI32Le(app_data, &offset, 40712800); WriteI32Le(app_data, &offset, 40712800);
// longitude field: replacement longitude for -74.006000 degrees.
WriteI32Le(app_data, &offset, -74006000); WriteI32Le(app_data, &offset, -74006000);
// name field: replacement contact name applied with the new coordinates.
WriteStringLiteral(app_data, &offset, "updated-name"); WriteStringLiteral(app_data, &offset, "updated-name");
constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t current_timestamp = 1704067200U;
@ -557,8 +608,11 @@ TEST(AdvertData, LeavesExistingGpsUntouchedWhenUpdatedAdvertHasInvalidCoordinate
// flags/type byte: chat advert with invalid longitude and a new name. // flags/type byte: chat advert with invalid longitude and a new name.
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK);
// latitude field: valid latitude so the update failure comes from longitude.
WriteI32Le(app_data, &offset, 37774900); WriteI32Le(app_data, &offset, 37774900);
// longitude field: one microdegree above +180.0, which should reject the update.
WriteI32Le(app_data, &offset, 180000001); WriteI32Le(app_data, &offset, 180000001);
// name field: replacement name that should not be applied when parsing fails.
WriteStringLiteral(app_data, &offset, "updated-name"); WriteStringLiteral(app_data, &offset, "updated-name");
constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t current_timestamp = 1704067200U;

Loading…
Cancel
Save