From 48b39f966639dbe5603ff96fd0157e2fdee46125 Mon Sep 17 00:00:00 2001 From: diffwireauto <330573775+diffwireauto@users.noreply.github.com> Date: Thu, 8 Oct 2026 02:57:19 -0400 Subject: [PATCH] fix(#831): clear region send-scope even when setting it throws Pre-PR Gemini review (Epic #815) flagged a scope leak: setChannelSendScope was outside the try, so a lost ack after the firmware applied the scope skipped the clear, and the next unscoped channel send would ride the stale scope. Moved the set inside the try so the finally always clears. Regression test asserts a clear is attempted when the set throws. Co-Authored-By: Claude Opus 4.8 --- lib/connector/meshcore_connector.dart | 5 ++++- test/connector/channel_scope_send_test.dart | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 7b46c113..d4d914bc 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4795,8 +4795,11 @@ class MeshCoreConnector extends ChangeNotifier { 'cannot derive a transport key for region "$regionName"', ); } - await setChannelSendScope(key); try { + // Inside the try so a lost ack on the set (the firmware may already have + // applied the scope) still runs the clear below, rather than leaking the + // scope onto the next unscoped send. + await setChannelSendScope(key); await sendFrame( frame, channelSendQueueId: channelSendQueueId, diff --git a/test/connector/channel_scope_send_test.dart b/test/connector/channel_scope_send_test.dart index d1912c43..404da8a9 100644 --- a/test/connector/channel_scope_send_test.dart +++ b/test/connector/channel_scope_send_test.dart @@ -69,4 +69,19 @@ void main() { expect(sent, isEmpty); }, ); + + test('a failed set-scope still attempts a clear (no scope leak)', () async { + await connector.channelRegionScopeStoreForTest.setScope(0, 'oki'); + var calls = 0; + connector.sendFrameOverrideForTest = (_) { + calls++; + if (calls == 1) throw Exception('lost ack on set-scope'); + }; + await expectLater( + connector.sendChannelFrameScopedForTest(0, frame), + throwsA(isA()), + ); + // call 1 = set-scope (threw); call 2 = clear-scope run from the finally. + expect(calls, 2); + }); }