fwtool 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fwtool-1.0.0/.gitignore +1 -0
- fwtool-1.0.0/LICENSE +19 -0
- fwtool-1.0.0/PKG-INFO +446 -0
- fwtool-1.0.0/README.md +416 -0
- fwtool-1.0.0/pyproject.toml +33 -0
- fwtool-1.0.0/requirements.txt +4 -0
- fwtool-1.0.0/requirements_dev.txt +82 -0
- fwtool-1.0.0/src/fwtool/__init__.py +0 -0
- fwtool-1.0.0/src/fwtool/cli.py +741 -0
- fwtool-1.0.0/test/test_fwtool.py +335 -0
- fwtool-1.0.0/uv.lock +524 -0
fwtool-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__pycache__
|
fwtool-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright 2026 Mohammad Mohsen <kuro.ece@gmail.com>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the “Software”), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
8
|
+
subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
19
|
+
|
fwtool-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fwtool
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A command-line interface for working with a custom firmware header placed at the beginning of a binary image.
|
|
5
|
+
Author-email: Mohammad Mohsen <kuro.ece@gmail.com>
|
|
6
|
+
License: Copyright 2026 Mohammad Mohsen <kuro.ece@gmail.com>
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
9
|
+
this software and associated documentation files (the “Software”), to deal in
|
|
10
|
+
the Software without restriction, including without limitation the rights to
|
|
11
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
12
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
13
|
+
subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
20
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
21
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
22
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
23
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
|
+
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Classifier: Programming Language :: Python
|
|
27
|
+
Requires-Python: >=3.13
|
|
28
|
+
Requires-Dist: crccheck>=1.3.1
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# fwtool
|
|
32
|
+
|
|
33
|
+
`fwtool` is a command-line utility for creating, updating, inspecting, and verifying
|
|
34
|
+
a custom firmware metadata header prepended to a binary image.
|
|
35
|
+
|
|
36
|
+
It is intended for firmware packaging workflows where a raw application binary is
|
|
37
|
+
post-processed into a single flashable image:
|
|
38
|
+
|
|
39
|
+
- a fixed-size header is placed at the beginning
|
|
40
|
+
- the firmware payload follows immediately after
|
|
41
|
+
- the header contains version, payload size, and CRC information
|
|
42
|
+
|
|
43
|
+
This is useful for bootloaders or firmware update logic that need to validate
|
|
44
|
+
and identify an application image before booting or programming it.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## What it does
|
|
49
|
+
|
|
50
|
+
`fwtool` supports four main operations:
|
|
51
|
+
|
|
52
|
+
1. **Attach** a new header to a raw firmware binary
|
|
53
|
+
2. **Edit** an existing header in a packaged binary
|
|
54
|
+
3. **Print** the parsed contents of an existing header
|
|
55
|
+
4. **Verify** that an existing header matches the payload
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Header format
|
|
60
|
+
|
|
61
|
+
The tool generates a **256-byte header** with the following layout:
|
|
62
|
+
|
|
63
|
+
| Offset | Size | Field | Description |
|
|
64
|
+
|--------------|------|---------|----------------------------------------------------|
|
|
65
|
+
| `0x00..0x03` | 4 | magic | ASCII string `XLAB` |
|
|
66
|
+
| `0x04..0x07` | 4 | version | Little-endian bytes: `[0x00, patch, minor, major]` |
|
|
67
|
+
| `0x08..0x0B` | 4 | size | Payload size in bytes, little-endian `uint32` |
|
|
68
|
+
| `0x0C..0x0F` | 4 | crc32 | CRC-32/MPEG-2 of payload, little-endian `uint32` |
|
|
69
|
+
| `0x10..0xFF` | 240 | padding | Filled with `0xFF` |
|
|
70
|
+
### Notes
|
|
71
|
+
|
|
72
|
+
- The CRC is calculated over the **payload only**, not over the header.
|
|
73
|
+
- The size stored in the header is the **payload size only**.
|
|
74
|
+
- The version is stored as:
|
|
75
|
+
- byte 0: `0x00`
|
|
76
|
+
- byte 1: patch
|
|
77
|
+
- byte 2: minor
|
|
78
|
+
- byte 3: major
|
|
79
|
+
|
|
80
|
+
For example, version `1.2.3` is stored as:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
00 03 02 01
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Typical use case
|
|
89
|
+
|
|
90
|
+
A common workflow looks like this:
|
|
91
|
+
|
|
92
|
+
1. Link the firmware application so that it expects to execute after the reserved header space.
|
|
93
|
+
2. Build the raw application binary.
|
|
94
|
+
3. Use `fwtool` to prepend the metadata header.
|
|
95
|
+
4. Program the resulting combined binary into flash.
|
|
96
|
+
|
|
97
|
+
For example:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
Flash address 0x08004000:
|
|
101
|
+
[256-byte metadata header]
|
|
102
|
+
[firmware payload]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Installation
|
|
108
|
+
|
|
109
|
+
### Requirements
|
|
110
|
+
|
|
111
|
+
- Python 3.9+
|
|
112
|
+
- [`crccheck`](https://pypi.org/project/crccheck/)
|
|
113
|
+
|
|
114
|
+
### Install dependency manually
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install crccheck
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Run directly
|
|
121
|
+
|
|
122
|
+
If you have the script as `fwtool.py`, you can run it with:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python fwtool.py ...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Install as a CLI tool
|
|
129
|
+
|
|
130
|
+
If the project includes a `pyproject.toml`, install it in editable mode:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pip install -e .
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Then run it as:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
fwtool ...
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Usage
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
fwtool binary [version] [output] [options]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Positional arguments
|
|
151
|
+
|
|
152
|
+
- `binary`
|
|
153
|
+
Path to input binary file
|
|
154
|
+
|
|
155
|
+
- `version`
|
|
156
|
+
Firmware version string such as:
|
|
157
|
+
- `1`
|
|
158
|
+
- `1.2`
|
|
159
|
+
- `1.2.3`
|
|
160
|
+
|
|
161
|
+
- `output`
|
|
162
|
+
Output path for attach/edit operations
|
|
163
|
+
|
|
164
|
+
### Options
|
|
165
|
+
|
|
166
|
+
- `--mode {attach,edit}`
|
|
167
|
+
Select how the tool treats the input file:
|
|
168
|
+
- `attach`: input is a raw binary without a header
|
|
169
|
+
- `edit`: input already contains a header and it will be replaced
|
|
170
|
+
|
|
171
|
+
- `--in-place`
|
|
172
|
+
Modify the input file directly instead of writing to a separate output file
|
|
173
|
+
|
|
174
|
+
- `--print-header`
|
|
175
|
+
Parse and print the header from an existing packaged binary
|
|
176
|
+
|
|
177
|
+
- `--verify-header`
|
|
178
|
+
Verify header magic, payload size, and payload CRC against the payload
|
|
179
|
+
|
|
180
|
+
- `--json`
|
|
181
|
+
Print machine-readable JSON output for `--print-header` or `--verify-header`
|
|
182
|
+
|
|
183
|
+
- `--quiet`
|
|
184
|
+
Suppress output for `--verify-header` and use exit code only
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Examples
|
|
189
|
+
|
|
190
|
+
### 1. Attach a new header to a raw binary
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
fwtool firmware.bin 1.2.3 packaged.bin --mode attach
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
This creates:
|
|
197
|
+
|
|
198
|
+
```text
|
|
199
|
+
packaged.bin = [256-byte header][firmware.bin payload]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
### 2. Replace the header of an existing packaged binary
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
fwtool packaged.bin 1.2.4 updated.bin --mode edit
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This keeps the payload but replaces the header with updated metadata.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### 3. Replace the header in place
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
fwtool packaged.bin 1.2.4 --mode edit --in-place
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
This modifies `packaged.bin` directly.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### 4. Attach a header in place
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
fwtool firmware.bin 1.2.3 --mode attach --in-place
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
This replaces the raw input file with a packaged binary containing the header.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### 5. Print header contents
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
fwtool packaged.bin --print-header
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Example output:
|
|
241
|
+
|
|
242
|
+
```text
|
|
243
|
+
magic: b'XLAB'
|
|
244
|
+
version: 1.2.3
|
|
245
|
+
size: 123456 bytes
|
|
246
|
+
crc32: 0x1a2b3c4d
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### 6. Print header contents as JSON
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
fwtool packaged.bin --print-header --json
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Example output:
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{
|
|
261
|
+
\"magic_ascii\": \"XLAB\",
|
|
262
|
+
\"magic_hex\": \"584c4142\",
|
|
263
|
+
\"version\": {
|
|
264
|
+
\"major\": 1,
|
|
265
|
+
\"minor\": 2,
|
|
266
|
+
\"patch\": 3,
|
|
267
|
+
\"string\": \"1.2.3\"
|
|
268
|
+
},
|
|
269
|
+
\"size\": 123456,
|
|
270
|
+
\"crc\": {
|
|
271
|
+
\"int\": 439041101,
|
|
272
|
+
\"hex\": \"0x1a2b3c4d\"
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### 7. Verify a packaged binary
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
fwtool packaged.bin --verify-header
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Example output:
|
|
286
|
+
|
|
287
|
+
```text
|
|
288
|
+
magic: OK
|
|
289
|
+
version: 1.2.3
|
|
290
|
+
size: OK (header=123456, actual=123456)
|
|
291
|
+
crc32: OK (header=0x1a2b3c4d, actual=0x1a2b3c4d)
|
|
292
|
+
verification: OK
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
### 8. Verify quietly using only the exit code
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
fwtool packaged.bin --verify-header --quiet
|
|
301
|
+
echo $?
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Exit code meanings:
|
|
305
|
+
|
|
306
|
+
- `0`: verification passed
|
|
307
|
+
- `1`: verification failed
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### 9. Verify with JSON output
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
fwtool packaged.bin --verify-header --json
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Example output:
|
|
318
|
+
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
\"ok\": true,
|
|
322
|
+
\"magic_ok\": true,
|
|
323
|
+
\"size_ok\": true,
|
|
324
|
+
\"crc_ok\": true,
|
|
325
|
+
\"header\": {
|
|
326
|
+
\"magic_ascii\": \"XLAB\",
|
|
327
|
+
\"magic_hex\": \"584c4142\",
|
|
328
|
+
\"version\": {
|
|
329
|
+
\"major\": 1,
|
|
330
|
+
\"minor\": 2,
|
|
331
|
+
\"patch\": 3,
|
|
332
|
+
\"string\": \"1.2.3\"
|
|
333
|
+
},
|
|
334
|
+
\"size\": 123456,
|
|
335
|
+
\"crc\": {
|
|
336
|
+
\"int\": 439041101,
|
|
337
|
+
\"hex\": \"0x1a2b3c4d\"
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
\"payload\": {
|
|
341
|
+
\"size\": 123456,
|
|
342
|
+
\"crc\": {
|
|
343
|
+
\"int\": 439041101,
|
|
344
|
+
\"hex\": \"0x1a2b3c4d\"
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Version handling
|
|
353
|
+
|
|
354
|
+
Accepted version formats:
|
|
355
|
+
|
|
356
|
+
- `1` → `1.0.0`
|
|
357
|
+
- `1.2` → `1.2.0`
|
|
358
|
+
- `1.2.3` → `1.2.3`
|
|
359
|
+
|
|
360
|
+
Rules:
|
|
361
|
+
|
|
362
|
+
- missing components are filled with zero
|
|
363
|
+
- extra components are ignored
|
|
364
|
+
- each component must be in the range `0..255`
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Verification behavior
|
|
369
|
+
|
|
370
|
+
When `--verify-header` is used, the tool checks:
|
|
371
|
+
|
|
372
|
+
- the magic field is `XLAB`
|
|
373
|
+
- the payload size matches the size stored in the header
|
|
374
|
+
- the payload CRC matches the CRC stored in the header
|
|
375
|
+
|
|
376
|
+
The header itself is **not** included in the size or CRC calculation.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Exit codes
|
|
381
|
+
|
|
382
|
+
### General operations
|
|
383
|
+
|
|
384
|
+
- `0` on success
|
|
385
|
+
- non-zero on failure
|
|
386
|
+
|
|
387
|
+
### `--verify-header`
|
|
388
|
+
|
|
389
|
+
- `0` if verification succeeds
|
|
390
|
+
- `1` if verification fails
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Common workflow example
|
|
395
|
+
|
|
396
|
+
Build a raw firmware image:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
arm-none-eabi-objcopy -O binary app.elf app.bin
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Attach metadata header:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
fwtool app.bin 1.2.3 app_packed.bin --mode attach
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Program the combined image to flash:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
st-flash write app_packed.bin 0x08004000
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Notes for embedded use
|
|
417
|
+
|
|
418
|
+
If your firmware image is packaged with a prepended header, the application must
|
|
419
|
+
typically be linked to execute **after** the reserved header region.
|
|
420
|
+
|
|
421
|
+
Example:
|
|
422
|
+
|
|
423
|
+
- metadata region starts at `0x08004000`
|
|
424
|
+
- header size is `0x100`
|
|
425
|
+
- application is linked to start at `0x08004100`
|
|
426
|
+
|
|
427
|
+
Then the combined image can be programmed at `0x08004000`, and the application
|
|
428
|
+
payload will land at the correct runtime address.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Development
|
|
433
|
+
|
|
434
|
+
### Run tests
|
|
435
|
+
|
|
436
|
+
If you have a `pytest` test suite:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
pytest -q
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## License
|
|
445
|
+
|
|
446
|
+
MIT License
|