Repository navigation
add image update payload builders (gen5) #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // Device image update commands (gen5 only). Pure payload builders: nothing | ||
| // here sends anything, and none of these opcodes may leave the host without | ||
| // the caller's explicit allowDangerous opt-in (0x8E, 0x8F and 0x90 are | ||
| // destructive; 0x53 only asks the strap to check what is staged). | ||
| // | ||
| // 0x8E start load [0x01] | ||
| // 0x8F load data [0x01][offset u32 LE][len u8][data] | ||
| // 0x53 verify image [0x01] | ||
| // 0x90 process image [0x01] — commit; cannot be undone from the host. | ||
| // | ||
| // A status of 1 on 0x90 only means the staged bytes were accepted and | ||
| // checksummed. It is not proof the image is now running. | ||
| import 'dart:typed_data'; | ||
|
|
||
| class FwUpdate { | ||
| static const int startLoad = 0x8E; | ||
| static const int loadData = 0x8F; | ||
| static const int verifyImage = 0x53; | ||
| static const int processImage = 0x90; | ||
|
|
||
| /// Length field of [loadData] is one byte. | ||
| static const int maxChunk = 255; | ||
|
|
||
| /// Bytes per [loadData]. Small enough that the framed write fits a typical | ||
| /// negotiated ATT payload (8 header + padded 9+128 inner + 4 crc = 152). | ||
| static const int chunk = 128; | ||
|
|
||
| static const List<int> rev1 = [0x01]; | ||
|
|
||
| static Uint8List loadDataPayload(int offset, List<int> data) { | ||
| if (data.isEmpty || data.length > maxChunk) { | ||
| throw ArgumentError('chunk must be 1..$maxChunk bytes, got ${data.length}'); | ||
| } | ||
| if (offset < 0 || offset > 0xFFFFFFFF) { | ||
| throw ArgumentError('offset out of u32 range: $offset'); | ||
| } | ||
| final b = BytesBuilder() | ||
| ..addByte(0x01) | ||
| ..add((ByteData(4)..setUint32(0, offset, Endian.little)) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| .buffer | ||
| .asUint8List()) | ||
| ..addByte(data.length) | ||
| ..add(data); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject data values outside the byte range.
|
||
| return b.toBytes(); | ||
| } | ||
|
|
||
| static int chunkCount(int imageLen) { | ||
| if (imageLen <= 0) throw ArgumentError('image length must be > 0, got $imageLen'); | ||
| return (imageLen + chunk - 1) ~/ chunk; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import 'package:openstrap_protocol/openstrap_protocol.dart'; | ||
| import 'package:test/test.dart'; | ||
|
|
||
| void main() { | ||
| test('load data payload layout', () { | ||
| final p = FwUpdate.loadDataPayload(0x01020304, [9, 8, 7]); | ||
| expect(p, [0x01, 0x04, 0x03, 0x02, 0x01, 3, 9, 8, 7]); | ||
| }); | ||
| test('rejects empty and oversize chunks', () { | ||
| expect(() => FwUpdate.loadDataPayload(0, const []), throwsArgumentError); | ||
| expect(() => FwUpdate.loadDataPayload(0, List.filled(256, 0)), | ||
| throwsArgumentError); | ||
| }); | ||
| test('rejects offsets outside u32', () { | ||
| expect(() => FwUpdate.loadDataPayload(-1, [1]), throwsArgumentError); | ||
| expect(() => FwUpdate.loadDataPayload(0x100000000, [1]), throwsArgumentError); | ||
| }); | ||
| test('chunk count rejects empty and negative lengths', () { | ||
| expect(() => FwUpdate.chunkCount(0), throwsArgumentError); | ||
| expect(() => FwUpdate.chunkCount(-5), throwsArgumentError); | ||
| }); | ||
| test('chunk count rounds up', () { | ||
| expect(FwUpdate.chunkCount(128), 1); | ||
| expect(FwUpdate.chunkCount(129), 2); | ||
| }); | ||
| test('start, load and process are blocked by the generic guard', () { | ||
| for (final op in [FwUpdate.startLoad, FwUpdate.loadData, FwUpdate.processImage]) { | ||
| expect(dangerousCmds.contains(op), isTrue); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium · Verification bypasses opt-in
When a caller relies on
dangerousCmdsto gate image-update commands and has not opted in,dangerousCmdsomitsFwUpdate.verifyImage's opcode0x53, so the generic guard permits verification withoutallowDangerous, contrary to the requirement that all four update opcodes need opt-in.Add opcode
0x53todangerousCmdsand cover verification in the guard test.Also at
lib/src/firmware_update.dart:17-18.Prompt for AI agents