diff --git a/test/mocks/Arduino.h b/test/mocks/Arduino.h index d35b9fb9e..b7acefbc2 100644 --- a/test/mocks/Arduino.h +++ b/test/mocks/Arduino.h @@ -6,6 +6,8 @@ #include #include +// 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) { if (base == 10) { snprintf(buffer, 32, "%ld", value); diff --git a/test/mocks/Ed25519.h b/test/mocks/Ed25519.h index 1a57f5d1e..2c649505c 100644 --- a/test/mocks/Ed25519.h +++ b/test/mocks/Ed25519.h @@ -10,6 +10,8 @@ extern "C" { #include +// Mock Ed25519 wrapper for native testing. +// Adapts the test-only C ed25519 implementation to the Arduino-style class API. class Ed25519 { public: static bool verify(const uint8_t* sig, const uint8_t* pub_key, const uint8_t* message, int msg_len) { diff --git a/test/mocks/SHA256.h b/test/mocks/SHA256.h index 0c783dc02..38e779b7d 100644 --- a/test/mocks/SHA256.h +++ b/test/mocks/SHA256.h @@ -4,6 +4,8 @@ #include #include +// Mock SHA256 class for native testing. +// Provides a deterministic stand-in so code can hash data without Arduino crypto deps. class SHA256 { uint32_t state_ = 2166136261u; diff --git a/test/mocks/Stream.h b/test/mocks/Stream.h index 5cc399eb3..e83b04294 100644 --- a/test/mocks/Stream.h +++ b/test/mocks/Stream.h @@ -3,6 +3,8 @@ #include #include +// Mock Stream class for native testing. +// Provides the minimal interface needed by code that depends on Arduino Stream. class Stream { public: virtual size_t readBytes(uint8_t*, size_t) { return 0; } diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index af0cb9d51..83daa4288 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -329,6 +329,7 @@ TEST(AdvertData, ParsesPositiveLatitudeAndNegativeLongitudeBoundariesFromNetwork WriteI32Le(app_data, &offset, 90000000); // longitude field: minimum supported longitude, -180.000000 degrees. WriteI32Le(app_data, &offset, -180000000); + // name field: raw bytes for "dummy-node-name". WriteStringLiteral(app_data, &offset, "dummy-node-name"); 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. 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); + // longitude field: Null Island longitude at exactly 0.000000 degrees. WriteI32Le(app_data, &offset, 0); + // name field: raw bytes for "dummy-node-name". WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; @@ -411,19 +415,15 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } -void ExpectTruncatedGpsPayloadIsRejected(uint8_t app_data_len) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; +TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) { + // Backing storage is unused because the advertised app_data length is zero. + uint8_t app_data[1] = {}; 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 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); test_mesh->recv(&packet); @@ -431,20 +431,67 @@ void ExpectTruncatedGpsPayloadIsRejected(uint8_t app_data_len) { EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } -TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) { - ExpectTruncatedGpsPayloadIsRejected(0); -} - 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) { - 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) { - 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) { @@ -499,6 +546,7 @@ TEST(AdvertData, KeepsExistingGpsWhenUpdatedAdvertOmitsCoordinates) { // flags/type byte: chat advert with a new name but no GPS fields. 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"); 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. 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); + // longitude field: replacement longitude for -74.006000 degrees. WriteI32Le(app_data, &offset, -74006000); + // name field: replacement contact name applied with the new coordinates. WriteStringLiteral(app_data, &offset, "updated-name"); 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. 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); + // longitude field: one microdegree above +180.0, which should reject the update. WriteI32Le(app_data, &offset, 180000001); + // name field: replacement name that should not be applied when parsing fails. WriteStringLiteral(app_data, &offset, "updated-name"); constexpr uint32_t current_timestamp = 1704067200U;