mirror of https://github.com/meshcore-dev/MeshCore
6 changed files with 502 additions and 134 deletions
@ -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; |
|||
} |
|||
|
|||
@ -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; |
|||
} |
|||
}; |
|||
@ -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() {} |
|||
}; |
|||
|
|||
@ -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], ×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; |
|||
} |
|||
|
|||
// 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…
Reference in new issue