feat(dfu): add packet codec and crc32 utilities

This commit is contained in:
2026-03-03 16:48:49 +01:00
parent e704f27a96
commit 7a33e71410
2 changed files with 223 additions and 0 deletions

View File

@ -0,0 +1,137 @@
import 'dart:typed_data';
import 'package:abawo_bt_app/model/shifter_types.dart';
const int _startPayloadLength = 11;
class DfuStartPayload {
const DfuStartPayload({
required this.totalLength,
required this.imageCrc32,
required this.sessionId,
required this.flags,
});
final int totalLength;
final int imageCrc32;
final int sessionId;
final int flags;
}
class DfuDataFrame {
const DfuDataFrame({
required this.sequence,
required this.offset,
required this.payloadLength,
required this.bytes,
});
final int sequence;
final int offset;
final int payloadLength;
final Uint8List bytes;
}
class DfuProtocol {
const DfuProtocol._();
static Uint8List encodeStartPayload(DfuStartPayload payload) {
final data = ByteData(_startPayloadLength);
data.setUint8(0, universalShifterDfuOpcodeStart);
data.setUint32(1, payload.totalLength, Endian.little);
data.setUint32(5, payload.imageCrc32, Endian.little);
data.setUint8(9, payload.sessionId);
data.setUint8(10, payload.flags);
return data.buffer.asUint8List();
}
static Uint8List encodeFinishPayload() {
return Uint8List.fromList([universalShifterDfuOpcodeFinish]);
}
static Uint8List encodeAbortPayload() {
return Uint8List.fromList([universalShifterDfuOpcodeAbort]);
}
static List<DfuDataFrame> buildDataFrames(
List<int> imageBytes, {
int startSequence = 0,
}) {
final frames = <DfuDataFrame>[];
var seq = _asU8(startSequence);
var offset = 0;
while (offset < imageBytes.length) {
final remaining = imageBytes.length - offset;
final chunkLength = remaining < universalShifterDfuFramePayloadSizeBytes
? remaining
: universalShifterDfuFramePayloadSizeBytes;
final frame = Uint8List(universalShifterDfuFrameSizeBytes);
frame[0] = seq;
frame.setRange(1, 1 + chunkLength, imageBytes, offset);
frames.add(
DfuDataFrame(
sequence: seq,
offset: offset,
payloadLength: chunkLength,
bytes: frame,
),
);
offset += chunkLength;
seq = nextSequence(seq);
}
return frames;
}
static int nextSequence(int sequence) {
return _asU8(sequence + 1);
}
static int rewindSequenceFromAck(int acknowledgedSequence) {
return nextSequence(acknowledgedSequence);
}
static int sequenceDistance(int from, int to) {
return _asU8(to - from);
}
static int parseAckPayload(List<int> payload) {
if (payload.length != 1) {
throw const FormatException('ACK payload must be exactly 1 byte.');
}
return _asU8(payload.first);
}
static const int crc32Initial = 0xFFFFFFFF;
static const int _crc32PolynomialReflected = 0xEDB88320;
static int crc32Update(int crc, List<int> 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<int> bytes) {
return crc32Finalize(crc32Update(crc32Initial, bytes));
}
static int _asU8(int value) {
return value & 0xFF;
}
}