fix: align bootloader image validation limits

This commit is contained in:
2026-04-29 19:55:10 +02:00
parent 09c686d542
commit 16365e1d04
4 changed files with 49 additions and 5 deletions

View File

@ -18,7 +18,7 @@ This guide explains the Universal Shifters single-slot bootloader update flow in
## Image Requirements
- File extension must be `.bin`.
- Image must be at least 8 bytes and no larger than `0x42000` bytes.
- Image must be at least 8 bytes and no larger than `0x3F000` bytes (252 KiB).
- Image bytes must start at application address `0x00030000`.
- Initial stack pointer must be aligned and within `0x20000000..=0x20010000`.
- Reset vector must have the Thumb bit set and point inside the image after the first two vector words.
@ -40,7 +40,7 @@ This guide explains the Universal Shifters single-slot bootloader update flow in
| Invalid stack pointer or reset vector | `.bin` is not a raw app image for `0x00030000` | Rebuild/export the application image from the correct linker layout. |
| Could not connect to bootloader DFU mode | Phone did not find `US-DFU` after app reset | Move closer, retry, and verify the device is advertising `US-DFU`. |
| Timed out waiting for bootloader DFU status | Status indication/read did not arrive | Reconnect to `US-DFU` and retry. |
| Bootloader status `bounds error` | Image length or app start rejected | Use a valid app image no larger than `0x42000` bytes. |
| Bootloader status `bounds error` | Image length or app start rejected | Use a valid app image no larger than `0x3F000` bytes (252 KiB). |
| Bootloader status `CRC error` | Full-image CRC did not match flash contents | Re-export or re-download the `.bin`, then retry. |
| Bootloader status `vector table error` | Bootloader rejected the written vector table | Rebuild firmware for app start `0x00030000`. |
| Bootloader status `flash error` | Flash erase/write/read failed | Retry once; if repeated, service or externally reflash the device. |

View File

@ -48,7 +48,7 @@ const int universalShifterAttWriteOverheadBytes = 3;
const int universalShifterDfuPreferredMtu = 128;
const int universalShifterDfuAppStart = 0x00030000;
const int universalShifterDfuAppSlotSizeBytes = 0x00042000;
const int universalShifterDfuAppSlotSizeBytes = 0x0003F000;
const int universalShifterDfuMinimumImageLengthBytes = 8;
const int universalShifterDfuRamStart = 0x20000000;
const int universalShifterDfuRamEnd = 0x20010000;

View File

@ -138,12 +138,14 @@ class FirmwareFileSelectionService {
final vectorStackPointer = _readLeU32(selection.fileBytes, 0);
final vectorReset = _readLeU32(selection.fileBytes, 4);
final sessionId = _normalizeSessionId(_sessionIdGenerator());
final metadata = BootloaderDfuFirmwareMetadata(
totalLength: selection.fileBytes.length,
crc32: BootloaderDfuProtocol.crc32(selection.fileBytes),
appStart: universalShifterDfuAppStart,
imageVersion: 0,
sessionId: _sessionIdGenerator() & 0xFF,
sessionId: sessionId,
flags: universalShifterDfuFlagNone,
vectorStackPointer: vectorStackPointer,
vectorReset: vectorReset,
@ -215,7 +217,12 @@ class FirmwareFileSelectionService {
return ByteData.sublistView(bytes).getUint32(offset, Endian.little);
}
static int _normalizeSessionId(int sessionId) {
final normalized = sessionId & 0xFF;
return normalized == 0 ? 1 : normalized;
}
static int _randomSessionId() {
return Random.secure().nextInt(256);
return Random.secure().nextInt(255) + 1;
}
}

View File

@ -120,6 +120,26 @@ void main() {
result.failure?.reason, FirmwareSelectionFailureReason.imageTooLarge);
});
test('accepts image exactly at application slot size', () async {
final image = Uint8List(universalShifterDfuAppSlotSizeBytes);
_writeLeU32(image, 0, 0x20001000);
_writeLeU32(image, 4, 0x00030009);
final service = FirmwareFileSelectionService(
filePicker: _FakeFirmwareFilePicker(
selection: FirmwarePickerSelection(
fileName: 'firmware.bin',
fileBytes: image,
),
),
);
final result = await service.selectAndPrepareBootloaderDfu();
expect(result.isSuccess, isTrue);
expect(result.firmware?.metadata.totalLength,
universalShifterDfuAppSlotSizeBytes);
});
test('rejects images with invalid vector table', () async {
final image = _validBootloaderImage();
_writeLeU32(image, 0, 0x10001000);
@ -158,6 +178,23 @@ void main() {
expect(second.firmware?.metadata.sessionId, 10);
});
test('normalizes generated zero session id to one', () async {
final service = FirmwareFileSelectionService(
filePicker: _FakeFirmwareFilePicker(
selection: FirmwarePickerSelection(
fileName: 'firmware.bin',
fileBytes: _validBootloaderImage(),
),
),
sessionIdGenerator: () => 0,
);
final result = await service.selectAndPrepareBootloaderDfu();
expect(result.isSuccess, isTrue);
expect(result.firmware?.metadata.sessionId, 1);
});
test('maps picker read failure to explicit validation error', () async {
final service = FirmwareFileSelectionService(
filePicker: _FakeFirmwareFilePicker(