test(dfu): cover retry failures and sequence wrap cases
This commit is contained in:
@ -67,6 +67,19 @@ void main() {
|
|||||||
expect(frames[1].bytes.length, universalShifterDfuFrameSizeBytes);
|
expect(frames[1].bytes.length, universalShifterDfuFrameSizeBytes);
|
||||||
expect(frames[1].bytes.sublist(1, 18), image.sublist(63, 80));
|
expect(frames[1].bytes.sublist(1, 18), image.sublist(63, 80));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('uses deterministic wrapping sequence numbers from custom start', () {
|
||||||
|
final image = List<int>.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', () {
|
group('DfuProtocol sequence and ACK helpers', () {
|
||||||
|
|||||||
@ -66,6 +66,32 @@ void main() {
|
|||||||
await transport.dispose();
|
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<int>.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 {
|
test('cancel sends ABORT and reports aborted state', () async {
|
||||||
final firstFrameSent = Completer<void>();
|
final firstFrameSent = Completer<void>();
|
||||||
final transport = _FakeFirmwareUpdateTransport(
|
final transport = _FakeFirmwareUpdateTransport(
|
||||||
@ -201,6 +227,45 @@ void main() {
|
|||||||
await service.dispose();
|
await service.dispose();
|
||||||
await transport.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<int>.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<List<int>> controlWrites = <List<int>>[];
|
final List<List<int>> controlWrites = <List<int>>[];
|
||||||
final List<List<int>> dataWrites = <List<int>>[];
|
final List<List<int>> dataWrites = <List<int>>[];
|
||||||
|
final List<int> ackNotifications = <int>[];
|
||||||
final List<String> postFinishSteps = <String>[];
|
final List<String> postFinishSteps = <String>[];
|
||||||
final Set<int> _droppedOnce = <int>{};
|
final Set<int> _droppedOnce = <int>{};
|
||||||
int _lastAck = 0xFF;
|
int _lastAck = 0xFF;
|
||||||
@ -254,9 +320,7 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport {
|
|||||||
if (opcode == universalShifterDfuOpcodeStart) {
|
if (opcode == universalShifterDfuOpcodeStart) {
|
||||||
_lastAck = 0xFF;
|
_lastAck = 0xFF;
|
||||||
_expectedSequence = 0;
|
_expectedSequence = 0;
|
||||||
scheduleMicrotask(() {
|
_scheduleAck(0xFF);
|
||||||
_ackController.add([0xFF]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opcode == universalShifterDfuOpcodeAbort) {
|
if (opcode == universalShifterDfuOpcodeAbort) {
|
||||||
@ -283,9 +347,7 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport {
|
|||||||
|
|
||||||
if (shouldDrop) {
|
if (shouldDrop) {
|
||||||
_droppedOnce.add(sequence);
|
_droppedOnce.add(sequence);
|
||||||
scheduleMicrotask(() {
|
_scheduleAck(_lastAck);
|
||||||
_ackController.add([_lastAck]);
|
|
||||||
});
|
|
||||||
return Ok(null);
|
return Ok(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,13 +356,19 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport {
|
|||||||
_expectedSequence = (_expectedSequence + 1) & 0xFF;
|
_expectedSequence = (_expectedSequence + 1) & 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleMicrotask(() {
|
_scheduleAck(_lastAck);
|
||||||
_ackController.add([_lastAck]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok(null);
|
return Ok(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _scheduleAck(int sequence) {
|
||||||
|
final ack = sequence & 0xFF;
|
||||||
|
ackNotifications.add(ack);
|
||||||
|
scheduleMicrotask(() {
|
||||||
|
_ackController.add([ack]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<void>> waitForExpectedResetDisconnect({
|
Future<Result<void>> waitForExpectedResetDisconnect({
|
||||||
required Duration timeout,
|
required Duration timeout,
|
||||||
|
|||||||
Reference in New Issue
Block a user