|
|
|
@ -4,17 +4,10 @@ |
|
|
|
|
|
|
|
#include <gtest/gtest.h> |
|
|
|
|
|
|
|
#include "../mocks/mesh.h" |
|
|
|
#include "helpers/AdvertDataHelpers.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; |
|
|
|
} |
|
|
|
@ -38,63 +31,11 @@ void WriteStringLiteral(uint8_t* dest, size_t* offset, const char (&value)[N]) { |
|
|
|
WriteBytes(dest, offset, reinterpret_cast<const uint8_t*>(value), N - 1); |
|
|
|
} |
|
|
|
|
|
|
|
TestMeshContext MakeTestMesh(uint32_t current_timestamp) { |
|
|
|
return TestMeshContext(current_timestamp); |
|
|
|
} |
|
|
|
|
|
|
|
ContactInfo MakeSenderContact(uint32_t advert_timestamp, int32_t gps_lat, int32_t gps_lon) { |
|
|
|
ContactInfo contact = {}; |
|
|
|
contact.id = mesh::Identity(kSenderPublicKeyHex); |
|
|
|
strcpy(contact.name, "existing-contact"); |
|
|
|
contact.type = ADV_TYPE_CHAT; |
|
|
|
contact.out_path_len = OUT_PATH_UNKNOWN; |
|
|
|
contact.last_advert_timestamp = advert_timestamp; |
|
|
|
contact.lastmod = advert_timestamp; |
|
|
|
contact.gps_lat = gps_lat; |
|
|
|
contact.gps_lon = gps_lon; |
|
|
|
return contact; |
|
|
|
AdvertDataParser Parse(const uint8_t* app_data, size_t app_data_len) { |
|
|
|
return AdvertDataParser(app_data, static_cast<uint8_t>(app_data_len)); |
|
|
|
} |
|
|
|
|
|
|
|
mesh::Packet BuildSignedAdvertPacket(uint32_t timestamp, const uint8_t* app_data, uint8_t app_data_len) { |
|
|
|
mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); |
|
|
|
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], ×tamp, 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], ×tamp, 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; |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { |
|
|
|
TEST(AdvertDataParser, ParsesNameAndCoordinates) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
@ -107,21 +48,18 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { |
|
|
|
// name field: trailing contact name bytes after the coordinate fields.
|
|
|
|
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, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
ASSERT_TRUE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_EQ(ADV_TYPE_REPEATER, test_mesh->discovered_contact->type); |
|
|
|
EXPECT_STREQ("dummy-node-name", test_mesh->discovered_contact->name); |
|
|
|
EXPECT_EQ(37774900, test_mesh->discovered_contact->gps_lat); |
|
|
|
EXPECT_EQ(-122419400, test_mesh->discovered_contact->gps_lon); |
|
|
|
ASSERT_TRUE(parser.isValid()); |
|
|
|
EXPECT_EQ(ADV_TYPE_REPEATER, parser.getType()); |
|
|
|
ASSERT_TRUE(parser.hasLatLon()); |
|
|
|
EXPECT_EQ(37774900, parser.getIntLat()); |
|
|
|
EXPECT_EQ(-122419400, parser.getIntLon()); |
|
|
|
ASSERT_TRUE(parser.hasName()); |
|
|
|
EXPECT_STREQ("dummy-node-name", parser.getName()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { |
|
|
|
TEST(AdvertDataParser, ParsesCoordinateExtremes) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
@ -134,21 +72,16 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { |
|
|
|
// name field: raw bytes for "dummy-node-name".
|
|
|
|
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, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
ASSERT_TRUE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_EQ(ADV_TYPE_SENSOR, test_mesh->discovered_contact->type); |
|
|
|
EXPECT_STREQ("dummy-node-name", test_mesh->discovered_contact->name); |
|
|
|
EXPECT_EQ(-90000000, test_mesh->discovered_contact->gps_lat); |
|
|
|
EXPECT_EQ(180000000, test_mesh->discovered_contact->gps_lon); |
|
|
|
ASSERT_TRUE(parser.isValid()); |
|
|
|
EXPECT_EQ(ADV_TYPE_SENSOR, parser.getType()); |
|
|
|
EXPECT_EQ(-90000000, parser.getIntLat()); |
|
|
|
EXPECT_EQ(180000000, parser.getIntLon()); |
|
|
|
EXPECT_STREQ("dummy-node-name", parser.getName()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, ParsesPositiveLatitudeAndNegativeLongitudeBoundariesFromNetworkPacket) { |
|
|
|
TEST(AdvertDataParser, ParsesPositiveLatitudeAndNegativeLongitudeBoundaries) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
@ -161,19 +94,14 @@ TEST(AdvertData, ParsesPositiveLatitudeAndNegativeLongitudeBoundariesFromNetwork |
|
|
|
// name field: raw bytes for "dummy-node-name".
|
|
|
|
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, offset); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
|
|
|
|
ASSERT_TRUE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_EQ(90000000, test_mesh->discovered_contact->gps_lat); |
|
|
|
EXPECT_EQ(-180000000, test_mesh->discovered_contact->gps_lon); |
|
|
|
ASSERT_TRUE(parser.isValid()); |
|
|
|
EXPECT_EQ(90000000, parser.getIntLat()); |
|
|
|
EXPECT_EQ(-180000000, parser.getIntLon()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, ParsesNullIslandCoordinatesFromNetworkPacket) { |
|
|
|
TEST(AdvertDataParser, ParsesNullIslandCoordinates) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
@ -186,19 +114,32 @@ TEST(AdvertData, ParsesNullIslandCoordinatesFromNetworkPacket) { |
|
|
|
// name field: raw bytes for "dummy-node-name".
|
|
|
|
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, offset); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
ASSERT_TRUE(parser.isValid()); |
|
|
|
EXPECT_EQ(0, parser.getIntLat()); |
|
|
|
EXPECT_EQ(0, parser.getIntLon()); |
|
|
|
} |
|
|
|
|
|
|
|
ASSERT_TRUE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_EQ(0, test_mesh->discovered_contact->gps_lat); |
|
|
|
EXPECT_EQ(0, test_mesh->discovered_contact->gps_lon); |
|
|
|
TEST(AdvertDataParser, ParsesNameWithoutCoordinates) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
// flags/type byte: chat advert with a name but no GPS fields.
|
|
|
|
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); |
|
|
|
// name field: contact name with no coordinate payload before it.
|
|
|
|
WriteStringLiteral(app_data, &offset, "updated-name"); |
|
|
|
|
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
ASSERT_TRUE(parser.isValid()); |
|
|
|
EXPECT_EQ(ADV_TYPE_CHAT, parser.getType()); |
|
|
|
EXPECT_FALSE(parser.hasLatLon()); |
|
|
|
ASSERT_TRUE(parser.hasName()); |
|
|
|
EXPECT_STREQ("updated-name", parser.getName()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { |
|
|
|
TEST(AdvertDataParser, RejectsLongitudeOutsideValidRange) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
@ -211,17 +152,12 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { |
|
|
|
// name field: parser should reject before the trailing name matters.
|
|
|
|
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, offset); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
|
|
|
|
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { |
|
|
|
TEST(AdvertDataParser, RejectsLongitudeBelowValidRange) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
@ -234,51 +170,70 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { |
|
|
|
// name field: included to keep the payload shape consistent.
|
|
|
|
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, offset); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertDataParser, RejectsLatitudeOutsideValidRange) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
EXPECT_FALSE(test_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, "dummy-node-name"); |
|
|
|
|
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) { |
|
|
|
// Backing storage is unused because the advertised app_data length is zero.
|
|
|
|
uint8_t app_data[1] = {}; |
|
|
|
TEST(AdvertDataParser, RejectsLatitudeBelowValidRange) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
constexpr uint32_t current_timestamp = 1704067200U; |
|
|
|
constexpr uint32_t advert_timestamp = current_timestamp + 1; |
|
|
|
// Leave the app_data length at zero so the parser never sees the flags byte.
|
|
|
|
mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, 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, "dummy-node-name"); |
|
|
|
|
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertDataParser, RejectsPayloadWithMissingFlagsByte) { |
|
|
|
// Backing storage is unused because the advertised app_data length is zero.
|
|
|
|
const uint8_t app_data[1] = {}; |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
const AdvertDataParser parser = Parse(app_data, 0); |
|
|
|
|
|
|
|
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, RejectsGpsPayloadWithOnlyFlagsByte) { |
|
|
|
TEST(AdvertDataParser, RejectsPayloadWithOnlyFlagsByte) { |
|
|
|
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); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
|
|
|
|
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, RejectsGpsPayloadWithLatitudeButMissingLongitude) { |
|
|
|
TEST(AdvertDataParser, RejectsPayloadWithLatitudeButMissingLongitude) { |
|
|
|
uint8_t app_data[5] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
@ -287,18 +242,13 @@ TEST(AdvertData, RejectsGpsPayloadWithLatitudeButMissingLongitude) { |
|
|
|
// 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); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, RejectsGpsPayloadOneByteShortOfFullCoordinates) { |
|
|
|
TEST(AdvertDataParser, RejectsPayloadOneByteShortOfFullCoordinates) { |
|
|
|
uint8_t app_data[8] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
uint8_t lon_bytes[sizeof(int32_t)] = {}; |
|
|
|
@ -312,155 +262,41 @@ TEST(AdvertData, RejectsGpsPayloadOneByteShortOfFullCoordinates) { |
|
|
|
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); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
TEST(AdvertDataParser, RejectsPayloadWithIncompleteFeat1) { |
|
|
|
uint8_t app_data[2] = {}; |
|
|
|
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, "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, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
|
|
|
|
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { |
|
|
|
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, "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, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
test_mesh->recv(&packet); |
|
|
|
|
|
|
|
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); |
|
|
|
} |
|
|
|
// flags/type byte: chat advert that claims to carry a two-byte feature field.
|
|
|
|
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_FEAT1_MASK); |
|
|
|
// feature field: only the first byte is present before truncation.
|
|
|
|
WriteU8(app_data, &offset, 0x34); |
|
|
|
|
|
|
|
TEST(AdvertData, KeepsExistingGpsWhenUpdatedAdvertOmitsCoordinates) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
// 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; |
|
|
|
constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; |
|
|
|
constexpr uint32_t new_advert_timestamp = current_timestamp + 1; |
|
|
|
mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); |
|
|
|
|
|
|
|
test_mesh->recv(&packet); |
|
|
|
|
|
|
|
ContactInfo* updated = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); |
|
|
|
ASSERT_NE(nullptr, updated); |
|
|
|
EXPECT_STREQ("updated-name", updated->name); |
|
|
|
EXPECT_EQ(37774900, updated->gps_lat); |
|
|
|
EXPECT_EQ(-122419400, updated->gps_lon); |
|
|
|
ASSERT_TRUE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_EQ(37774900, test_mesh->discovered_contact->gps_lat); |
|
|
|
EXPECT_EQ(-122419400, test_mesh->discovered_contact->gps_lon); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, OverwritesExistingGpsWhenUpdatedAdvertIncludesCoordinates) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
// 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; |
|
|
|
constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; |
|
|
|
constexpr uint32_t new_advert_timestamp = current_timestamp + 1; |
|
|
|
mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); |
|
|
|
|
|
|
|
test_mesh->recv(&packet); |
|
|
|
|
|
|
|
ContactInfo* updated = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); |
|
|
|
ASSERT_NE(nullptr, updated); |
|
|
|
EXPECT_STREQ("updated-name", updated->name); |
|
|
|
EXPECT_EQ(40712800, updated->gps_lat); |
|
|
|
EXPECT_EQ(-74006000, updated->gps_lon); |
|
|
|
ASSERT_TRUE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_EQ(40712800, test_mesh->discovered_contact->gps_lat); |
|
|
|
EXPECT_EQ(-74006000, test_mesh->discovered_contact->gps_lon); |
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
TEST(AdvertData, LeavesExistingGpsUntouchedWhenUpdatedAdvertHasInvalidCoordinates) { |
|
|
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; |
|
|
|
TEST(AdvertDataParser, RejectsPayloadWithIncompleteFeat2) { |
|
|
|
uint8_t app_data[4] = {}; |
|
|
|
size_t offset = 0; |
|
|
|
|
|
|
|
// 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; |
|
|
|
constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; |
|
|
|
constexpr uint32_t new_advert_timestamp = current_timestamp + 1; |
|
|
|
mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); |
|
|
|
|
|
|
|
auto test_mesh = MakeTestMesh(current_timestamp); |
|
|
|
ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); |
|
|
|
// flags/type byte: chat advert that claims to carry both two-byte feature fields.
|
|
|
|
WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_FEAT1_MASK | ADV_FEAT2_MASK); |
|
|
|
// feature 1 field: complete two-byte value before the truncated feature 2 field.
|
|
|
|
WriteU8(app_data, &offset, 0x34); |
|
|
|
WriteU8(app_data, &offset, 0x12); |
|
|
|
// feature 2 field: only the first byte is present before truncation.
|
|
|
|
WriteU8(app_data, &offset, 0x78); |
|
|
|
|
|
|
|
test_mesh->recv(&packet); |
|
|
|
const AdvertDataParser parser = Parse(app_data, offset); |
|
|
|
|
|
|
|
ContactInfo* existing = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); |
|
|
|
ASSERT_NE(nullptr, existing); |
|
|
|
EXPECT_STREQ("existing-contact", existing->name); |
|
|
|
EXPECT_EQ(37774900, existing->gps_lat); |
|
|
|
EXPECT_EQ(-122419400, existing->gps_lon); |
|
|
|
EXPECT_EQ(existing_advert_timestamp, existing->last_advert_timestamp); |
|
|
|
EXPECT_FALSE(test_mesh->discovered_contact.has_value()); |
|
|
|
EXPECT_FALSE(parser.isValid()); |
|
|
|
} |
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|