import 'package:abawo_bt_app/model/shifter_types.dart'; import 'package:abawo_bt_app/service/dfu_protocol.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('BootloaderDfuProtocol CRC32', () { test('matches known vector', () { final crc = BootloaderDfuProtocol.crc32('123456789'.codeUnits); expect(crc, 0xCBF43926); }); }); group('BootloaderDfuProtocol control payload encoding', () { test('encodes START payload with exact 19-byte LE layout', () { final payload = BootloaderDfuProtocol.encodeStartPayload( const BootloaderDfuStartPayload( totalLength: 0x1234, imageCrc32: 0x89ABCDEF, appStart: universalShifterDfuAppStart, imageVersion: 0x10203040, sessionId: 0x22, flags: universalShifterDfuFlagNone, ), ); expect(payload.length, 19); expect(payload, [ universalShifterDfuOpcodeStart, 0x34, 0x12, 0x00, 0x00, 0xEF, 0xCD, 0xAB, 0x89, 0x00, 0x00, 0x03, 0x00, 0x40, 0x30, 0x20, 0x10, 0x22, universalShifterDfuFlagNone, ]); }); test('encodes FINISH, ABORT, and GET_STATUS payloads', () { expect( BootloaderDfuProtocol.encodeFinishPayload(0x12), [universalShifterDfuOpcodeFinish, 0x12], ); expect( BootloaderDfuProtocol.encodeAbortPayload(0x34), [universalShifterDfuOpcodeAbort, 0x34], ); expect( BootloaderDfuProtocol.encodeGetStatusPayload(), [universalShifterDfuOpcodeGetStatus], ); }); }); group('BootloaderDfuProtocol data frame building', () { test('builds offset frames with payload CRC and variable final length', () { final image = List.generate(60, (index) => index); final frames = BootloaderDfuProtocol.buildDataFrames( imageBytes: image, sessionId: 0x7A, ); expect(frames.length, 2); expect(frames[0].sessionId, 0x7A); expect(frames[0].offset, 0); expect(frames[0].payloadLength, universalShifterBootloaderDfuMaxPayloadSizeBytes); expect(frames[0].bytes.length, universalShifterDfuFrameSizeBytes); expect(frames[0].bytes[0], 0x7A); expect(frames[0].bytes.sublist(1, 5), [0, 0, 0, 0]); expect( frames[0].bytes.sublist(5, 9), _leU32Bytes(BootloaderDfuProtocol.crc32(image.sublist(0, 55))), ); expect(frames[0].bytes.sublist(9), image.sublist(0, 55)); expect(frames[1].offset, 55); expect(frames[1].payloadLength, 5); expect(frames[1].bytes.length, 14); expect(frames[1].bytes.sublist(1, 5), [55, 0, 0, 0]); expect(frames[1].bytes.sublist(9), image.sublist(55)); }); test('uses caller supplied payload size for low-MTU links', () { final image = List.generate(15, (index) => index); final frames = BootloaderDfuProtocol.buildDataFrames( imageBytes: image, sessionId: 0x01, payloadSize: 4, ); expect(frames.map((frame) => frame.payloadLength), [4, 4, 4, 3]); expect(frames.map((frame) => frame.offset), [0, 4, 8, 12]); }); test('calculates safe payload size from negotiated MTU', () { expect( BootloaderDfuProtocol.maxPayloadSizeForMtu(64), universalShifterBootloaderDfuMaxPayloadSizeBytes - 3, ); expect(BootloaderDfuProtocol.maxPayloadSizeForMtu(23), 11); expect(BootloaderDfuProtocol.maxPayloadSizeForMtu(12), 0); expect( BootloaderDfuProtocol.maxPayloadSizeForMtu(128), universalShifterBootloaderDfuMaxPayloadSizeBytes, ); }); }); group('BootloaderDfuProtocol status parsing', () { test('parses bootloader status payload', () { final status = BootloaderDfuProtocol.parseStatusPayload( [0x00, 0x22, 0x78, 0x56, 0x34, 0x12], ); expect(status.code, DfuBootloaderStatusCode.ok); expect(status.rawCode, 0x00); expect(status.sessionId, 0x22); expect(status.expectedOffset, 0x12345678); expect(status.isOk, isTrue); }); test('preserves unknown status codes', () { final status = BootloaderDfuProtocol.parseStatusPayload( [0xFE, 0x00, 0x00, 0x00, 0x00, 0x00], ); expect(status.code, DfuBootloaderStatusCode.unknown); expect(status.rawCode, 0xFE); }); test('rejects malformed status payloads', () { expect( () => BootloaderDfuProtocol.parseStatusPayload(const [0, 1]), throwsFormatException, ); }); }); } List _leU32Bytes(int value) { return [ value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF, ]; }