From bdcd200a62ff2eac41623df7bc99bb04c3b54c23 Mon Sep 17 00:00:00 2001 From: Yandrik Date: Wed, 4 Mar 2026 18:09:48 +0100 Subject: [PATCH] test(dfu): cover retry failures and sequence wrap cases --- test/service/dfu_protocol_test.dart | 13 +++ .../service/firmware_update_service_test.dart | 86 +++++++++++++++++-- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/test/service/dfu_protocol_test.dart b/test/service/dfu_protocol_test.dart index 1b5b74f..b1a1b45 100644 --- a/test/service/dfu_protocol_test.dart +++ b/test/service/dfu_protocol_test.dart @@ -67,6 +67,19 @@ void main() { expect(frames[1].bytes.length, universalShifterDfuFrameSizeBytes); expect(frames[1].bytes.sublist(1, 18), image.sublist(63, 80)); }); + + test('uses deterministic wrapping sequence numbers from custom start', () { + final image = List.generate( + 3 * universalShifterDfuFramePayloadSizeBytes, + (index) => index & 0xFF); + + final frames = DfuProtocol.buildDataFrames(image, startSequence: 0xFE); + + expect(frames.length, 3); + expect(frames[0].sequence, 0xFE); + expect(frames[1].sequence, 0xFF); + expect(frames[2].sequence, 0x00); + }); }); group('DfuProtocol sequence and ACK helpers', () { diff --git a/test/service/firmware_update_service_test.dart b/test/service/firmware_update_service_test.dart index 885c360..d971040 100644 --- a/test/service/firmware_update_service_test.dart +++ b/test/service/firmware_update_service_test.dart @@ -66,6 +66,32 @@ void main() { await transport.dispose(); }); + test('fails after bounded retries when ACK progress times out', () async { + final transport = _FakeFirmwareUpdateTransport(suppressDataAcks: true); + final service = FirmwareUpdateService( + transport: transport, + defaultWindowSize: 1, + defaultAckTimeout: const Duration(milliseconds: 40), + maxNoProgressRetries: 2, + ); + + final image = List.generate(90, (index) => index & 0xFF); + final result = await service.startUpdate( + imageBytes: image, + sessionId: 10, + ); + + expect(result.isErr(), isTrue); + expect(result.unwrapErr().toString(), contains('Upload stalled')); + expect(result.unwrapErr().toString(), contains('after 3 retries')); + expect(transport.controlWrites.last, [universalShifterDfuOpcodeAbort]); + expect(transport.sequenceWriteCount(0), 3); + expect(service.currentProgress.state, DfuUpdateState.failed); + + await service.dispose(); + await transport.dispose(); + }); + test('cancel sends ABORT and reports aborted state', () async { final firstFrameSent = Completer(); final transport = _FakeFirmwareUpdateTransport( @@ -201,6 +227,45 @@ void main() { await service.dispose(); await transport.dispose(); }); + + test('handles deterministic ACK sequence wrap-around across 0xFF->0x00', + () async { + const frameCount = 260; + final transport = _FakeFirmwareUpdateTransport(); + final service = FirmwareUpdateService( + transport: transport, + defaultWindowSize: 16, + defaultAckTimeout: const Duration(milliseconds: 100), + ); + + final image = List.generate( + frameCount * universalShifterDfuFramePayloadSizeBytes, + (index) => index & 0xFF, + ); + + final result = await service.startUpdate( + imageBytes: image, + sessionId: 16, + ); + + expect(result.isOk(), isTrue); + + var ffToZeroTransitions = 0; + for (var i = 1; i < transport.ackNotifications.length; i++) { + if (transport.ackNotifications[i - 1] == 0xFF && + transport.ackNotifications[i] == 0x00) { + ffToZeroTransitions += 1; + } + } + + expect(ffToZeroTransitions, greaterThanOrEqualTo(2)); + expect(service.currentProgress.lastAckedSequence, 0x03); + expect(service.currentProgress.sentBytes, image.length); + expect(service.currentProgress.state, DfuUpdateState.completed); + + await service.dispose(); + await transport.dispose(); + }); }); } @@ -226,6 +291,7 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport { final List> controlWrites = >[]; final List> dataWrites = >[]; + final List ackNotifications = []; final List postFinishSteps = []; final Set _droppedOnce = {}; int _lastAck = 0xFF; @@ -254,9 +320,7 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport { if (opcode == universalShifterDfuOpcodeStart) { _lastAck = 0xFF; _expectedSequence = 0; - scheduleMicrotask(() { - _ackController.add([0xFF]); - }); + _scheduleAck(0xFF); } if (opcode == universalShifterDfuOpcodeAbort) { @@ -283,9 +347,7 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport { if (shouldDrop) { _droppedOnce.add(sequence); - scheduleMicrotask(() { - _ackController.add([_lastAck]); - }); + _scheduleAck(_lastAck); return Ok(null); } @@ -294,13 +356,19 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport { _expectedSequence = (_expectedSequence + 1) & 0xFF; } - scheduleMicrotask(() { - _ackController.add([_lastAck]); - }); + _scheduleAck(_lastAck); return Ok(null); } + void _scheduleAck(int sequence) { + final ack = sequence & 0xFF; + ackNotifications.add(ack); + scheduleMicrotask(() { + _ackController.add([ack]); + }); + } + @override Future> waitForExpectedResetDisconnect({ required Duration timeout,