1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! DFU split firmware update — update split peripherals over the split link.
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────────┐
//! │ SPLIT DFU UPDATE │
//! │ (peripheral connects / is connected) │
//! └─────────────────────────────────────────────────────────────────────┘
//!
//!
//! ═══ PHASE 0: HANDSHAKE ── hash comparison ───────────────────────────
//! Do we need to update the peripheral?
//!
//! ```text
//! Central Peripheral
//! │ │
//! │ ├── (on boot) FirmwareHashResponse(hash)
//! │ │ <- read_embedded_firmware_hash()
//! │ │ = CRC32(__vector_table..__veneer_limit)
//! │ │
//! │ ── or ── │
//! │ │
//! ├── FirmwareHashQuery ─────────────>│
//! │<── FirmwareHashResponse(hash) ────┤
//! │ │
//! │ hash == expected_hash? │
//! │ ├─ Yes → STOP (up-to-date) │
//! │ └─ No → ↓ │
//! │ │
//! ```
//!
//!
//! ═══ PHASE 1: CHUNK TRANSFER ── per-chunk CRC ────────────────────────
//!
//! Central calculates CRC of every chunk it sends, peripheral does as well
//! and sends the CRC back in a FirmwareChunkAck. If they match, central
//! sends the next chunk; if not, retry. Central also accumulates the
//! overall CRC (`central_crc.update(chunk)`).
//!
//! ```text
//! (outer attempt loop: 1..3)
//!
//! Central Peripheral
//! │ │
//! │ central_crc = Crc32::new() │
//! │ │
//! │ for chunk in firmware[256]: │
//! │ chunk_crc = CRC32(chunk) │
//! │ central_crc.update(chunk) │
//! │ │
//! │ retry = 0 │
//! │ ┌─ retry < 3 ─────────────────┐ │
//! │ │ │ │
//! ├── FirmwareChunk{offset,data} ──>│ │
//! │ │ │ │
//! │ │ handler.write_chunk()
//! │ │ — incremental erase (page-by-page)
//! │ │ — write to flash
//! │ │ chunk_crc = CRC32(chunk) │
//! │ │ │ │
//! │ │<─ FirmwareChunkAck{offset,──┤ │
//! │ │ crc: chunk_crc} │ │
//! │ │ │ │
//! │ CRC match: │ │
//! │ ack_crc == chunk_crc? │ │
//! │ ├─ Yes → next chunk │ │
//! │ └─ No → retry++ ────── ────┘ │
//! │ │
//! │ All chunks acked? │
//! │ ├─ Yes → ↓ │
//! │ └─ No → attempt++ → Phase 1 │
//! ```
//!
//!
//! ═══ PHASE 2: END-TO-END CRC ── flash readback ──────────────────────
//!
//! Central compares its accumulated CRC with the firmware hash. If ok it
//! sends FirmwareUpdateComplete. Peripheral reads back the DFU partition
//! from flash and sends FirmwareCrcReport. Central verifies; on match it
//! sends FirmwareCrcOk, peripheral marks the firmware valid and resets.
//!
//! ```text
//! Central Peripheral
//! │ │
//! │ central_crc.finalize() │
//! │ == expected_hash? │
//! │ ├─ No → ABORT (binary bug!) │
//! │ └─ Yes → ↓ │
//! │ │
//! ├── FirmwareUpdateComplete ────────>│
//! │ │
//! │ handler.compute_dfu_crc()
//! │ = CRC32(whole DFU partition)
//! │ via flash readback (256B blocks)
//! │ │
//! │<── FirmwareCrcReport(dfu_crc) ────┤
//! │ │
//! │ dfu_crc == expected_hash? │
//! │ ├─ Yes → ↓ │
//! │ └─ No → send CrcFail ─────┐ │
//! │ │ │
//! │ │ attempt++ → Phase 1
//! │ │ │
//! │ ├── FirmwareCrcOk ────────>│ │
//! │ │ │ │
//! │ │ handler.mark_updated_and_reset()
//! │ │ (only after CrcOk — never into corrupt FW)
//! │ │ │ │
//! │ │<─ FirmwareUpdateConfirm ─┤ │
//! │ │ │ │
//! │ │ │ │
//! │ └── CrcOk in 5s? ──────────│ │
//! │ No → timeout → abort │ │
//! │ │ │
//! │ └── CrcFail in 5s? ───── ──┤ │
//! │ No → timeout → abort ─┘ │
//! ```
//!
//!
//! ═══ RETRY SUMMARY ════════════════════════════════════════════════════
//!
//! ```text
//! Layer Max Trigger Consequence
//! ───── ─── ─────── ──────────
//! Per-chunk 3× Ack CRC mismatch Re-send same chunk
//! or 2s timeout
//!
//! Outer attempt 3× Chunk never acked Full restart of
//! or E2E CRC mismatch Phase 1 + 2
//! or CRC timeout
//!
//! No retry — Central CRC != expected Abort — binary
//! (central_crc.finalize()) mismatch, not TX
//! ```
//!
//!
//! ═══ SAFETY GATES ════════════════════════════════════════════════════
//!
//! ```text
//! Where What Why
//! ───── ──── ───
//! Peripheral boot mark_updated_and_reset() Never boot into
//! only on FirmwareCrcOk corrupt firmware
//!
//! After all chunks central_crc == expected_hash Catch binary bugs
//! before asking peripheral
//!
//! End-to-end compute_dfu_crc() Catch silent flash
//! flash readback write errors
//!
//! Per-chunk CRC32(data) in Ack Catch bitflips,
//! packet loss,
//! wrong offset
//! ```
pub use ;
pub use ;
pub use ;