Skip to main content

config_generator/
annotate.rs

1// Copyright (c) 2026 Tuloup Simon
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Human-readable rendering of the configuration zone blob.
17//!
18//! Produces a multi-line text annotation that pairs every byte with its
19//! semantic meaning. Useful as a sanity check before committing to the
20//! irreversible Lock command.
21
22use std::fmt::Write;
23
24/// Render a 128-byte blob as an annotated text document.
25///
26/// The annotation lists every byte alongside the field it belongs to and
27/// flags the value (factory-area, lock state, slot configuration, etc.).
28/// The CRC value passed in is included at the bottom and is meant to be
29/// the CRC of the writable portion (bytes 16-127), computed by the caller.
30#[must_use]
31pub(crate) fn format(blob: &[u8], crc: u16) -> String
32{
33    let mut out = String::with_capacity(8192);
34
35    let _ = writeln!(out, "ATECC608B configuration zone, 128 bytes");
36    let _ = writeln!(out, "========================================");
37    let _ = writeln!(out);
38    let _ = writeln!(out, "Bytes 0-15 are the read-only factory area. The chip ignores any");
39    let _ = writeln!(out, "Write command targeted at this range. The values shown here are");
40    let _ = writeln!(out, "placeholders from the generator and do not reflect what is actually");
41    let _ = writeln!(out, "stored in the chip.");
42    let _ = writeln!(out);
43
44    write_factory_area(&mut out, &blob[0..16]);
45    let _ = writeln!(out);
46
47    write_device_config(&mut out, &blob[16..20]);
48    let _ = writeln!(out);
49
50    write_slot_configs(&mut out, &blob[20..52]);
51    let _ = writeln!(out);
52
53    write_counters(&mut out, &blob[52..68]);
54    let _ = writeln!(out);
55
56    write_feature_area(&mut out, &blob[68..84]);
57    let _ = writeln!(out);
58
59    write_lock_area(&mut out, &blob[84..96]);
60    let _ = writeln!(out);
61
62    write_key_configs(&mut out, &blob[96..128]);
63    let _ = writeln!(out);
64
65    write_summary(&mut out, blob, crc);
66    out
67}
68
69fn write_factory_area(out: &mut String, bytes: &[u8])
70{
71    let _ = writeln!(out, "Factory area (bytes 0-15, read-only):");
72    let _ = writeln!(out, "  SN[0..4]      (0-3):   {}", hex_row(&bytes[0..4]));
73    let _ = writeln!(out, "  RevNum        (4-7):   {}", hex_row(&bytes[4..8]));
74    let _ = writeln!(out, "  SN[4..8]      (8-11):  {}", hex_row(&bytes[8..12]));
75    let _ = writeln!(out, "  SN[8]         (12):    {:02X} (must be 0xEE on real chip)", bytes[12]);
76    let _ = writeln!(out, "  AES_Enable    (13):    {:02X}", bytes[13]);
77    let _ = writeln!(out, "  I2C_Enable    (14):    {:02X}", bytes[14]);
78    let _ = writeln!(out, "  Reserved1     (15):    {:02X}", bytes[15]);
79}
80
81fn write_device_config(out: &mut String, bytes: &[u8])
82{
83    let _ = writeln!(out, "Device configuration (bytes 16-19):");
84    let _ = writeln!(out, "  I2C_Address   (16):    0x{:02X}  (7-bit addr 0x{:02X} in 8-bit form)",
85        bytes[0], bytes[0] >> 1);
86    let _ = writeln!(out, "  Reserved2     (17):    0x{:02X}  (must be 0x00)", bytes[1]);
87    let _ = writeln!(out, "  CountMatch    (18):    0x{:02X}  (feature disabled)", bytes[2]);
88    let _ = writeln!(out, "  ChipMode      (19):    0x{:02X}  ({})",
89        bytes[3], decode_chip_mode(bytes[3]));
90}
91
92fn write_slot_configs(out: &mut String, bytes: &[u8])
93{
94    let _ = writeln!(out, "SlotConfig[16] (bytes 20-51), 2 bytes per slot, little-endian:");
95    for slot in 0..16
96    {
97        let lo = bytes[2 * slot];
98        let hi = bytes[2 * slot + 1];
99        let word = u16::from(lo) | (u16::from(hi) << 8);
100        let _ = writeln!(out,
101            "  Slot {slot:2}: bytes {lo:02X} {hi:02X} -> 0x{word:04X}  {}",
102            describe_slot_config(slot as u8, word),
103        );
104    }
105}
106
107fn write_counters(out: &mut String, bytes: &[u8])
108{
109    let _ = writeln!(out, "Counters (bytes 52-67):");
110    let _ = writeln!(out, "  Counter0      (52-59): {}", hex_row(&bytes[0..8]));
111    let _ = writeln!(out, "  Counter1      (60-67): {}", hex_row(&bytes[8..16]));
112    let _ = writeln!(out, "  Note: 0xFF... means factory-default 'counter at zero'.");
113}
114
115fn write_feature_area(out: &mut String, bytes: &[u8])
116{
117    let _ = writeln!(out, "Feature configuration (bytes 68-83):");
118    let _ = writeln!(out, "  UseLock              (68):    0x{:02X}", bytes[0]);
119    let _ = writeln!(out, "  VolatileKeyPermission (69):   0x{:02X}", bytes[1]);
120    let _ = writeln!(out, "  SecureBoot           (70-71): {} {}",
121        hex_byte(bytes[2]), hex_byte(bytes[3]));
122    let _ = writeln!(out, "  KdflvLoc             (72):    0x{:02X}", bytes[4]);
123    let _ = writeln!(out, "  KdflvStr             (73-74): {} {}",
124        hex_byte(bytes[5]), hex_byte(bytes[6]));
125    let _ = writeln!(out, "  Reserved3            (75-83): {}", hex_row(&bytes[7..16]));
126}
127
128fn write_lock_area(out: &mut String, bytes: &[u8])
129{
130    let _ = writeln!(out, "Lock and chip-option area (bytes 84-95):");
131    let _ = writeln!(out, "  UserExtra            (84):    0x{:02X}", bytes[0]);
132    let _ = writeln!(out, "  UserExtraAdd         (85):    0x{:02X}", bytes[1]);
133    let _ = writeln!(out, "  LockValue (data)     (86):    0x{:02X}  ({})",
134        bytes[2], if bytes[2] == 0x55 { "unlocked" } else { "locked" });
135    let _ = writeln!(out, "  LockConfig (config)  (87):    0x{:02X}  ({})",
136        bytes[3], if bytes[3] == 0x55 { "unlocked" } else { "locked" });
137    let _ = writeln!(out, "  SlotLocked bitmap    (88-89): {} {}",
138        hex_byte(bytes[4]), hex_byte(bytes[5]));
139    let _ = writeln!(out, "  ChipOptions          (90-91): {} {}",
140        hex_byte(bytes[6]), hex_byte(bytes[7]));
141    let _ = writeln!(out, "  X509format           (92-95): {} {} {} {}",
142        hex_byte(bytes[8]), hex_byte(bytes[9]), hex_byte(bytes[10]), hex_byte(bytes[11]));
143}
144
145fn write_key_configs(out: &mut String, bytes: &[u8])
146{
147    let _ = writeln!(out, "KeyConfig[16] (bytes 96-127), 2 bytes per slot, little-endian:");
148    for slot in 0..16
149    {
150        let lo = bytes[2 * slot];
151        let hi = bytes[2 * slot + 1];
152        let word = u16::from(lo) | (u16::from(hi) << 8);
153        let _ = writeln!
154        (   out,
155            "  Slot {slot:2}: bytes {lo:02X} {hi:02X} -> 0x{word:04X}  {}",
156            describe_key_config(word),
157        );
158    }
159}
160
161fn write_summary(out: &mut String, blob: &[u8], crc: u16)
162{
163    let _ = writeln!(out, "Summary:");
164    let _ = writeln!(out, "  Total bytes:                  {}", blob.len());
165    let _ = writeln!(out, "  Writable portion (16-127):    {} bytes", blob.len() - 16);
166    let _ = writeln!(out, "  CRC-16 of writable portion:   0x{crc:04X}");
167    let _ = writeln!(out);
168    let _ = writeln!(out, "Lock the configuration zone only after verifying that the chip");
169    let _ = writeln!(out, "contains the same bytes 16-127 as above, and only with a host");
170    let _ = writeln!(out, "command that passes the expected CRC 0x{crc:04X} as a safety check.");
171}
172
173fn decode_chip_mode(byte: u8) -> String
174{
175    let i2c_extra = byte & 1;
176    let ttl       = (byte >> 1) & 1;
177    let wdg_long  = (byte >> 2) & 1;
178    let clk_div   = (byte >> 3) & 0x1F;
179
180    let clock = match clk_div
181    {
182        0x00 => "M0",
183        0x05 => "M1",
184        0x0D => "M2",
185        _    => "unknown",
186    };
187    let watchdog = if wdg_long == 1 { "long" } else { "short" };
188
189    format!
190    (
191        "I2C_Extra={i2c_extra}, TTL={ttl}, watchdog={watchdog}, clock_divider={clock}",
192    )
193}
194
195fn describe_slot_config(slot: u8, word: u16) -> String
196{
197    // We assume the matching KeyConfig.Private bit dictates which layout
198    // applies, but for annotation purposes we always print the ECC view.
199    // Slots known to hold data are explicitly labelled.
200    let ext_sig     =  word        & 1;
201    let is_secret   = (word >> 7)  & 1;
202    let gen_key     = (word >> 8)  & 1;
203    let priv_write  = (word >> 9)  & 1;
204    let write_config= (word >> 12) & 0xF;
205    let limited_use = (word >> 5)  & 1;
206    let read_key    =  word        & 0xF;
207    let write_key   = (word >> 8)  & 0xF;
208
209    let wc = match write_config
210    {
211        0x0 => "Always",
212        0x2 => "Never",
213        0x4 => "Always_then_Encrypt",
214        0x6 => "Encrypt",
215        0x8 => "Never (alt)",
216        0xC => "Never_then_Encrypt",
217        _   => "reserved",
218    };
219
220    match slot
221    {
222        5 | 6 => format!
223        (
224            "(data) ReadKey={read_key}, LimitedUse={limited_use}, IsSecret={is_secret}, WriteKey={write_key}, WriteConfig={wc}",
225        ),
226        8 => format!
227        (
228            "(I/O master key) IsSecret={is_secret}, WriteConfig={wc}",
229        ),
230        _ => format!
231        (
232            "(ECC) ExtSig={ext_sig}, IsSecret={is_secret}, GenKey={gen_key}, PrivWrite={priv_write}, WriteConfig={wc}",
233        ),
234    }
235}
236
237fn describe_key_config(word: u16) -> String
238{
239    let private  =  word        & 1;
240    let pub_info = (word >> 1)  & 1;
241    let key_type = (word >> 2)  & 0x07;
242    let lockable = (word >> 5)  & 1;
243    let req_auth = (word >> 7)  & 1;
244    let auth_key = (word >> 8)  & 0xF;
245
246    let kt = match key_type
247    {
248        0 => "B283",
249        1 => "K283",
250        4 => "P-256",
251        6 => "AES",
252        7 => "Data 32B",
253        _ => "unknown",
254    };
255
256    if private == 1
257    {
258        format!
259        (
260            "Private={private}, PubInfo={pub_info}, KeyType={kt}, Lockable={lockable}, ReqAuth={req_auth}, AuthKey={auth_key}",
261        )
262    }
263    else
264    {
265        format!
266        (
267            "Private={private}, KeyType={kt}, Lockable={lockable}",
268        )
269    }
270}
271
272fn hex_row(bytes: &[u8]) -> String
273{
274    bytes.iter().map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" ")
275}
276
277fn hex_byte(byte: u8) -> String
278{
279    format!("{byte:02X}")
280}
281
282#[cfg(test)]
283mod tests
284{
285    use super::*;
286    use crate::blob::build;
287    use crate::crc::crc16;
288
289    #[test]
290    fn annotation_runs_without_panicking()
291    {
292        let blob = build();
293        let crc = crc16(&blob[16..128]);
294        let text = format(&blob, crc);
295        assert!(text.contains("0xC92D"));
296        assert!(text.contains("Slot  0"));
297        assert!(text.contains("Slot 15"));
298    }
299
300    #[test]
301    fn slot_0_annotation_mentions_genkey_only()
302    {
303        let blob = build();
304        let crc = crc16(&blob[16..128]);
305        let text = format(&blob, crc);
306        // Slot 0 line should mention GenKey=1, PrivWrite=0.
307        let slot0_line = text
308            .lines()
309            .find(|line| line.contains("Slot  0:") && line.contains("ECC"))
310            .expect("slot 0 line must be present");
311        assert!(slot0_line.contains("GenKey=1"));
312        assert!(slot0_line.contains("PrivWrite=0"));
313    }
314
315    #[test]
316    fn slot_2_annotation_mentions_privwrite_on()
317    {
318        let blob = build();
319        let crc = crc16(&blob[16..128]);
320        let text = format(&blob, crc);
321        let slot2_line = text
322            .lines()
323            .find(|line| line.contains("Slot  2:") && line.contains("ECC"))
324            .expect("slot 2 line must be present");
325        assert!(slot2_line.contains("GenKey=1"));
326        assert!(slot2_line.contains("PrivWrite=1"));
327    }
328
329    #[test]
330    fn slot_5_annotation_labelled_as_data()
331    {
332        let blob = build();
333        let crc = crc16(&blob[16..128]);
334        let text = format(&blob, crc);
335        let slot5_line = text
336            .lines()
337            .find(|line| line.starts_with("  Slot  5:"))
338            .expect("slot 5 line must be present");
339        assert!(slot5_line.contains("(data)"));
340        assert!(slot5_line.contains("LimitedUse=1"));
341    }
342}