From 230a6838e08bcac28516c5766f40c3e0a537311c Mon Sep 17 00:00:00 2001 From: Yandrik Date: Tue, 5 May 2026 20:44:31 +0200 Subject: [PATCH] fix: fix dfu id mismatch because stale notification --- lib/service/firmware_update_service.dart | 37 +++++++++++++++++-- .../service/firmware_update_service_test.dart | 33 +++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/lib/service/firmware_update_service.dart b/lib/service/firmware_update_service.dart index 2e96597..48a40fd 100644 --- a/lib/service/firmware_update_service.dart +++ b/lib/service/firmware_update_service.dart @@ -527,10 +527,33 @@ class FirmwareUpdateService { Future _sendStartAndWaitForStatus( BootloaderDfuStartPayload payload, { required Duration timeout, - }) { - return _writeControlAndWaitForStatus( - BootloaderDfuProtocol.encodeStartPayload(payload), + }) async { + final eventCount = _statusEventCount; + final encodedPayload = BootloaderDfuProtocol.encodeStartPayload(payload); + _log.fine( + 'Writing DFU START command for session ${payload.sessionId} ' + '(len=${encodedPayload.length})', + ); + final result = await _transport.writeControl(encodedPayload); + if (result.isErr()) { + _log.warning('DFU START write failed: ${result.unwrapErr()}'); + throw _DfuFailure( + 'Failed to write bootloader control command: ${result.unwrapErr()}', + ); + } + return _waitForStatus( + afterEventCount: eventCount, timeout: timeout, + acceptStatus: (status) { + if (status.sessionId == payload.sessionId) { + return true; + } + _log.fine( + 'Ignoring stale START status for session ${status.sessionId}; ' + 'waiting for session ${payload.sessionId}', + ); + return false; + }, ); } @@ -708,6 +731,7 @@ class FirmwareUpdateService { required int afterEventCount, required Duration timeout, bool recoverable = false, + bool Function(DfuBootloaderStatus status)? acceptStatus, }) async { final deadline = DateTime.now().add(timeout); var observedEvents = afterEventCount; @@ -722,7 +746,12 @@ class FirmwareUpdateService { 'session=${_latestStatus!.sessionId}, offset=${_latestStatus!.expectedOffset}, ' 'code=${_statusLabel(_latestStatus!)}', ); - return _latestStatus!; + final status = _latestStatus!; + if (acceptStatus == null || acceptStatus(status)) { + return status; + } + observedEvents = _statusEventCount; + continue; } final remaining = deadline.difference(DateTime.now()); diff --git a/test/service/firmware_update_service_test.dart b/test/service/firmware_update_service_test.dart index 4baebdc..fc9beea 100644 --- a/test/service/firmware_update_service_test.dart +++ b/test/service/firmware_update_service_test.dart @@ -307,6 +307,33 @@ void main() { await transport.dispose(); }); + test('ignores stale previous-session status while waiting for START', + () async { + final image = _validImage(80); + final transport = _FakeFirmwareUpdateTransport( + totalBytes: image.length, + staleStartStatusSessionId: 20, + ); + final service = FirmwareUpdateService( + transport: transport, + defaultStatusTimeout: const Duration(milliseconds: 100), + ); + + final result = await service.startUpdate( + imageBytes: image, + sessionId: 21, + ); + + expect(result.isOk(), isTrue); + expect( + transport.controlWrites.first.first, universalShifterDfuOpcodeStart); + expect(transport.dataWrites.first[0], 21); + expect(service.currentProgress.state, DfuUpdateState.completed); + + await service.dispose(); + await transport.dispose(); + }); + test('fails with bootloader status error on rejected START', () async { final image = _validImage(40); final transport = _FakeFirmwareUpdateTransport( @@ -410,6 +437,7 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport { this.suppressFirstDataStatus = false, this.failDataWriteAtOffsetOnce, this.resetSessionOnRecoveryStatus = false, + this.staleStartStatusSessionId, this.suppressFinishStatus = false, this.disconnectAfterFinish = true, this.finishStatusCode = DfuBootloaderStatusCode.ok, @@ -425,6 +453,7 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport { final bool suppressFirstDataStatus; final int? failDataWriteAtOffsetOnce; final bool resetSessionOnRecoveryStatus; + final int? staleStartStatusSessionId; final bool suppressFinishStatus; final bool disconnectAfterFinish; final DfuBootloaderStatusCode finishStatusCode; @@ -502,6 +531,10 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport { if (opcode == universalShifterDfuOpcodeStart) { _sessionId = payload[17]; _expectedOffset = 0; + final staleSessionId = staleStartStatusSessionId; + if (staleSessionId != null) { + _scheduleStatus(DfuBootloaderStatusCode.ok, staleSessionId, 0); + } _scheduleStatus(startStatusCode, _sessionId, 0); } else if (opcode == universalShifterDfuOpcodeGetStatus) { if (resetSessionOnRecoveryStatus && _connectCount > 1) {