import 'dart:typed_data'; import 'package:abawo_bt_app/model/shifter_types.dart'; const int _startPayloadLength = 19; class BootloaderDfuStartPayload { const BootloaderDfuStartPayload({ required this.totalLength, required this.imageCrc32, this.appStart = universalShifterDfuAppStart, this.imageVersion = 0, required this.sessionId, required this.flags, }); final int totalLength; final int imageCrc32; final int appStart; final int imageVersion; final int sessionId; final int flags; } class BootloaderDfuDataFrame { const BootloaderDfuDataFrame({ required this.sessionId, required this.offset, required this.payloadLength, required this.bytes, }); final int sessionId; final int offset; final int payloadLength; final Uint8List bytes; } class BootloaderDfuProtocol { const BootloaderDfuProtocol._(); static const int crc32Initial = 0xFFFFFFFF; static const int _crc32PolynomialReflected = 0xEDB88320; static Uint8List encodeStartPayload(BootloaderDfuStartPayload payload) { final data = ByteData(_startPayloadLength); data.setUint8(0, universalShifterDfuOpcodeStart); data.setUint32(1, payload.totalLength, Endian.little); data.setUint32(5, payload.imageCrc32, Endian.little); data.setUint32(9, payload.appStart, Endian.little); data.setUint32(13, payload.imageVersion, Endian.little); data.setUint8(17, payload.sessionId & 0xFF); data.setUint8(18, payload.flags & 0xFF); return data.buffer.asUint8List(); } static Uint8List encodeFinishPayload(int sessionId) { return Uint8List.fromList([ universalShifterDfuOpcodeFinish, sessionId & 0xFF, ]); } static Uint8List encodeAbortPayload(int sessionId) { return Uint8List.fromList([ universalShifterDfuOpcodeAbort, sessionId & 0xFF, ]); } static Uint8List encodeGetStatusPayload() { return Uint8List.fromList([universalShifterDfuOpcodeGetStatus]); } static BootloaderDfuDataFrame buildDataFrame({ required List imageBytes, required int sessionId, required int offset, int payloadSize = universalShifterBootloaderDfuMaxPayloadSizeBytes, }) { if (offset < 0 || offset >= imageBytes.length) { throw RangeError.range(offset, 0, imageBytes.length - 1, 'offset'); } if (payloadSize <= 0 || payloadSize > universalShifterBootloaderDfuMaxPayloadSizeBytes) { throw RangeError.range( payloadSize, 1, universalShifterBootloaderDfuMaxPayloadSizeBytes, 'payloadSize', ); } final remaining = imageBytes.length - offset; final payloadLength = remaining < payloadSize ? remaining : payloadSize; final payloadEnd = offset + payloadLength; final payload = imageBytes.sublist(offset, payloadEnd); final frame = Uint8List( universalShifterBootloaderDfuDataHeaderSizeBytes + payloadLength, ); frame[0] = sessionId & 0xFF; final data = ByteData.view(frame.buffer); data.setUint32(1, offset, Endian.little); data.setUint32(5, crc32(payload), Endian.little); frame.setRange( universalShifterBootloaderDfuDataHeaderSizeBytes, universalShifterBootloaderDfuDataHeaderSizeBytes + payloadLength, payload, ); return BootloaderDfuDataFrame( sessionId: sessionId & 0xFF, offset: offset, payloadLength: payloadLength, bytes: frame, ); } static List buildDataFrames({ required List imageBytes, required int sessionId, int payloadSize = universalShifterBootloaderDfuMaxPayloadSizeBytes, }) { final frames = []; var offset = 0; while (offset < imageBytes.length) { final frame = buildDataFrame( imageBytes: imageBytes, sessionId: sessionId, offset: offset, payloadSize: payloadSize, ); frames.add(frame); offset += frame.payloadLength; } return frames; } static int maxPayloadSizeForMtu(int negotiatedMtu) { final writePayloadBytes = negotiatedMtu - universalShifterAttWriteOverheadBytes; final availablePayload = writePayloadBytes - universalShifterBootloaderDfuDataHeaderSizeBytes; if (availablePayload <= 0) { return 0; } if (availablePayload > universalShifterBootloaderDfuMaxPayloadSizeBytes) { return universalShifterBootloaderDfuMaxPayloadSizeBytes; } return availablePayload; } static DfuBootloaderStatus parseStatusPayload(List payload) { if (payload.length != universalShifterBootloaderDfuStatusSizeBytes) { throw const FormatException( 'DFU status payload must be exactly 6 bytes.'); } final data = ByteData.sublistView(Uint8List.fromList(payload)); final rawCode = data.getUint8(0); return DfuBootloaderStatus( code: DfuBootloaderStatusCode.fromRaw(rawCode), rawCode: rawCode, sessionId: data.getUint8(1), expectedOffset: data.getUint32(2, Endian.little), ); } static int crc32Update(int crc, List bytes) { var next = crc & 0xFFFFFFFF; for (final byte in bytes) { next ^= byte; for (var bit = 0; bit < 8; bit++) { if ((next & 0x1) != 0) { next = (next >> 1) ^ _crc32PolynomialReflected; } else { next >>= 1; } } } return next & 0xFFFFFFFF; } static int crc32Finalize(int crc) { return (crc ^ 0xFFFFFFFF) & 0xFFFFFFFF; } static int crc32(List bytes) { return crc32Finalize(crc32Update(crc32Initial, bytes)); } }