313 lines
8.4 KiB
Dart
313 lines
8.4 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import 'package:abawo_bt_app/model/shifter_types.dart';
|
|
|
|
const int _startPayloadLength = 11;
|
|
const int _bootloaderStartPayloadLength = 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 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;
|
|
}
|
|
}
|
|
|
|
class BootloaderDfuProtocol {
|
|
const BootloaderDfuProtocol._();
|
|
|
|
static Uint8List encodeStartPayload(BootloaderDfuStartPayload payload) {
|
|
final data = ByteData(_bootloaderStartPayloadLength);
|
|
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<int> 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<BootloaderDfuDataFrame> buildDataFrames({
|
|
required List<int> imageBytes,
|
|
required int sessionId,
|
|
int payloadSize = universalShifterBootloaderDfuMaxPayloadSizeBytes,
|
|
}) {
|
|
final frames = <BootloaderDfuDataFrame>[];
|
|
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<int> 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 const int crc32Initial = DfuProtocol.crc32Initial;
|
|
|
|
static int crc32Update(int crc, List<int> bytes) {
|
|
return DfuProtocol.crc32Update(crc, bytes);
|
|
}
|
|
|
|
static int crc32Finalize(int crc) {
|
|
return DfuProtocol.crc32Finalize(crc);
|
|
}
|
|
|
|
static int crc32(List<int> bytes) {
|
|
return DfuProtocol.crc32(bytes);
|
|
}
|
|
}
|