Skip to content

Commit 3c24468

Browse files
committed
chore: document general and app attributes
1 parent 76b9397 commit 3c24468

3 files changed

Lines changed: 272 additions & 45 deletions

File tree

tockloader-lib/src/attributes/app_attributes.rs

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ use tokio_serial::SerialStream;
1212
use crate::bootloader_serial::{issue_command, Command, Response};
1313
use crate::errors::TockloaderError;
1414

15+
/// Structure used to package an app's header and footer data.
16+
/// This structure is used to package together all relevant information like metadata about an app.
17+
/// The information is usually stored within a [TbfHeader], and one or more [TbfFooters](TbfFooterV2Credentials).
18+
/// For more details see <https://book.tockos.org/doc/tock_binary_format>
1519
#[derive(Debug)]
1620
pub struct AppAttributes {
1721
pub tbf_header: TbfHeader,
1822
pub tbf_footers: Vec<TbfFooter>,
1923
}
2024

25+
/// Structure used to package a footers credential data and the size of the footer.
26+
/// This is where credentials of the footer is stored
2127
#[derive(Debug)]
2228
pub struct TbfFooter {
2329
pub credentials: TbfFooterV2Credentials,
@@ -39,8 +45,29 @@ impl AppAttributes {
3945
tbf_footers: footers_data,
4046
}
4147
}
42-
43-
// TODO: Document this function
48+
/// The function below is used to retrieve header and footer data
49+
///
50+
/// Starting from the 0x40000 address,
51+
/// using the ['parse_tbf_header_lengths'] function, we read the very first 8 bytes to determine:
52+
///
53+
/// - tbf-version
54+
/// - header_size
55+
/// - total_size
56+
///
57+
/// Afterwards, with the 'header_size' we just read,
58+
/// we read the the rest of the header information using the same function
59+
/// then, we save our binary app which marks the total size of the app and move on to the the footer.
60+
///
61+
/// Now, we calculate the total size of the footer.
62+
///
63+
/// Then, we compute the exact offset of the current footer.
64+
///
65+
/// Using it, we read the correct number of bytes from memory,
66+
/// construct a 'TbfFooter' struct from the raw bytes, and store it.
67+
///
68+
/// Once all footers for an app are gathered,
69+
/// we wrap the header, footers, and other metadata into an `AppAttributes` struct,
70+
/// and push it into the apps_details vector, which holds info for all flashed applications
4471
pub(crate) fn read_apps_data_probe(
4572
board_core: &mut Core,
4673
addr: u64,
@@ -63,7 +90,7 @@ impl AppAttributes {
6390
match parse_tbf_header_lengths(
6491
&appdata
6592
.try_into()
66-
.expect("Buffer length must be at least 8 bytes long."),
93+
.expect("Buffer length must be at least 8 bytes long."), // All attributes always add up to 8 bytes.
6794
) {
6895
Ok(data) => {
6996
tbf_version = data.0;
@@ -81,16 +108,19 @@ impl AppAttributes {
81108
let header = parse_tbf_header(&header_data, tbf_version)
82109
.map_err(TockloaderError::ParsingError)?;
83110

84-
let binary_end_offset = header.get_binary_end();
111+
let binary_end_offset = header.get_binary_end(); // the end of the header marks the beginning of the footer
85112

113+
// 1. Calculate the total size of all footers by subtracting the binary's end offset (`binary_end_offset`) from the total application size (`total_size`).
114+
// 2. Initialize the reading offset (`footer_offset`) to the start of the footers (right after the binary ends).
115+
// 3. Loop until we've read all footers (when `footer_offset` reaches `total_size`):
86116
let mut footers: Vec<TbfFooter> = vec![];
87117
let total_footers_size = total_size - binary_end_offset;
88118
let mut footer_offset = binary_end_offset;
89119
let mut footer_number = 0;
90120

91121
loop {
92122
let mut appfooter =
93-
vec![0u8; (total_footers_size - (footer_offset - binary_end_offset)) as usize];
123+
vec![0u8; (total_footers_size - (footer_offset - binary_end_offset)) as usize]; // we take the size of the whole footer initially then decrease it by the size of the previous one as we don't know the size of the footer we are reading
94124

95125
board_core
96126
.read(appaddr + footer_offset as u64, &mut appfooter)
@@ -102,10 +132,10 @@ impl AppAttributes {
102132
footers.insert(footer_number, TbfFooter::new(footer_info.0, footer_info.1));
103133

104134
footer_number += 1;
105-
footer_offset += footer_info.1 + 4;
135+
footer_offset += footer_info.1 + 4; // the next footer begins using this relation: (footer_offset = binary_end_offset) += summation of (previous_footer_size + 4)
106136

107137
if footer_offset == total_size {
108-
break;
138+
break; // all footers have been processed.
109139
}
110140
}
111141

@@ -117,7 +147,18 @@ impl AppAttributes {
117147
}
118148
}
119149

120-
// TODO: Document this function
150+
/// This reads application metadata and footers from a device just like the previous function except it's done via serial.
151+
///
152+
/// Starting at the given address, this function:
153+
///
154+
/// 1. Reads the initial 8 bytes of the app header to get version and size info.
155+
/// 2. Reads the full app header based on that size.
156+
/// 3. Reads and parses all app footers following the app binary.
157+
/// 4. Collects and returns all app attributes found sequentially in memory.
158+
///
159+
/// Communicates using the Tockloader protocol’s `ReadRange` command and handles multiple apps until no more are found.
160+
///
161+
/// Returns a vector of app attributes or an error on failure.
121162
pub(crate) async fn read_apps_data_serial(
122163
port: &mut SerialStream,
123164
addr: u64,
@@ -127,13 +168,24 @@ impl AppAttributes {
127168
let mut apps_details: Vec<AppAttributes> = vec![];
128169

129170
loop {
130-
let mut pkt = (appaddr as u32).to_le_bytes().to_vec();
131-
let length = (8_u16).to_le_bytes().to_vec();
171+
let mut pkt = (appaddr as u32).to_le_bytes().to_vec(); // the tockloader protocol only supports up to 32 bytes, hence we converted acquiring 4 byte little endians.
172+
let length = (8_u16).to_le_bytes().to_vec(); // 2 byte little endians
132173
for i in length {
133174
pkt.push(i);
134175
}
135176

136-
let (_, appdata) =
177+
// This sends a `ReadRange` command to the microcontroller to read 8 bytes of memory starting at `appaddr`,
178+
// and receives the result over the serial connection.
179+
// The microcontroller receives the command, checks the Command ID (`0x06` for `ReadRange`),
180+
// then reads the 1-byte payload length and the actual payload:
181+
// 4 bytes for the address (little-endian), and 2 bytes for the length to read (also little-endian).
182+
// The microcontroller processes incoming bytes using a state machine like this:
183+
// enum SerialState {
184+
// WaitingForCommand, // Awaiting the 1-byte command ID
185+
// WaitingForLength, // Awaiting the 1-byte payload length
186+
// WaitingForPayload { command: u8, length: usize, buffer: Vec<u8> }, // Receiving payload
187+
// }
188+
let (_, appdata) = // the first element in the tuple be returned is raw data so we ignore it and take the second which is the app data we need
137189
issue_command(port, Command::ReadRange, pkt, true, 8, Response::ReadRange).await?;
138190

139191
let tbf_version: u16;
@@ -158,7 +210,13 @@ impl AppAttributes {
158210
for i in length {
159211
pkt.push(i);
160212
}
161-
213+
// This also sends a ReadRange command to fetch the full TBF header from the same app address (appaddr).
214+
//
215+
// The first read (8 bytes) gave us only the header lengths and total size,
216+
// but now that we know the full header size (header_size), we send a new request to retrieve the entire header.
217+
//
218+
// The returned data (header_data) is a raw byte vector containing all fields of the TBF header,
219+
// including version, flags, entry point, protected regions, package name, etc.
162220
let (_, header_data) = issue_command(
163221
port,
164222
Command::ReadRange,
@@ -187,6 +245,11 @@ impl AppAttributes {
187245
pkt.push(i);
188246
}
189247

248+
// Issue a ReadRange command over serial to fetch the next footer block.
249+
//
250+
// The response contains raw bytes starting from footer_offset, with the desired length.
251+
//
252+
// We ignore the first tuple element and keep only the returned footer bytes.
190253
let (_, appfooter) = issue_command(
191254
port,
192255
Command::ReadRange,

tockloader-lib/src/attributes/decode.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33
// Copyright OXIDOS AUTOMOTIVE 2024.
44

5+
/// Attributes are key-value pairs that describe hardware configuration, stored in a fixed 64-byte format:
6+
///
7+
/// 1. 8-byte null-padded UTF-8 key (e.g., "board__\0\0")
8+
/// 2. 1-byte value length (1-55)
9+
/// 3. Variable-length UTF-8 value (e.g., "nrf52840")
10+
/// 4. Null padding to fill 64 bytes
11+
///
12+
/// These attributes are obtained by:
13+
///
14+
/// 1. Reading physical memory from 0x600-0x9FF (1024 bytes = 16 attribute slots)
15+
/// 2. Decoding each 64-byte chunk into key-value pairs
16+
/// 3. Storing valid pairs in this struct
17+
///
18+
/// Tock attributes examples:
19+
///
20+
/// 1. board: Hardware platform name
21+
/// 2. arch: CPU architecture
22+
/// 3. appaddr: Application memory start address
23+
/// 4. boothash: Bootloader integrity checksum
24+
///
25+
/// This structure used to hold the data of the attributes region at the 0x600-0x9FF range
526
#[derive(Debug)]
627
pub struct DecodedAttribute {
728
pub key: String,
@@ -16,8 +37,25 @@ impl DecodedAttribute {
1637
}
1738
}
1839
}
19-
20-
// TODO: explain what is happening here
40+
/// Function used to decode 64 byte chunks from the attributes region at the 0x600-0x9FF range
41+
///
42+
/// Each attribute follows this layout:
43+
///
44+
/// 1. Bytes 0–7: UTF-8 key string
45+
/// 2. Byte 8: Value length (1–55)
46+
/// 3. Bytes 9–63: UTF-8 value string
47+
///
48+
/// The function returns:
49+
///
50+
/// - `Some(DecodedAttribute)` containing the parsed key and value if valid
51+
/// - `None` if:
52+
/// - The value length is zero or exceeds 55 bytes (corrupt or uninitialized)
53+
/// - The value contains invalid UTF-8 data
54+
///
55+
/// Panics
56+
///
57+
/// This function panics if the input step is less than 64 bytes.
58+
/// The caller must ensure chunks are exactly 64 bytes.
2159
pub(crate) fn decode_attribute(step: &[u8]) -> Option<DecodedAttribute> {
2260
let raw_key = &step[0..8];
2361

@@ -47,7 +85,11 @@ pub(crate) fn decode_attribute(step: &[u8]) -> Option<DecodedAttribute> {
4785
Some(DecodedAttribute::new(key, value))
4886
}
4987

50-
// TODO: explain what is happening here
88+
/// Specifically used in the read_system_attributes fn from the system_attributes.rs
89+
///
90+
/// to decode the bytes of the sentinel kernel attribute.
91+
///
92+
/// It's used to decode utf-8 encoded bytes and return them as Strings
5193
pub(crate) fn bytes_to_string(raw: &[u8]) -> String {
5294
let decoder = utf8_decode::Decoder::new(raw.iter().cloned());
5395

0 commit comments

Comments
 (0)