Block coordinate traffic on configured event channels (#11045)

* Block coordinate traffic on configured event channels

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Suppress event coordinates in reliable relay paths

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Reject blocked phone coordinates before rate limiting

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Prevent event coordinates from reaching MQTT

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Add event coordinate policy preference

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Test event coordinate policy in native CI

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Make event policy test tolerate a full NodeDB

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Test Router event coordinate enforcement

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Test PhoneAPI event coordinate retry handling

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Test reliable event coordinate suppression

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Test MQTT event coordinate suppression

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Run event policy behavioral suites in native CI

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* tests: address CodeRabbit review feedback

- test_event_channel_phone_api: complete the setUp/tearDown save-restore
  pair. GlobalState now carries cryptLock and myNodeInfo; setUp() nulls
  cryptLock before constructing MockRouter (Router's ctor asserts it is
  unset), and tearDown() restores both so the suite leaves no global
  mutated. Not reachable today - the globals start null in this binary -
  but the pair was asymmetric.

- Replace the strcpy calls this branch added on Channel.settings.name
  (char[12]) with the bounded form the rest of the test tree already uses,
  strncpy(dst, src, sizeof(dst) - 1). Covers the flagged site in
  test_nexthop_routing plus the six equivalents in
  test_event_channel_phone_api, test_mqtt and test_position_precision,
  which trip the same ast-grep dangerous-buffer-functions-cpp rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ayysasha
2026-08-11 12:34:08 +00:00
committed by GitHub
co-authored by Sisyphus Claude Opus 5 Ben Meadors
parent fb17f6ddbe
commit 546b9d9e40
18 changed files with 1492 additions and 36 deletions
+125 -1
View File
@@ -338,16 +338,64 @@ const meshtastic_MeshPacket encrypted = {
.encrypted = {.size = 0},
.id = 3,
};
void configureCoordinatePolicyChannels(bool eventChannelIsPrimary = true)
{
memset(&channelFile, 0, sizeof(channelFile));
channelFile.channels_count = 2;
auto &eventChannel = channelFile.channels[0];
eventChannel.index = 0;
eventChannel.has_settings = true;
strncpy(eventChannel.settings.name, "everyone", sizeof(eventChannel.settings.name) - 1);
eventChannel.settings.uplink_enabled = true;
eventChannel.settings.downlink_enabled = true;
eventChannel.role = eventChannelIsPrimary ? meshtastic_Channel_Role_PRIMARY : meshtastic_Channel_Role_SECONDARY;
#ifdef USERPREFS_CHANNEL_0_PSK
static const uint8_t configuredEventPsk[] = USERPREFS_CHANNEL_0_PSK;
eventChannel.settings.psk.size = sizeof(configuredEventPsk);
memcpy(eventChannel.settings.psk.bytes, configuredEventPsk, sizeof(configuredEventPsk));
#endif
auto &privateChannel = channelFile.channels[1];
privateChannel.index = 1;
privateChannel.has_settings = true;
strncpy(privateChannel.settings.name, "private", sizeof(privateChannel.settings.name) - 1);
privateChannel.settings.psk.size = 32;
memset(privateChannel.settings.psk.bytes, 0xab, privateChannel.settings.psk.size);
privateChannel.settings.uplink_enabled = true;
privateChannel.settings.downlink_enabled = true;
privateChannel.role = eventChannelIsPrimary ? meshtastic_Channel_Role_SECONDARY : meshtastic_Channel_Role_PRIMARY;
channels.onConfigChanged();
}
meshtastic_MeshPacket makePositionPacket(ChannelIndex channel)
{
meshtastic_MeshPacket packet = decoded;
packet.to = NODENUM_BROADCAST;
packet.channel = channel;
packet.decoded.portnum = meshtastic_PortNum_POSITION_APP;
return packet;
}
void clearPublicationState()
{
TEST_ASSERT_EQUAL(0, unitTest->queueSize());
pubsub->published_.clear();
mockMeshService->messages_.clear();
}
} // namespace
// Initialize mocks and configuration before running each test.
void setUp(void)
{
config = meshtastic_LocalConfig_init_zero;
memset(&config, 0, sizeof(config));
moduleConfig.mqtt =
meshtastic_ModuleConfig_MQTTConfig{.enabled = true, .map_reporting_enabled = true, .has_map_report_settings = true};
moduleConfig.mqtt.map_report_settings = meshtastic_ModuleConfig_MapReportSettings{
.publish_interval_secs = 0, .position_precision = 14, .should_report_location = true};
memset(&channelFile, 0, sizeof(channelFile));
channelFile.channels[0] = meshtastic_Channel{
.index = 0,
.has_settings = true,
@@ -355,6 +403,7 @@ void setUp(void)
.role = meshtastic_Channel_Role_PRIMARY,
};
channelFile.channels_count = 1;
channels.onConfigChanged();
owner = meshtastic_User{.id = "!12345678"};
myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 0x12345678}; // Match the expected gateway ID in topic
localPosition =
@@ -412,6 +461,50 @@ void test_sendDirectlyConnectedEncrypted(void)
TEST_ASSERT_EQUAL(encrypted.id, env.packet->id);
}
void test_eventPositionPublicationFollowsCompileTimePolicy(void)
{
configureCoordinatePolicyChannels();
clearPublicationState();
const meshtastic_MeshPacket position = makePositionPacket(0);
mqtt->onSend(encrypted, position, 0);
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL && defined(USERPREFS_CHANNEL_0_PSK)
TEST_ASSERT_TRUE(pubsub->published_.empty());
TEST_ASSERT_EQUAL(0, unitTest->queueSize());
#else
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
#endif
}
void test_privatePositionStillPublishesWithEventPolicy(void)
{
configureCoordinatePolicyChannels();
clearPublicationState();
const meshtastic_MeshPacket position = makePositionPacket(1);
mqtt->onSend(encrypted, position, 1);
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
TEST_ASSERT_EQUAL_STRING("msh/2/e/private/!12345678", pubsub->published_.front().first.c_str());
}
void test_explicitPkiPositionStillPublishesWithEventPolicy(void)
{
configureCoordinatePolicyChannels();
clearPublicationState();
meshtastic_MeshPacket position = makePositionPacket(0);
meshtastic_MeshPacket encryptedPki = encrypted;
position.to = 2;
position.pki_encrypted = true;
encryptedPki.pki_encrypted = true;
mqtt->onSend(encryptedPki, position, 0);
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
TEST_ASSERT_EQUAL_STRING("msh/2/e/PKI/!12345678", pubsub->published_.front().first.c_str());
}
// Verify that the decoded MeshPacket is proxied through the MeshService when encryption_enabled = false.
void test_proxyToMeshServiceDecoded(void)
{
@@ -918,6 +1011,32 @@ void test_reportToMapDefaultImprecise(void)
TEST_ASSERT_EQUAL_STRING("msh/2/map/", topic.c_str());
}
void test_eventPrimaryMapReportFollowsCompileTimePolicy(void)
{
configureCoordinatePolicyChannels();
clearPublicationState();
unitTest->reportToMap();
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL && defined(USERPREFS_CHANNEL_0_PSK)
TEST_ASSERT_TRUE(pubsub->published_.empty());
TEST_ASSERT_EQUAL(0, unitTest->queueSize());
#else
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
#endif
}
void test_privatePrimaryMapReportStillPublishesWithEventPolicy(void)
{
configureCoordinatePolicyChannels(false);
clearPublicationState();
unitTest->reportToMap();
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
TEST_ASSERT_EQUAL_STRING("msh/2/map/", pubsub->published_.front().first.c_str());
}
// Location is sent over the phone proxy.
void test_reportToMapImpreciseProxied(void)
{
@@ -1135,6 +1254,9 @@ void setup()
UNITY_BEGIN();
RUN_TEST(test_sendDirectlyConnectedDecoded);
RUN_TEST(test_sendDirectlyConnectedEncrypted);
RUN_TEST(test_eventPositionPublicationFollowsCompileTimePolicy);
RUN_TEST(test_privatePositionStillPublishesWithEventPolicy);
RUN_TEST(test_explicitPkiPositionStillPublishesWithEventPolicy);
RUN_TEST(test_proxyToMeshServiceDecoded);
RUN_TEST(test_proxyToMeshServiceEncrypted);
RUN_TEST(test_dontMqttMeOnPublicServer);
@@ -1171,6 +1293,8 @@ void setup()
RUN_TEST(test_publishTextMessageDirect);
RUN_TEST(test_publishTextMessageWithProxy);
RUN_TEST(test_reportToMapDefaultImprecise);
RUN_TEST(test_eventPrimaryMapReportFollowsCompileTimePolicy);
RUN_TEST(test_privatePrimaryMapReportStillPublishesWithEventPolicy);
RUN_TEST(test_reportToMapImpreciseProxied);
RUN_TEST(test_usingDefaultServer);
RUN_TEST(test_usingDefaultServerWithPort);