Files
Thomas Göttgens 9fbc176e91 Extend userPrefs coverage to the whole channel table and the missing config fields (#11624)
* Extend userPrefs coverage to the whole channel table and the missing config fields

initDefaultChannel() handled only indices 0-2, so USERPREFS_CHANNELS_TO_WRITE above 3 produced live secondary channels carrying the public default PSK; it now covers all eight slots, with bin/platformio-custom.py completing every field of a configured index so indices 0-2 stay byte-identical. Adds USERPREFS_CHANNEL_<n>_IS_MUTED, USERPREFS_CONFIG_DEVICE_REBROADCAST_MODE, USERPREFS_CONFIG_DEVICE_NODE_INFO_BROADCAST_SECS, USERPREFS_CONFIG_LORA_CONFIG_OK_TO_MQTT, USERPREFS_CONFIG_SECURITY_IS_MANAGED and USERPREFS_CANNED_MESSAGES, applied after installRoleDefaults() and validated the way AdminModule validates a set-config. Adds test_userprefs_channels, covering the configured table under coverage-channel-table and the stock defaults under every other env.

* Address review: hex channel count, PSK width assert, canned-message termination

USERPREFS_CHANNELS_TO_WRITE now parses 0x-prefixed hex, matching the format
userPrefs.jsonc documents, without int(x, 0)'s rejection of a leading-zero
decimal such as "03". A static_assert rejects a USERPREFS_CHANNEL_<n>_PSK
literal wider than psk.bytes, which memcpy would otherwise write over the fields
after it. The USERPREFS_CANNED_MESSAGES copy keeps strncpy's zero-padding and
terminates explicitly, rather than shortening the length, which would have left
the last byte unwritten.
2026-08-27 15:00:59 +00:00

180 lines
6.8 KiB
C++

// Channels::initDefaults() must populate every index named by USERPREFS_CHANNELS_TO_WRITE, and an
// index configured in part must keep the firmware's own value for every field left out.
//
// Under coverage-channel-table / native-windows-channel-table userprefs_fixture.h is -include'd and
// the configured cases run; under any other env the suite asserts the stock defaults instead.
#include "Channels.h"
#include "MeshTypes.h" // Include BEFORE TestUtil.h (provides NodeNum, isBroadcast, etc.)
#include "NodeDB.h" // channelFile
#include "TestUtil.h"
#include "mesh-pb-constants.h"
#include <cstdio>
#include <cstring>
#include <unity.h>
#if defined(ARCH_PORTDUINO)
#define UPC_TEST_ENTRY extern "C"
#else
#define UPC_TEST_ENTRY
#endif
// The well-known default PSK in its 1-byte short form, i.e. what initDefaultChannel() leaves on an
// index no userPref touched.
static const uint8_t kDefaultPskShortForm = 0x01;
void setUp(void)
{
memset(&channelFile, 0, sizeof(channelFile));
channels.initDefaults();
}
void tearDown(void) {}
static void expectShortFormDefaultPsk(const meshtastic_ChannelSettings &s)
{
TEST_ASSERT_EQUAL_UINT(1, s.psk.size);
TEST_ASSERT_EQUAL_UINT8(kDefaultPskShortForm, s.psk.bytes[0]);
}
// An index initDefaultChannel() wrote but no userPref configured: default PSK, empty name, position
// sharing off, nothing muted, no MQTT.
static void expectStockChannel(uint8_t idx)
{
const meshtastic_Channel &ch = channels.getByIndex(idx);
TEST_ASSERT_TRUE(ch.has_settings);
TEST_ASSERT_TRUE(ch.settings.has_module_settings);
expectShortFormDefaultPsk(ch.settings);
TEST_ASSERT_EQUAL_STRING("", ch.settings.name);
TEST_ASSERT_EQUAL_UINT(0, ch.settings.module_settings.position_precision);
TEST_ASSERT_FALSE(ch.settings.module_settings.is_muted);
TEST_ASSERT_FALSE(ch.settings.uplink_enabled);
TEST_ASSERT_FALSE(ch.settings.downlink_enabled);
}
#ifdef USERPREFS_CHANNELS_TO_WRITE
static const uint8_t kChannel0Psk[] = USERPREFS_CHANNEL_0_PSK;
static const uint8_t kChannel3Psk[] = USERPREFS_CHANNEL_3_PSK;
// The bug this table replaces: USERPREFS_CHANNELS_TO_WRITE past 3 produced live secondary channels
// carrying the public default PSK instead of the vendor's, because initDefaultChannel()'s switch
// stopped at case 2.
static void test_index_beyond_two_gets_its_configured_psk()
{
const meshtastic_ChannelSettings &s = channels.getByIndex(3).settings;
TEST_ASSERT_EQUAL_UINT(sizeof(kChannel3Psk), s.psk.size);
TEST_ASSERT_EQUAL_UINT8_ARRAY(kChannel3Psk, s.psk.bytes, sizeof(kChannel3Psk));
TEST_ASSERT_EQUAL_STRING(USERPREFS_CHANNEL_3_NAME, s.name);
}
static void test_index_zero_matches_its_userprefs()
{
const meshtastic_ChannelSettings &s = channels.getByIndex(0).settings;
TEST_ASSERT_EQUAL_UINT(sizeof(kChannel0Psk), s.psk.size);
TEST_ASSERT_EQUAL_UINT8_ARRAY(kChannel0Psk, s.psk.bytes, sizeof(kChannel0Psk));
TEST_ASSERT_EQUAL_STRING(USERPREFS_CHANNEL_0_NAME, s.name);
TEST_ASSERT_EQUAL_UINT(USERPREFS_CHANNEL_0_PRECISION, s.module_settings.position_precision);
TEST_ASSERT_TRUE(s.module_settings.is_muted);
TEST_ASSERT_TRUE(s.uplink_enabled);
TEST_ASSERT_FALSE(s.downlink_enabled);
}
static void test_every_written_index_has_a_role()
{
for (int i = 0; i < USERPREFS_CHANNELS_TO_WRITE; i++) {
const meshtastic_Channel &ch = channels.getByIndex(i);
TEST_ASSERT_TRUE(ch.has_settings);
TEST_ASSERT_TRUE(ch.settings.has_module_settings);
TEST_ASSERT_EQUAL(i == 0 ? meshtastic_Channel_Role_PRIMARY : meshtastic_Channel_Role_SECONDARY, ch.role);
}
}
// Index 1 is inside CHANNELS_TO_WRITE but has no userPref of its own.
static void test_unconfigured_index_inside_range_is_stock()
{
expectStockChannel(1);
}
// Index 4 sets only a name upstream; the generator fills the rest with the values
// initDefaultChannel() would otherwise have left in place, so a partial config is not a way to
// accidentally ship uplink or a raised position precision.
static void test_partly_configured_index_keeps_firmware_defaults()
{
const meshtastic_ChannelSettings &s = channels.getByIndex(4).settings;
TEST_ASSERT_EQUAL_STRING(USERPREFS_CHANNEL_4_NAME, s.name);
expectShortFormDefaultPsk(s);
TEST_ASSERT_EQUAL_UINT(0, s.module_settings.position_precision);
TEST_ASSERT_FALSE(s.module_settings.is_muted);
TEST_ASSERT_FALSE(s.uplink_enabled);
TEST_ASSERT_FALSE(s.downlink_enabled);
}
// Indices past USERPREFS_CHANNELS_TO_WRITE are never handed to initDefaultChannel(), so they must
// stay zeroed rather than picking up a neighbour's key.
static void test_index_past_channels_to_write_is_untouched()
{
for (int i = USERPREFS_CHANNELS_TO_WRITE; i < MAX_NUM_CHANNELS; i++) {
const meshtastic_ChannelSettings &s = channels.getByIndex(i).settings;
TEST_ASSERT_EQUAL_STRING("", s.name);
TEST_ASSERT_EQUAL_UINT(0, s.psk.size);
}
}
// The switch this table replaces used strcpy() into a char[12].
static void test_over_long_name_is_truncated_and_terminated()
{
const meshtastic_ChannelSettings &s = channels.getByIndex(2).settings;
TEST_ASSERT_TRUE(strlen(USERPREFS_CHANNEL_2_NAME) >= sizeof(s.name));
TEST_ASSERT_EQUAL_UINT(sizeof(s.name) - 1, strlen(s.name));
TEST_ASSERT_EQUAL_CHAR('\0', s.name[sizeof(s.name) - 1]);
TEST_ASSERT_EQUAL_MEMORY(USERPREFS_CHANNEL_2_NAME, s.name, sizeof(s.name) - 1);
}
#else // no channel userPrefs: the baseline the table must not have moved
static void test_stock_build_writes_only_index_zero()
{
expectStockChannel(0);
TEST_ASSERT_EQUAL(meshtastic_Channel_Role_PRIMARY, channels.getByIndex(0).role);
for (int i = 1; i < MAX_NUM_CHANNELS; i++) {
const meshtastic_ChannelSettings &s = channels.getByIndex(i).settings;
TEST_ASSERT_EQUAL_STRING("", s.name);
TEST_ASSERT_EQUAL_UINT(0, s.psk.size);
}
}
static void test_stock_build_fills_the_whole_table()
{
TEST_ASSERT_EQUAL_UINT(MAX_NUM_CHANNELS, channelFile.channels_count);
}
#endif
UPC_TEST_ENTRY void setup()
{
initializeTestEnvironment();
UNITY_BEGIN();
#ifdef USERPREFS_CHANNELS_TO_WRITE
printf("\n=== channel table beyond index 2 ===\n");
RUN_TEST(test_index_beyond_two_gets_its_configured_psk);
RUN_TEST(test_index_zero_matches_its_userprefs);
RUN_TEST(test_every_written_index_has_a_role);
printf("\n=== omitted fields and bounds ===\n");
RUN_TEST(test_unconfigured_index_inside_range_is_stock);
RUN_TEST(test_partly_configured_index_keeps_firmware_defaults);
RUN_TEST(test_index_past_channels_to_write_is_untouched);
RUN_TEST(test_over_long_name_is_truncated_and_terminated);
#else
printf("\n=== stock defaults (no channel userPrefs) ===\n");
RUN_TEST(test_stock_build_writes_only_index_zero);
RUN_TEST(test_stock_build_fills_the_whole_table);
#endif
exit(UNITY_END());
}
UPC_TEST_ENTRY void loop() {}