Files
abawo-bt-app/lib/service/dfu_protocol.dart

192 lines
5.5 KiB
Dart

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<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 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));
}
}