fix: align bootloader image validation limits
This commit is contained in:
@ -18,7 +18,7 @@ This guide explains the Universal Shifters single-slot bootloader update flow in
|
|||||||
## Image Requirements
|
## Image Requirements
|
||||||
|
|
||||||
- File extension must be `.bin`.
|
- 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`.
|
- Image bytes must start at application address `0x00030000`.
|
||||||
- Initial stack pointer must be aligned and within `0x20000000..=0x20010000`.
|
- 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.
|
- 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. |
|
| 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`. |
|
| 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. |
|
| 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 `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 `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. |
|
| Bootloader status `flash error` | Flash erase/write/read failed | Retry once; if repeated, service or externally reflash the device. |
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const int universalShifterAttWriteOverheadBytes = 3;
|
|||||||
const int universalShifterDfuPreferredMtu = 128;
|
const int universalShifterDfuPreferredMtu = 128;
|
||||||
|
|
||||||
const int universalShifterDfuAppStart = 0x00030000;
|
const int universalShifterDfuAppStart = 0x00030000;
|
||||||
const int universalShifterDfuAppSlotSizeBytes = 0x00042000;
|
const int universalShifterDfuAppSlotSizeBytes = 0x0003F000;
|
||||||
const int universalShifterDfuMinimumImageLengthBytes = 8;
|
const int universalShifterDfuMinimumImageLengthBytes = 8;
|
||||||
const int universalShifterDfuRamStart = 0x20000000;
|
const int universalShifterDfuRamStart = 0x20000000;
|
||||||
const int universalShifterDfuRamEnd = 0x20010000;
|
const int universalShifterDfuRamEnd = 0x20010000;
|
||||||
|
|||||||
@ -138,12 +138,14 @@ class FirmwareFileSelectionService {
|
|||||||
final vectorStackPointer = _readLeU32(selection.fileBytes, 0);
|
final vectorStackPointer = _readLeU32(selection.fileBytes, 0);
|
||||||
final vectorReset = _readLeU32(selection.fileBytes, 4);
|
final vectorReset = _readLeU32(selection.fileBytes, 4);
|
||||||
|
|
||||||
|
final sessionId = _normalizeSessionId(_sessionIdGenerator());
|
||||||
|
|
||||||
final metadata = BootloaderDfuFirmwareMetadata(
|
final metadata = BootloaderDfuFirmwareMetadata(
|
||||||
totalLength: selection.fileBytes.length,
|
totalLength: selection.fileBytes.length,
|
||||||
crc32: BootloaderDfuProtocol.crc32(selection.fileBytes),
|
crc32: BootloaderDfuProtocol.crc32(selection.fileBytes),
|
||||||
appStart: universalShifterDfuAppStart,
|
appStart: universalShifterDfuAppStart,
|
||||||
imageVersion: 0,
|
imageVersion: 0,
|
||||||
sessionId: _sessionIdGenerator() & 0xFF,
|
sessionId: sessionId,
|
||||||
flags: universalShifterDfuFlagNone,
|
flags: universalShifterDfuFlagNone,
|
||||||
vectorStackPointer: vectorStackPointer,
|
vectorStackPointer: vectorStackPointer,
|
||||||
vectorReset: vectorReset,
|
vectorReset: vectorReset,
|
||||||
@ -215,7 +217,12 @@ class FirmwareFileSelectionService {
|
|||||||
return ByteData.sublistView(bytes).getUint32(offset, Endian.little);
|
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() {
|
static int _randomSessionId() {
|
||||||
return Random.secure().nextInt(256);
|
return Random.secure().nextInt(255) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,6 +120,26 @@ void main() {
|
|||||||
result.failure?.reason, FirmwareSelectionFailureReason.imageTooLarge);
|
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 {
|
test('rejects images with invalid vector table', () async {
|
||||||
final image = _validBootloaderImage();
|
final image = _validBootloaderImage();
|
||||||
_writeLeU32(image, 0, 0x10001000);
|
_writeLeU32(image, 0, 0x10001000);
|
||||||
@ -158,6 +178,23 @@ void main() {
|
|||||||
expect(second.firmware?.metadata.sessionId, 10);
|
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 {
|
test('maps picker read failure to explicit validation error', () async {
|
||||||
final service = FirmwareFileSelectionService(
|
final service = FirmwareFileSelectionService(
|
||||||
filePicker: _FakeFirmwareFilePicker(
|
filePicker: _FakeFirmwareFilePicker(
|
||||||
|
|||||||
Reference in New Issue
Block a user