From 51c7bd063666a2d428d1332a117ea668efef42a9 Mon Sep 17 00:00:00 2001 From: Mohammad Abdul Sahil <127765312+abdulsaheel@users.noreply.github.com> Date: Sat, 19 Sep 2026 02:19:28 +0530 Subject: [PATCH] gate get_alarm_time on status like getclock/getdatarange gen5 GET_ALARM_TIME reply was decoded off the form byte alone, no status check. a failure/deferred reply leaves the body stale, so old alarm bytes could get reported as the current alarm. same fix as the getclock/ getdatarange/getbatterypackinfo status gates already in this file, gen4 stays ungated for the same unconfirmed-status-byte reason. --- lib/src/control.dart | 12 +++++++-- test/control_plane_offsets_test.dart | 37 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/src/control.dart b/lib/src/control.dart index df4b94c..8ded04f 100644 --- a/lib/src/control.dart +++ b/lib/src/control.dart @@ -868,10 +868,18 @@ CmdResponse? parseCommandResponse(Uint8List inner, // body[2:6] epoch u32 LE body[6:8] subseconds u16 LE // — which confirms the epoch offset used here, and adds the active flag. // The response carries no alarm ID; the requested ID selects it. + // + // Status-gated on gen5 ONLY, same convention as getClock/getDataRange/ + // getBatteryPackInfo above: this opcode is sent to both profiles (see + // commands.dart), gen5's status byte is confirmed so a failure/deferred + // reply's stale body (leftover bytes from a prior successful read) isn't + // reported as the strap's current alarm; gen4's status byte is + // unconfirmed so it stays ungated, same reasoning as those siblings. + final statusOk = !profile.isGen5 || status == 1; final form = payload.length >= 3 ? payload[2] : -1; - if (form == 0x01 && payload.length >= 7) { + if (statusOk && form == 0x01 && payload.length >= 7) { dec['alarm_epoch'] = u32(payload, 3); - } else if (form == 0x04 && payload.length >= 8) { + } else if (statusOk && form == 0x04 && payload.length >= 8) { dec['alarm_epoch'] = u32(payload, 4); // "exactly 1 means active" — anything else is not an armed alarm, and is // reported as inactive rather than guessed at. diff --git a/test/control_plane_offsets_test.dart b/test/control_plane_offsets_test.dart index d806324..3122867 100644 --- a/test/control_plane_offsets_test.dart +++ b/test/control_plane_offsets_test.dart @@ -310,6 +310,43 @@ void main() { expect(get4(2)['alarm_active'], isFalse); }); + test( + 'gen5: a non-success outer status emits neither alarm_epoch nor ' + 'alarm_active', () { + // Same convention as getClock/getDataRange: a failure/deferred reply's + // body bytes are stale leftovers from a prior successful read. + final body = [0x04, 1, ...le32(1786000000), ...le16(0)]; + for (final status in [0, 2, 3]) { + final r = parseCommandResponse( + cmdResponse(Cmd.getAlarmTime, body, status: status), + profile: BandProfile.gen5)!; + expect(r.decoded.containsKey('alarm_epoch'), isFalse, + reason: 'status=$status'); + expect(r.decoded.containsKey('alarm_active'), isFalse, + reason: 'status=$status'); + } + }); + + test('gen5: a success outer status still decodes normally', () { + final body = [0x04, 1, ...le32(1786000000), ...le16(0)]; + final r = parseCommandResponse( + cmdResponse(Cmd.getAlarmTime, body, status: 1), + profile: BandProfile.gen5)!; + expect(r.decoded['alarm_epoch'], 1786000000); + expect(r.decoded['alarm_active'], isTrue); + }); + + test('gen4 is left ungated on outer status, unlike gen5', () { + // Matches getClock/getDataRange's gen4 policy: the status byte's + // semantics are unconfirmed on gen4, so gating it would silently drop + // valid alarm reads rather than risk a stale one. + final body = [0x04, 1, ...le32(1786000000), ...le16(0)]; + final r = parseCommandResponse( + cmdResponse(Cmd.getAlarmTime, body, status: 0))!; + expect(r.decoded['alarm_epoch'], 1786000000); + expect(r.decoded['alarm_active'], isTrue); + }); + test('gen5 GET_CUSTOM_ADVERTISING_NAME (0x8D) decodes like gen4 0x4C', () { // Reply body: revision, status, length, then the ASCII name — the same // shape at the same offsets on both generations.