2.8: NodeDB shrink, decoupling, and restructuring (#10413)
* 2.8: NodeDB refactor to decouple satellite entries and decrease size * Regen * Refactor node mute handling to use dedicated functions for clarity and consistency * Develop ref * Fix NodeDB review follow-ups Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/6b1d6cf6-ed6b-43b6-95cb-8e141757664e Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Address review validation nits Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/6b1d6cf6-ed6b-43b6-95cb-8e141757664e Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Trunk * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Extract legacy NodeDatabase migration * Fix remaining NodeDB review issues Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/c76b9a5a-7244-4fbc-9ef0-98091d8caaea Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Fixes * Trunk * Fix latest review compile follow-ups Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/5198da01-ec4c-4c16-8a09-68b8e6d5d410 Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Fix cppcheck style warnings Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/e60287ba-4ece-46e0-83d8-a6d89664c0bb Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Change pointer type for mesh node in set_favorite function * Change pointer types for mesh node references to const in multiple applets * Add NodeDB layout v25 documentation and migration guidelines * Remove tests for uninitialized PacketHistory state due to undefined behavior * Fix code block formatting in copilot instructions --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
GitHub
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Copilot Autofix powered by AI
parent
1bcabb893b
commit
94bb21ecc7
@@ -113,7 +113,7 @@ void test_DH25519(void)
|
||||
void test_PKC(void)
|
||||
{
|
||||
uint8_t private_key[32];
|
||||
meshtastic_UserLite_public_key_t public_key;
|
||||
meshtastic_NodeInfoLite_public_key_t public_key;
|
||||
uint8_t expected_shared[32];
|
||||
uint8_t expected_decrypted[32];
|
||||
uint8_t radioBytes[128] __attribute__((__aligned__));
|
||||
|
||||
@@ -641,30 +641,12 @@ void test_sender_zero_substituted(void)
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p2, false));
|
||||
}
|
||||
|
||||
void test_uninitialized_wasSeenRecently(void)
|
||||
{
|
||||
// Simulate uninitialized state — create a PacketHistory that looks uninitialized
|
||||
// We can't easily make allocation fail, but we can test the initOk guard with a destructed one
|
||||
PacketHistory h(4);
|
||||
TEST_ASSERT_TRUE(h.initOk()); // sanity check
|
||||
h.~PacketHistory();
|
||||
|
||||
auto p = makePacket(0x1111, 100);
|
||||
TEST_ASSERT_FALSE(h.wasSeenRecently(&p));
|
||||
|
||||
// Reconstruct in place to allow proper destruction
|
||||
new (&h) PacketHistory(4);
|
||||
}
|
||||
|
||||
void test_uninitialized_wasRelayer(void)
|
||||
{
|
||||
PacketHistory h(4);
|
||||
h.~PacketHistory();
|
||||
|
||||
TEST_ASSERT_FALSE(h.wasRelayer(0xAA, 100, 0x1111));
|
||||
|
||||
new (&h) PacketHistory(4);
|
||||
}
|
||||
// NOTE: Tests for the !initOk() short-circuit in wasSeenRecently / wasRelayer
|
||||
// were removed: the only way to drive that branch from a test is to destruct
|
||||
// the object and then call methods on it, which is UB and trips ASAN under
|
||||
// `pio test -e coverage`. In practice the guard can only fire if `new[]` throws
|
||||
// (in which case the constructor doesn't return), so the lost coverage is
|
||||
// purely defensive.
|
||||
|
||||
void test_multiple_instances_independent(void)
|
||||
{
|
||||
@@ -819,8 +801,6 @@ void setup()
|
||||
// Group 10 — Edge Cases
|
||||
RUN_TEST(test_packet_id_zero_not_stored);
|
||||
RUN_TEST(test_sender_zero_substituted);
|
||||
RUN_TEST(test_uninitialized_wasSeenRecently);
|
||||
RUN_TEST(test_uninitialized_wasRelayer);
|
||||
RUN_TEST(test_multiple_instances_independent);
|
||||
|
||||
// Group 11 — Hash Table Stress
|
||||
|
||||
@@ -50,7 +50,7 @@ class MockNodeDB : public NodeDB
|
||||
hasCachedNode = true;
|
||||
cachedNodeNum = n;
|
||||
cachedNode.num = n;
|
||||
cachedNode.has_user = true;
|
||||
cachedNode.bitfield |= NODEINFO_BITFIELD_HAS_USER_MASK;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -494,9 +494,9 @@ static void test_tm_nodeinfo_directResponse_learnsRequestorNodeInfo(void)
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(static_cast<int>(ProcessMessage::STOP), static_cast<int>(result));
|
||||
TEST_ASSERT_NOT_NULL(requestor);
|
||||
TEST_ASSERT_TRUE(requestor->has_user);
|
||||
TEST_ASSERT_EQUAL_STRING("requester-long", requestor->user.long_name);
|
||||
TEST_ASSERT_EQUAL_STRING("rq", requestor->user.short_name);
|
||||
TEST_ASSERT_TRUE((requestor->bitfield & NODEINFO_BITFIELD_HAS_USER_MASK) != 0);
|
||||
TEST_ASSERT_EQUAL_STRING("requester-long", requestor->long_name);
|
||||
TEST_ASSERT_EQUAL_STRING("rq", requestor->short_name);
|
||||
TEST_ASSERT_EQUAL_UINT8(request.channel, requestor->channel);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
// Tests for src/mesh/TypeConversions.cpp covering:
|
||||
// - bitfield bit collapse on store + extraction round-trip
|
||||
// - long_name / short_name truncation at the new max_size:25 / 5 boundaries
|
||||
// - public_key / hw_model / role pass-through
|
||||
// - thin vs bundled NodeInfo emission
|
||||
//
|
||||
// All exercised via the explicit-args overload of ConvertToNodeInfo so we don't
|
||||
// touch the global nodeDB pointer (which isn't initialized in this test env).
|
||||
|
||||
#include "NodeDB.h"
|
||||
#include "TestUtil.h"
|
||||
#include "TypeConversions.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <unity.h>
|
||||
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
// ---------- helpers -----------------------------------------------------------
|
||||
|
||||
static meshtastic_User makeUser(const char *longName, const char *shortName)
|
||||
{
|
||||
meshtastic_User u = meshtastic_User_init_zero;
|
||||
if (longName)
|
||||
strncpy(u.long_name, longName, sizeof(u.long_name) - 1);
|
||||
if (shortName)
|
||||
strncpy(u.short_name, shortName, sizeof(u.short_name) - 1);
|
||||
u.hw_model = meshtastic_HardwareModel_TBEAM;
|
||||
u.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
|
||||
return u;
|
||||
}
|
||||
|
||||
// ---------- has_user / id / macaddr ------------------------------------------
|
||||
|
||||
void test_copy_user_sets_has_user_bit(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User u = makeUser("Kevin Hester", "kh");
|
||||
TEST_ASSERT_FALSE((lite.bitfield & NODEINFO_BITFIELD_HAS_USER_MASK) != 0);
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_HAS_USER_MASK) != 0);
|
||||
}
|
||||
|
||||
void test_convert_to_user_id_derived_from_nodenum(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0x12345678;
|
||||
meshtastic_User u = TypeConversions::ConvertToUser(&lite);
|
||||
TEST_ASSERT_EQUAL_STRING("!12345678", u.id);
|
||||
}
|
||||
|
||||
void test_convert_to_user_zero_fills_macaddr(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0xCAFEBABE;
|
||||
meshtastic_User u = TypeConversions::ConvertToUser(&lite);
|
||||
uint8_t zeros[sizeof(u.macaddr)] = {0};
|
||||
TEST_ASSERT_EQUAL_MEMORY(zeros, u.macaddr, sizeof(u.macaddr));
|
||||
}
|
||||
|
||||
// ---------- long_name truncation ---------------------------------------------
|
||||
|
||||
void test_long_name_short_passes_through(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User u = makeUser("Kevin Hester", "kh"); // 12 chars, fits
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||
TEST_ASSERT_EQUAL_STRING("Kevin Hester", lite.long_name);
|
||||
}
|
||||
|
||||
void test_long_name_exact_24_fits(void)
|
||||
{
|
||||
// 24 chars -> stored as 24 chars + NUL inside char[25].
|
||||
const char *exact24 = "abcdefghijklmnopqrstuvwx";
|
||||
TEST_ASSERT_EQUAL_INT(24, (int)strlen(exact24));
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User u = makeUser(exact24, "ex");
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||
TEST_ASSERT_EQUAL_STRING(exact24, lite.long_name);
|
||||
TEST_ASSERT_EQUAL_INT(24, (int)strlen(lite.long_name));
|
||||
}
|
||||
|
||||
void test_long_name_truncates_when_too_long(void)
|
||||
{
|
||||
// 33 chars in, must fit in 24 + NUL.
|
||||
const char *tooLong = "North-County Search & Rescue Base";
|
||||
TEST_ASSERT_EQUAL_INT(33, (int)strlen(tooLong));
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User u = makeUser(tooLong, "nc");
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||
TEST_ASSERT_EQUAL_INT(24, (int)strlen(lite.long_name));
|
||||
TEST_ASSERT_EQUAL_STRING_LEN(tooLong, lite.long_name, 24);
|
||||
TEST_ASSERT_EQUAL_INT('\0', lite.long_name[24]);
|
||||
}
|
||||
|
||||
void test_long_name_round_trip_to_wire(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User in = makeUser("Mountain Repeater Site E", "mr"); // exactly 24
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||
TEST_ASSERT_EQUAL_STRING(in.long_name, out.long_name);
|
||||
}
|
||||
|
||||
void test_long_name_truncated_utf8_boundary_sanitized(void)
|
||||
{
|
||||
// Suffix the 24th byte with the start of a 4-byte emoji; truncation should
|
||||
// leave the dangling bytes for sanitizeUtf8 to replace with '?'.
|
||||
char input[40] = {0};
|
||||
memset(input, 'a', 22); // 22 ASCII
|
||||
input[22] = static_cast<char>(0xF0); // emoji lead byte at position 22
|
||||
input[23] = static_cast<char>(0x9F); // continuation
|
||||
input[24] = static_cast<char>(0xA4); // continuation - past the cap
|
||||
input[25] = static_cast<char>(0x96); // continuation - past the cap
|
||||
input[26] = '\0';
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User u = makeUser(input, "u8");
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||
// 24 bytes survive plus NUL. sanitizeUtf8 should have turned the dangling
|
||||
// multi-byte head into '?' since its continuation bytes were chopped.
|
||||
TEST_ASSERT_EQUAL_INT(24, (int)strlen(lite.long_name));
|
||||
for (int i = 0; i < 22; ++i)
|
||||
TEST_ASSERT_EQUAL_INT('a', lite.long_name[i]);
|
||||
// The 4-byte sequence got truncated mid-codepoint; sanitizeUtf8 replaces
|
||||
// any invalid lead/continuation byte with '?'.
|
||||
TEST_ASSERT_EQUAL_INT('?', lite.long_name[22]);
|
||||
TEST_ASSERT_EQUAL_INT('?', lite.long_name[23]);
|
||||
}
|
||||
|
||||
// ---------- short_name truncation --------------------------------------------
|
||||
|
||||
void test_short_name_passes_through(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User u = makeUser("Test", "abcd"); // 4 chars, fits short_name[5]
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||
TEST_ASSERT_EQUAL_STRING("abcd", lite.short_name);
|
||||
}
|
||||
|
||||
void test_short_name_truncates_when_too_long(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User u = makeUser("Test", "abcdefgh");
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||
TEST_ASSERT_EQUAL_INT(4, (int)strlen(lite.short_name));
|
||||
TEST_ASSERT_EQUAL_INT('\0', lite.short_name[4]);
|
||||
}
|
||||
|
||||
// ---------- bitfield collapse + round-trip per bool --------------------------
|
||||
|
||||
void test_bitfield_is_licensed_round_trip(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User in = makeUser("a", "a");
|
||||
in.is_licensed = true;
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_LICENSED_MASK) != 0);
|
||||
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||
TEST_ASSERT_TRUE(out.is_licensed);
|
||||
|
||||
in.is_licensed = false;
|
||||
meshtastic_NodeInfoLite lite2 = meshtastic_NodeInfoLite_init_default;
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite2, in);
|
||||
TEST_ASSERT_FALSE((lite2.bitfield & NODEINFO_BITFIELD_IS_LICENSED_MASK) != 0);
|
||||
meshtastic_User out2 = TypeConversions::ConvertToUser(&lite2);
|
||||
TEST_ASSERT_FALSE(out2.is_licensed);
|
||||
}
|
||||
|
||||
void test_bitfield_unmessagable_present_and_true(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User in = makeUser("a", "a");
|
||||
in.has_is_unmessagable = true;
|
||||
in.is_unmessagable = true;
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK) != 0);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK) != 0);
|
||||
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||
TEST_ASSERT_TRUE(out.has_is_unmessagable);
|
||||
TEST_ASSERT_TRUE(out.is_unmessagable);
|
||||
}
|
||||
|
||||
void test_bitfield_unmessagable_present_but_false(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User in = makeUser("a", "a");
|
||||
in.has_is_unmessagable = true;
|
||||
in.is_unmessagable = false;
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK) != 0);
|
||||
TEST_ASSERT_FALSE((lite.bitfield & NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK) != 0);
|
||||
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||
TEST_ASSERT_TRUE(out.has_is_unmessagable);
|
||||
TEST_ASSERT_FALSE(out.is_unmessagable);
|
||||
}
|
||||
|
||||
void test_bitfield_unmessagable_absent(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User in = makeUser("a", "a");
|
||||
in.has_is_unmessagable = false;
|
||||
in.is_unmessagable = true; // explicitly true to make sure absence still wins
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
TEST_ASSERT_FALSE((lite.bitfield & NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK) != 0);
|
||||
TEST_ASSERT_FALSE((lite.bitfield & NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK) != 0);
|
||||
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||
TEST_ASSERT_FALSE(out.has_is_unmessagable);
|
||||
TEST_ASSERT_FALSE(out.is_unmessagable);
|
||||
}
|
||||
|
||||
void test_copy_user_preserves_unrelated_bits(void)
|
||||
{
|
||||
// Pre-set is_muted and is_key_manually_verified - CopyUserToNodeInfoLite
|
||||
// must not stomp them.
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.bitfield = NODEINFO_BITFIELD_IS_MUTED_MASK | NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK |
|
||||
NODEINFO_BITFIELD_VIA_MQTT_MASK | NODEINFO_BITFIELD_IS_FAVORITE_MASK | NODEINFO_BITFIELD_IS_IGNORED_MASK;
|
||||
meshtastic_User in = makeUser("a", "a");
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) != 0);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_VIA_MQTT_MASK) != 0);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_FAVORITE_MASK) != 0);
|
||||
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_IGNORED_MASK) != 0);
|
||||
}
|
||||
|
||||
void test_bitfield_bits_are_independent(void)
|
||||
{
|
||||
// Set just is_licensed, verify only that bit (and HAS_USER) is set.
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User in = makeUser("a", "a");
|
||||
in.is_licensed = true;
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
const uint32_t expected = NODEINFO_BITFIELD_HAS_USER_MASK | NODEINFO_BITFIELD_IS_LICENSED_MASK;
|
||||
TEST_ASSERT_EQUAL_HEX32(expected, lite.bitfield);
|
||||
}
|
||||
|
||||
// ---------- public_key / hw_model / role pass-through ------------------------
|
||||
|
||||
void test_public_key_round_trip(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User in = makeUser("a", "a");
|
||||
in.public_key.size = 32;
|
||||
for (int i = 0; i < 32; ++i)
|
||||
in.public_key.bytes[i] = (uint8_t)(i ^ 0xA5);
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
TEST_ASSERT_EQUAL_INT(32, lite.public_key.size);
|
||||
TEST_ASSERT_EQUAL_MEMORY(in.public_key.bytes, lite.public_key.bytes, 32);
|
||||
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||
TEST_ASSERT_EQUAL_INT(32, out.public_key.size);
|
||||
TEST_ASSERT_EQUAL_MEMORY(in.public_key.bytes, out.public_key.bytes, 32);
|
||||
}
|
||||
|
||||
void test_hw_model_and_role_round_trip(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
meshtastic_User in = makeUser("a", "a");
|
||||
in.hw_model = meshtastic_HardwareModel_HELTEC_V3;
|
||||
in.role = meshtastic_Config_DeviceConfig_Role_ROUTER;
|
||||
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||
TEST_ASSERT_EQUAL_INT(meshtastic_HardwareModel_HELTEC_V3, lite.hw_model);
|
||||
TEST_ASSERT_EQUAL_INT(meshtastic_Config_DeviceConfig_Role_ROUTER, lite.role);
|
||||
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||
TEST_ASSERT_EQUAL_INT(meshtastic_HardwareModel_HELTEC_V3, out.hw_model);
|
||||
TEST_ASSERT_EQUAL_INT(meshtastic_Config_DeviceConfig_Role_ROUTER, out.role);
|
||||
}
|
||||
|
||||
// ---------- ConvertToNodeInfo (3-arg) ----------------------------------------
|
||||
|
||||
void test_convert_to_node_info_thin_omits_position_and_metrics(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0xAA;
|
||||
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfoThin(&lite);
|
||||
TEST_ASSERT_FALSE(info.has_position);
|
||||
TEST_ASSERT_FALSE(info.has_device_metrics);
|
||||
}
|
||||
|
||||
void test_convert_to_node_info_3arg_with_position(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0xAA;
|
||||
meshtastic_PositionLite pos = meshtastic_PositionLite_init_default;
|
||||
pos.latitude_i = 374200000;
|
||||
pos.longitude_i = -1221000000;
|
||||
pos.altitude = 30;
|
||||
pos.time = 12345;
|
||||
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, &pos, nullptr);
|
||||
TEST_ASSERT_TRUE(info.has_position);
|
||||
TEST_ASSERT_FALSE(info.has_device_metrics);
|
||||
TEST_ASSERT_EQUAL_INT32(374200000, info.position.latitude_i);
|
||||
TEST_ASSERT_EQUAL_INT32(-1221000000, info.position.longitude_i);
|
||||
TEST_ASSERT_EQUAL_INT32(30, info.position.altitude);
|
||||
TEST_ASSERT_EQUAL_UINT32(12345, info.position.time);
|
||||
}
|
||||
|
||||
void test_convert_to_node_info_3arg_with_metrics(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0xAA;
|
||||
meshtastic_DeviceMetrics dm = meshtastic_DeviceMetrics_init_default;
|
||||
dm.battery_level = 88;
|
||||
dm.has_battery_level = true;
|
||||
dm.voltage = 3.71f;
|
||||
dm.has_voltage = true;
|
||||
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, &dm);
|
||||
TEST_ASSERT_FALSE(info.has_position);
|
||||
TEST_ASSERT_TRUE(info.has_device_metrics);
|
||||
TEST_ASSERT_EQUAL_INT(88, info.device_metrics.battery_level);
|
||||
TEST_ASSERT_TRUE(info.device_metrics.has_battery_level);
|
||||
TEST_ASSERT_EQUAL_FLOAT(3.71f, info.device_metrics.voltage);
|
||||
}
|
||||
|
||||
void test_convert_to_node_info_3arg_null_inputs(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0xAA;
|
||||
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||
TEST_ASSERT_FALSE(info.has_position);
|
||||
TEST_ASSERT_FALSE(info.has_device_metrics);
|
||||
}
|
||||
|
||||
void test_convert_to_node_info_extracts_bitfield_bools(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0xBB;
|
||||
lite.bitfield = NODEINFO_BITFIELD_VIA_MQTT_MASK | NODEINFO_BITFIELD_IS_FAVORITE_MASK | NODEINFO_BITFIELD_IS_IGNORED_MASK |
|
||||
NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK | NODEINFO_BITFIELD_IS_MUTED_MASK;
|
||||
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||
TEST_ASSERT_TRUE(info.via_mqtt);
|
||||
TEST_ASSERT_TRUE(info.is_favorite);
|
||||
TEST_ASSERT_TRUE(info.is_ignored);
|
||||
TEST_ASSERT_TRUE(info.is_key_manually_verified);
|
||||
TEST_ASSERT_TRUE(info.is_muted);
|
||||
}
|
||||
|
||||
void test_convert_to_node_info_extracts_bitfield_bools_none_set(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0xBB;
|
||||
lite.bitfield = 0;
|
||||
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||
TEST_ASSERT_FALSE(info.via_mqtt);
|
||||
TEST_ASSERT_FALSE(info.is_favorite);
|
||||
TEST_ASSERT_FALSE(info.is_ignored);
|
||||
TEST_ASSERT_FALSE(info.is_key_manually_verified);
|
||||
TEST_ASSERT_FALSE(info.is_muted);
|
||||
}
|
||||
|
||||
void test_convert_to_node_info_user_only_when_has_user_bit_set(void)
|
||||
{
|
||||
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||
lite.num = 0x01;
|
||||
strcpy(lite.long_name, "Alpha");
|
||||
strcpy(lite.short_name, "A");
|
||||
// No HAS_USER bit -> user fields ignored on emit.
|
||||
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||
TEST_ASSERT_FALSE(info.has_user);
|
||||
|
||||
lite.bitfield |= NODEINFO_BITFIELD_HAS_USER_MASK;
|
||||
meshtastic_NodeInfo info2 = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||
TEST_ASSERT_TRUE(info2.has_user);
|
||||
TEST_ASSERT_EQUAL_STRING("Alpha", info2.user.long_name);
|
||||
TEST_ASSERT_EQUAL_STRING("A", info2.user.short_name);
|
||||
TEST_ASSERT_EQUAL_STRING("!00000001", info2.user.id);
|
||||
}
|
||||
|
||||
// ---------- entry point -------------------------------------------------------
|
||||
|
||||
void setup()
|
||||
{
|
||||
delay(10);
|
||||
delay(2000);
|
||||
initializeTestEnvironment();
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_copy_user_sets_has_user_bit);
|
||||
RUN_TEST(test_convert_to_user_id_derived_from_nodenum);
|
||||
RUN_TEST(test_convert_to_user_zero_fills_macaddr);
|
||||
RUN_TEST(test_long_name_short_passes_through);
|
||||
RUN_TEST(test_long_name_exact_24_fits);
|
||||
RUN_TEST(test_long_name_truncates_when_too_long);
|
||||
RUN_TEST(test_long_name_round_trip_to_wire);
|
||||
RUN_TEST(test_long_name_truncated_utf8_boundary_sanitized);
|
||||
RUN_TEST(test_short_name_passes_through);
|
||||
RUN_TEST(test_short_name_truncates_when_too_long);
|
||||
RUN_TEST(test_bitfield_is_licensed_round_trip);
|
||||
RUN_TEST(test_bitfield_unmessagable_present_and_true);
|
||||
RUN_TEST(test_bitfield_unmessagable_present_but_false);
|
||||
RUN_TEST(test_bitfield_unmessagable_absent);
|
||||
RUN_TEST(test_copy_user_preserves_unrelated_bits);
|
||||
RUN_TEST(test_bitfield_bits_are_independent);
|
||||
RUN_TEST(test_public_key_round_trip);
|
||||
RUN_TEST(test_hw_model_and_role_round_trip);
|
||||
RUN_TEST(test_convert_to_node_info_thin_omits_position_and_metrics);
|
||||
RUN_TEST(test_convert_to_node_info_3arg_with_position);
|
||||
RUN_TEST(test_convert_to_node_info_3arg_with_metrics);
|
||||
RUN_TEST(test_convert_to_node_info_3arg_null_inputs);
|
||||
RUN_TEST(test_convert_to_node_info_extracts_bitfield_bools);
|
||||
RUN_TEST(test_convert_to_node_info_extracts_bitfield_bools_none_set);
|
||||
RUN_TEST(test_convert_to_node_info_user_only_when_has_user_bit_set);
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
Reference in New Issue
Block a user