feat: add bootloader DFU protocol validation

This commit is contained in:
2026-04-29 17:56:32 +02:00
parent eb26c759e8
commit b673c9100d
7 changed files with 534 additions and 86 deletions

View File

@ -1,21 +1,29 @@
import 'dart:typed_data';
class DfuV1FirmwareMetadata {
const DfuV1FirmwareMetadata({
class BootloaderDfuFirmwareMetadata {
const BootloaderDfuFirmwareMetadata({
required this.totalLength,
required this.crc32,
required this.appStart,
required this.imageVersion,
required this.sessionId,
required this.flags,
required this.vectorStackPointer,
required this.vectorReset,
});
final int totalLength;
final int crc32;
final int appStart;
final int imageVersion;
final int sessionId;
final int flags;
final int vectorStackPointer;
final int vectorReset;
}
class DfuV1PreparedFirmware {
const DfuV1PreparedFirmware({
class BootloaderDfuPreparedFirmware {
const BootloaderDfuPreparedFirmware({
required this.fileName,
required this.fileBytes,
required this.metadata,
@ -25,7 +33,7 @@ class DfuV1PreparedFirmware {
final String fileName;
final String? filePath;
final Uint8List fileBytes;
final DfuV1FirmwareMetadata metadata;
final BootloaderDfuFirmwareMetadata metadata;
}
enum FirmwareSelectionFailureReason {
@ -33,6 +41,9 @@ enum FirmwareSelectionFailureReason {
malformedSelection,
unsupportedExtension,
emptyFile,
imageTooSmall,
imageTooLarge,
invalidVectorTable,
readFailed,
}
@ -52,7 +63,7 @@ class FirmwareFileSelectionResult {
this.failure,
});
final DfuV1PreparedFirmware? firmware;
final BootloaderDfuPreparedFirmware? firmware;
final FirmwareSelectionFailure? failure;
bool get isSuccess => firmware != null;
@ -60,7 +71,8 @@ class FirmwareFileSelectionResult {
bool get isCanceled =>
failure?.reason == FirmwareSelectionFailureReason.canceled;
static FirmwareFileSelectionResult success(DfuV1PreparedFirmware firmware) {
static FirmwareFileSelectionResult success(
BootloaderDfuPreparedFirmware firmware) {
return FirmwareFileSelectionResult._(firmware: firmware);
}

View File

@ -20,6 +20,8 @@ const String universalShifterDfuDataCharacteristicUuid =
'0993826f-0ee4-4b37-9614-d13ecba40009';
const String universalShifterDfuAckCharacteristicUuid =
'0993826f-0ee4-4b37-9614-d13ecba4000a';
const String universalShifterDfuStatusCharacteristicUuid =
'0993826f-0ee4-4b37-9614-d13ecba4000a';
const String ftmsServiceUuid = '00001826-0000-1000-8000-00805f9b34fb';
const String batteryServiceUuid = '0000180f-0000-1000-8000-00805f9b34fb';
const String batteryLevelCharacteristicUuid =
@ -36,14 +38,26 @@ bool isFtmsUuid(Uuid uuid) {
const int universalShifterDfuOpcodeStart = 0x01;
const int universalShifterDfuOpcodeFinish = 0x02;
const int universalShifterDfuOpcodeAbort = 0x03;
const int universalShifterDfuOpcodeGetStatus = 0x04;
const int universalShifterDfuFrameSizeBytes = 64;
const int universalShifterDfuFramePayloadSizeBytes = 63;
const int universalShifterBootloaderDfuDataHeaderSizeBytes = 9;
const int universalShifterBootloaderDfuMaxPayloadSizeBytes =
universalShifterDfuFrameSizeBytes -
universalShifterBootloaderDfuDataHeaderSizeBytes;
const int universalShifterBootloaderDfuStatusSizeBytes = 6;
const int universalShifterAttWriteOverheadBytes = 3;
const int universalShifterDfuMinimumMtu =
universalShifterDfuFrameSizeBytes + universalShifterAttWriteOverheadBytes;
const int universalShifterDfuPreferredMtu = 128;
const int universalShifterDfuAppStart = 0x00030000;
const int universalShifterDfuAppSlotSizeBytes = 0x00042000;
const int universalShifterDfuMinimumImageLengthBytes = 8;
const int universalShifterDfuRamStart = 0x20000000;
const int universalShifterDfuRamEnd = 0x20010000;
const int universalShifterDfuFlagEncrypted = 0x01;
const int universalShifterDfuFlagSigned = 0x02;
const int universalShifterDfuFlagNone = 0x00;
@ -134,6 +148,49 @@ class DfuUpdateProgress {
state == DfuUpdateState.failed;
}
enum DfuBootloaderStatusCode {
ok(0x00),
parseError(0x01),
stateError(0x02),
boundsError(0x03),
crcError(0x04),
flashError(0x05),
unsupportedError(0x06),
vectorError(0x07),
queueFull(0x08),
bootMetadataError(0x09),
unknown(-1);
const DfuBootloaderStatusCode(this.value);
final int value;
static DfuBootloaderStatusCode fromRaw(int value) {
for (final code in values) {
if (code.value == value) {
return code;
}
}
return DfuBootloaderStatusCode.unknown;
}
}
class DfuBootloaderStatus {
const DfuBootloaderStatus({
required this.code,
required this.rawCode,
required this.sessionId,
required this.expectedOffset,
});
final DfuBootloaderStatusCode code;
final int rawCode;
final int sessionId;
final int expectedOffset;
bool get isOk => code == DfuBootloaderStatusCode.ok;
}
enum DfuPreflightFailureReason {
deviceNotConnected,
wrongConnectedDevice,
@ -253,7 +310,8 @@ enum UniversalShifterCommand {
stopScan(0x02),
connectToDevice(0x03),
disconnect(0x04),
turnOff(0x05);
turnOff(0x05),
enterDfu(0x06);
const UniversalShifterCommand(this.value);
final int value;