Skip to content

Commit ddf7462

Browse files
committed
chore: document general and app attributes
1 parent 067a1f8 commit ddf7462

3 files changed

Lines changed: 192 additions & 91 deletions

File tree

tockloader-lib/src/attributes/app_attributes.rs

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +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>
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>
1919
#[derive(Debug)]
2020
pub struct AppAttributes {
2121
pub tbf_header: TbfHeader,
2222
pub tbf_footers: Vec<TbfFooter>,
2323
}
2424

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
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
2727
#[derive(Debug)]
2828
pub struct TbfFooter {
2929
pub credentials: TbfFooterV2Credentials,
@@ -46,24 +46,25 @@ impl AppAttributes {
4646
}
4747
}
4848
/// The function below is used to retrieve header and footer data
49+
///
4950
/// Starting from the 0x40000 address,
50-
/// we read the very first 8 bytes to determine:
51-
/// Reads from all the applications flashed on the board.
51+
/// using the 'parse_tbf_header_lengths' function, we read the very first 8 bytes to determine:
52+
///
5253
/// - tbf-version
5354
/// - header_size
5455
/// - total_size
55-
/// using the parse_tbf_header_lengths function,
56-
/// Afterward, with the header_size we just read,
56+
///
57+
/// Afterwards, with the 'header_size' we just read,
5758
/// we read the the rest of the header information using the same function
58-
/// then, we save the end of our binary app which marks the total size of the app and the start of the footer.
59-
/// Now, we calculate the total size of the footer step by step:
60-
/// 1. Using the known end of the binary (from the header's total_size),
61-
/// 2. Subtracting the size of any previously read footers,
62-
/// 3. Finally, accounting for the extra 4 bytes (2 bytes for type, 2 for length) for each footer entry.
63-
/// We compute the exact offset where the current footer begins using the below relation:
64-
/// footer_offset = binary_offset + previous_footer_size + 4
65-
/// Using this offset, we read the correct number of bytes from memory,
66-
/// construct a TbfFooter struct from the raw bytes, and store it.
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+
///
6768
/// Once all footers for an app are gathered,
6869
/// we wrap the header, footers, and other metadata into an `AppAttributes` struct,
6970
/// and push it into the apps_details vector, which holds info for all flashed applications
@@ -89,7 +90,7 @@ impl AppAttributes {
8990
match parse_tbf_header_lengths(
9091
&appdata
9192
.try_into()
92-
.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.
9394
) {
9495
Ok(data) => {
9596
tbf_version = data.0;
@@ -107,16 +108,20 @@ impl AppAttributes {
107108
let header = parse_tbf_header(&header_data, tbf_version)
108109
.map_err(TockloaderError::ParsingError)?;
109110

110-
let binary_end_offset = header.get_binary_end();
111-
111+
let binary_end_offset = header.get_binary_end(); // the end of the header marks the beginning of the footer
112+
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`):
116+
let mut footers: Vec<TbfFooter> = vec![];
112117
let mut footers: Vec<TbfFooter> = vec![];
113118
let total_footers_size = total_size - binary_end_offset;
114119
let mut footer_offset = binary_end_offset;
115120
let mut footer_number = 0;
116121

117122
loop {
118123
let mut appfooter =
119-
vec![0u8; (total_footers_size - (footer_offset - binary_end_offset)) as usize];
124+
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
120125

121126
board_core
122127
.read(appaddr + footer_offset as u64, &mut appfooter)
@@ -128,10 +133,10 @@ impl AppAttributes {
128133
footers.insert(footer_number, TbfFooter::new(footer_info.0, footer_info.1));
129134

130135
footer_number += 1;
131-
footer_offset += footer_info.1 + 4;
136+
footer_offset += footer_info.1 + 4; // the next footer begins using this relation: (footer_offset = binary_end_offset) += summation of (previous_footer_size + 4)
132137

133138
if footer_offset == total_size {
134-
break;
139+
break; // all footers have been processed.
135140
}
136141
}
137142

@@ -143,7 +148,18 @@ impl AppAttributes {
143148
}
144149
}
145150

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

155171
loop {
156-
let mut pkt = (appaddr as u32).to_le_bytes().to_vec();
157-
let length = (8_u16).to_le_bytes().to_vec();
172+
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.
173+
let length = (8_u16).to_le_bytes().to_vec(); // 2 byte little endians
158174
for i in length {
159-
pkt.push(i);
175+
pkt.push(i);
160176
}
161-
162-
let (_, appdata) =
163-
issue_command(port, Command::ReadRange, pkt, true, 8, Response::ReadRange).await?;
177+
178+
/// This sends a `ReadRange` command to the microcontroller to read 8 bytes of memory starting at `appaddr`,
179+
/// and receives the result over the serial connection.
180+
///
181+
/// The microcontroller receives the command, checks the Command ID (`0x06` for `ReadRange`),
182+
/// then reads the 1-byte payload length and the actual payload:
183+
/// 4 bytes for the address (little-endian), and 2 bytes for the length to read (also little-endian).
184+
///
185+
/// The microcontroller processes incoming bytes using a state machine like this:
186+
///
187+
/// enum SerialState {
188+
/// WaitingForCommand, // Awaiting the 1-byte command ID
189+
/// WaitingForLength, // Awaiting the 1-byte payload length
190+
/// WaitingForPayload { command: u8, length: usize, buffer: Vec<u8> }, // Receiving payload
191+
/// }
192+
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
193+
issue_command(port, Command::ReadRange, pkt, true, 8, Response::ReadRange).await?;
164194

165195
let tbf_version: u16;
166196
let header_size: u16;
@@ -178,13 +208,19 @@ impl AppAttributes {
178208
}
179209
_ => break,
180210
};
181-
211+
182212
let mut pkt = (appaddr as u32).to_le_bytes().to_vec();
183213
let length = (header_size).to_le_bytes().to_vec();
184214
for i in length {
185215
pkt.push(i);
186216
}
187-
217+
/// This also sends a ReadRange command to fetch the full TBF header from the same app address (appaddr).
218+
///
219+
/// The first read (8 bytes) gave us only the header lengths and total size,
220+
/// but now that we know the full header size (header_size), we send a new request to retrieve the entire header.
221+
///
222+
/// The returned data (header_data) is a raw byte vector containing all fields of the TBF header,
223+
/// including version, flags, entry point, protected regions, package name, etc.
188224
let (_, header_data) = issue_command(
189225
port,
190226
Command::ReadRange,
@@ -213,6 +249,11 @@ impl AppAttributes {
213249
pkt.push(i);
214250
}
215251

252+
/// Issue a ReadRange command over serial to fetch the next footer block.
253+
///
254+
/// The response contains raw bytes starting from footer_offset, with the desired length.
255+
///
256+
/// We ignore the first tuple element and keep only the returned footer bytes.
216257
let (_, appfooter) = issue_command(
217258
port,
218259
Command::ReadRange,

tockloader-lib/src/attributes/decode.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
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:
5+
/// Attributes are key-value pairs that describe hardware configuration, stored in a fixed 64-byte format:
6+
///
67
/// 1. 8-byte null-padded UTF-8 key (e.g., "board__\0\0")
78
/// 2. 1-byte value length (1-55)
89
/// 3. Variable-length UTF-8 value (e.g., "nrf52840")
910
/// 4. Null padding to fill 64 bytes
1011
///
1112
/// These attributes are obtained by:
13+
///
1214
/// 1. Reading physical memory from 0x600-0x9FF (1024 bytes = 16 attribute slots)
1315
/// 2. Decoding each 64-byte chunk into key-value pairs
1416
/// 3. Storing valid pairs in this struct
1517
///
1618
/// Tock attributes examples:
19+
///
1720
/// 1. board: Hardware platform name
1821
/// 2. arch: CPU architecture
1922
/// 3. appaddr: Application memory start address
@@ -35,18 +38,22 @@ impl DecodedAttribute {
3538
}
3639
}
3740
/// Function used to decode 64 byte chunks from the attributes region at the 0x600-0x9FF range
41+
///
3842
/// Each attribute follows this layout:
43+
///
3944
/// 1. Bytes 0–7: UTF-8 key string
4045
/// 2. Byte 8: Value length (1–55)
4146
/// 3. Bytes 9–63: UTF-8 value string
4247
///
4348
/// The function returns:
49+
///
4450
/// - `Some(DecodedAttribute)` containing the parsed key and value if valid
4551
/// - `None` if:
4652
/// - The value length is zero or exceeds 55 bytes (corrupt or uninitialized)
4753
/// - The value contains invalid UTF-8 data
4854
///
4955
/// Panics
56+
///
5057
/// This function panics if the input step is less than 64 bytes.
5158
/// The caller must ensure chunks are exactly 64 bytes.
5259
pub(crate) fn decode_attribute(step: &[u8]) -> Option<DecodedAttribute> {
@@ -79,8 +86,10 @@ pub(crate) fn decode_attribute(step: &[u8]) -> Option<DecodedAttribute> {
7986
}
8087

8188
/// Specifically used in the read_system_attributes fn from the system_attributes.rs
89+
///
8290
/// to decode the bytes of the sentinel kernel attribute.
83-
/// Function used to decode utf-8 encoded bytes and return them as Strings
91+
///
92+
/// It's used to decode utf-8 encoded bytes and return them as Strings
8493
pub(crate) fn bytes_to_string(raw: &[u8]) -> String {
8594
let decoder = utf8_decode::Decoder::new(raw.iter().cloned());
8695

0 commit comments

Comments
 (0)