Validate companion repeater status responses

This commit is contained in:
mikecarper committed 2026-08-25 10:27:04 -07:00
1 parent 7077283e95
commit 8b7c5fafb4
4 files changed
+70 -7

No files matched your search

+13 -7
View File
@@ -2,6 +2,7 @@
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#include <helpers/CompanionStatusResponse.h>
#include <helpers/IdentityGeneration.h>
#include "helpers/radiolib/RXPowerSaving.h"
#include "helpers/radiolib/RxBoostedGainDefaults.h"
@@ -1163,13 +1164,19 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
clearTerminalLogin();
}
#endif
} else if (len > 4 && // check for status response
pending_status &&
memcmp(&pending_status, contact.id.pub_key, 4) == 0 // legacy matching scheme
// FUTURE: tag == pending_status
) {
} else if (mesh::companionStatusTagMatches(pending_status, tag)) {
pending_status = 0;
// Do not expose a truncated or unrelated response as repeater statistics.
// The app parses at least 48 bytes and otherwise throws a RangeError.
if (!mesh::companionStatusResponseIsLongEnough(len)) {
MESH_DEBUG_PRINTLN(
"onContactResponse(), short status response: len=%u, expected>=%u",
(unsigned)len,
(unsigned)mesh::COMPANION_MIN_STATUS_RESPONSE_SIZE);
return;
}
int i = 0;
out_frame[i++] = PUSH_CODE_STATUS_RESPONSE;
out_frame[i++] = 0; // reserved
@@ -3366,8 +3373,7 @@ void MyMesh::handleCmdFrame(size_t len) {
writeErrFrame(ERR_CODE_TABLE_FULL);
} else {
clearPendingReqs();
// FUTURE: pending_status = tag; // match this in onContactResponse()
memcpy(&pending_status, recipient->id.pub_key, 4); // legacy matching scheme
pending_status = tag; // match the reflected tag in onContactResponse()
out_frame[0] = RESP_CODE_SENT;
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
memcpy(&out_frame[2], &tag, 4);
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
namespace mesh {
// A repeater/room-server status response starts with the reflected four-byte
// request tag. The app currently parses 48 bytes of status fields after that
// tag; newer firmware may append additional counters.
static constexpr size_t COMPANION_STATUS_RESPONSE_TAG_SIZE = 4;
static constexpr size_t COMPANION_MIN_STATUS_DATA_SIZE = 48;
static constexpr size_t COMPANION_MIN_STATUS_RESPONSE_SIZE =
COMPANION_STATUS_RESPONSE_TAG_SIZE + COMPANION_MIN_STATUS_DATA_SIZE;
inline bool companionStatusTagMatches(uint32_t pending_tag,
uint32_t response_tag) {
return pending_tag != 0 && response_tag == pending_tag;
}
inline bool companionStatusResponseIsLongEnough(size_t response_len) {
return response_len >= COMPANION_MIN_STATUS_RESPONSE_SIZE;
}
} // namespace mesh
+1
View File
@@ -48,6 +48,7 @@ does not reflect the GoogleTest count -- run the built binary directly
| `test_identity_generation` | `src/helpers/IdentityGeneration.h` | reserved-prefix rejection; bounded retries; final provisioned attempt; fail-closed exhaustion |
| `test_remote_cli_reply_cache` | `src/helpers/RemoteCliReplyCache.h`, `src/helpers/RemoteCliRequest.h`, `src/helpers/RemoteCliTimeout.h` | authenticated logical-request matching; bounded recent-reply history; backward-compatible retry identity; 300% response timeout; empty-response completion; on-air truncation and clearing |
| `test_companion_frame_queue` | `src/helpers/CompanionFrameQueue.h` | response/required/best-effort classification; reserved capacity; stable priority; safe eviction; message-waiting coalescing |
| `test_companion_status_response` | `src/helpers/CompanionStatusResponse.h` | request-tag correlation and minimum status-response length, including rejection of the three-entry ACL payload that previously masqueraded as status |
| `test_serial_mode_switch` | `src/helpers/ArduinoSerialInterface.cpp`, `src/helpers/MultiSerialInterface.h` | terminal/seeder control-sequence recognition and passthrough ownership; queued/atomic USB output under backpressure and short writes; partial-frame busy state; requester-affine replies, locked contact streams, and Bluetooth-only pairing routing |
| `test_ble_tx_stall_watchdog` | `src/helpers/BleTxStallWatchdog.h` | exact BLE fragment progress; blocked-reply timeout; rollover-safe elapsed time; disconnect recovery retry and completion |
| `test_atomic_file_writer` | `src/helpers/AtomicFileWriter.h` | verified temporary-file commit; short-write, readback, validation, and rename failures; preservation of the live file and stale-temp cleanup |
@@ -0,0 +1,31 @@
#include <gtest/gtest.h>
#include <helpers/CompanionStatusResponse.h>
TEST(CompanionStatusResponse, RequiresTheExactPendingRequestTag) {
EXPECT_FALSE(mesh::companionStatusTagMatches(0, 0));
EXPECT_FALSE(mesh::companionStatusTagMatches(0, 0x12345678));
EXPECT_FALSE(mesh::companionStatusTagMatches(0x12345678, 0x87654321));
EXPECT_TRUE(mesh::companionStatusTagMatches(0x12345678, 0x12345678));
}
TEST(CompanionStatusResponse, RejectsThreeEntryAclPayloadAsStatus) {
// An ACL response is tag + seven bytes per entry. Three entries produced
// the 21-byte payload that the app incorrectly attempted to parse as stats.
constexpr size_t three_entry_acl_response = 4 + (3 * 7);
EXPECT_FALSE(
mesh::companionStatusResponseIsLongEnough(three_entry_acl_response));
}
TEST(CompanionStatusResponse, AcceptsLegacyMinimumAndCurrentStatusSizes) {
EXPECT_FALSE(mesh::companionStatusResponseIsLongEnough(
mesh::COMPANION_MIN_STATUS_RESPONSE_SIZE - 1));
EXPECT_TRUE(mesh::companionStatusResponseIsLongEnough(
mesh::COMPANION_MIN_STATUS_RESPONSE_SIZE));
EXPECT_TRUE(mesh::companionStatusResponseIsLongEnough(4 + 56));
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}