From 64478aa99b2fd339c86f710ac709c856e6942ee6 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Fri, 21 Jul 2023 15:24:36 +0300 Subject: [PATCH 01/12] Created workspace and split tbf-parser into its own thing --- Cargo.toml | 13 +++++-------- tbf-parser/Cargo.toml | 9 +++++++++ tbf-parser/src/lib.rs | 14 ++++++++++++++ tockloader/Cargo.toml | 10 ++++++++++ {src => tockloader/src}/cli.rs | 0 {src => tockloader/src}/main.rs | 0 6 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 tbf-parser/Cargo.toml create mode 100644 tbf-parser/src/lib.rs create mode 100644 tockloader/Cargo.toml rename {src => tockloader/src}/cli.rs (100%) rename {src => tockloader/src}/main.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 59905e5..884b2de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,6 @@ -[package] -name = "tockloader" -version = "0.1.0" -edition = "2021" +[workspace] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -clap = { version = "4.1.1", features = ["cargo"] } +members = [ + "tockloader", + "tbf-parser" +] \ No newline at end of file diff --git a/tbf-parser/Cargo.toml b/tbf-parser/Cargo.toml new file mode 100644 index 0000000..858f51b --- /dev/null +++ b/tbf-parser/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tbf-parser" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tock-tbf = { git = "https://github.com/tock/tock" } \ No newline at end of file diff --git a/tbf-parser/src/lib.rs b/tbf-parser/src/lib.rs new file mode 100644 index 0000000..7d12d9a --- /dev/null +++ b/tbf-parser/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/tockloader/Cargo.toml b/tockloader/Cargo.toml new file mode 100644 index 0000000..7050d3f --- /dev/null +++ b/tockloader/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tockloader" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.1.1", features = ["cargo"] } +tbf-parser = { path = "../tbf-parser"} \ No newline at end of file diff --git a/src/cli.rs b/tockloader/src/cli.rs similarity index 100% rename from src/cli.rs rename to tockloader/src/cli.rs diff --git a/src/main.rs b/tockloader/src/main.rs similarity index 100% rename from src/main.rs rename to tockloader/src/main.rs From baf3f1d621f8ab9f65d4c1f1c33c82cec822bef1 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Thu, 27 Jul 2023 16:09:40 +0300 Subject: [PATCH 02/12] switched tockloader to be a root repository --- Cargo.toml | 14 ++++++++++++-- {tockloader/src => src}/cli.rs | 0 {tockloader/src => src}/main.rs | 2 ++ tbf-parser/Cargo.toml | 5 ++++- tbf-parser/src/lib.rs | 14 ++++++++++++++ tockloader/Cargo.toml | 10 ---------- 6 files changed, 32 insertions(+), 13 deletions(-) rename {tockloader/src => src}/cli.rs (100%) rename {tockloader/src => src}/main.rs (94%) delete mode 100644 tockloader/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index 884b2de..f5587d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,16 @@ [workspace] members = [ - "tockloader", "tbf-parser" -] \ No newline at end of file +] + +[package] +name = "tockloader" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.1.1", features = ["cargo"] } +tbf-parser = { path = "./tbf-parser"} diff --git a/tockloader/src/cli.rs b/src/cli.rs similarity index 100% rename from tockloader/src/cli.rs rename to src/cli.rs diff --git a/tockloader/src/main.rs b/src/main.rs similarity index 94% rename from tockloader/src/main.rs rename to src/main.rs index 7c80977..457242c 100644 --- a/tockloader/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ use cli::make_cli; fn main() { let matches = make_cli().get_matches(); + println!("{}", tbf_parser::add(1, 2)); + if matches.get_flag("debug") { println!("Debug mode enabled"); } diff --git a/tbf-parser/Cargo.toml b/tbf-parser/Cargo.toml index 858f51b..303f71e 100644 --- a/tbf-parser/Cargo.toml +++ b/tbf-parser/Cargo.toml @@ -6,4 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tock-tbf = { git = "https://github.com/tock/tock" } \ No newline at end of file + +[features] +default = [] +std = [] \ No newline at end of file diff --git a/tbf-parser/src/lib.rs b/tbf-parser/src/lib.rs index 7d12d9a..ecee79d 100644 --- a/tbf-parser/src/lib.rs +++ b/tbf-parser/src/lib.rs @@ -1,3 +1,17 @@ +#![cfg_attr(not(feature = "std"), no_std)] + + +#[cfg(feature = "std")] +pub fn add(left: usize, right: usize) -> usize { + let a = [left, right].to_vec(); + let mut sum = 1; + for item in a { + sum += item + } + return sum +} + +#[cfg(not(feature = "std"))] pub fn add(left: usize, right: usize) -> usize { left + right } diff --git a/tockloader/Cargo.toml b/tockloader/Cargo.toml deleted file mode 100644 index 7050d3f..0000000 --- a/tockloader/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "tockloader" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -clap = { version = "4.1.1", features = ["cargo"] } -tbf-parser = { path = "../tbf-parser"} \ No newline at end of file From eaae0a7bb7763af0622903cef99e1d6c73f1b4c5 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Thu, 27 Jul 2023 16:15:41 +0300 Subject: [PATCH 03/12] Copied orignal tock-tbf code into new workspace --- src/main.rs | 2 - tbf-parser/src/lib.rs | 38 +- tbf-parser/src/parse.rs | 337 ++++++++++++++ tbf-parser/src/types.rs | 958 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1308 insertions(+), 27 deletions(-) create mode 100644 tbf-parser/src/parse.rs create mode 100644 tbf-parser/src/types.rs diff --git a/src/main.rs b/src/main.rs index 457242c..7c80977 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,6 @@ use cli::make_cli; fn main() { let matches = make_cli().get_matches(); - println!("{}", tbf_parser::add(1, 2)); - if matches.get_flag("debug") { println!("Debug mode enabled"); } diff --git a/tbf-parser/src/lib.rs b/tbf-parser/src/lib.rs index ecee79d..bc54262 100644 --- a/tbf-parser/src/lib.rs +++ b/tbf-parser/src/lib.rs @@ -1,28 +1,16 @@ -#![cfg_attr(not(feature = "std"), no_std)] +// Adapted from tock-tbf (https://github.com/tock/tock) +// === ORIGINAL LICENSE === +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// ======================== +//! Tock Binary Format (TBF) header parsing library. -#[cfg(feature = "std")] -pub fn add(left: usize, right: usize) -> usize { - let a = [left, right].to_vec(); - let mut sum = 1; - for item in a { - sum += item - } - return sum -} +// Parsing the headers does not require any unsafe operations. +#![forbid(unsafe_code)] +#![no_std] -#[cfg(not(feature = "std"))] -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod parse; +#[allow(dead_code)] // Some fields not read on device, but read when creating headers +pub mod types; \ No newline at end of file diff --git a/tbf-parser/src/parse.rs b/tbf-parser/src/parse.rs new file mode 100644 index 0000000..e39dd9a --- /dev/null +++ b/tbf-parser/src/parse.rs @@ -0,0 +1,337 @@ +// Adapted from tock-tbf (https://github.com/tock/tock) +// === ORIGINAL LICENSE === +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// ======================== + +//! Tock Binary Format parsing code. + +use core::convert::TryInto; +use core::iter::Iterator; +use core::{mem, str}; + +use crate::types; + +/// Takes a value and rounds it up to be aligned % 4 +macro_rules! align4 { + ($e:expr $(,)?) => { + ($e) + ((4 - (($e) % 4)) % 4) + }; +} + +/// Parse the TBF header length and the entire length of the TBF binary. +/// +/// ## Return +/// +/// If all parsing is successful: +/// - Ok((Version, TBF header length, entire TBF length)) +/// +/// If we cannot parse the header because we have run out of flash, or the +/// values are entirely wrong we return `UnableToParse`. This means we have hit +/// the end of apps in flash. +/// - Err(InitialTbfParseError::UnableToParse) +/// +/// Any other error we return an error and the length of the entire app so that +/// we can skip over it and check for the next app. +/// - Err(InitialTbfParseError::InvalidHeader(app_length)) +pub fn parse_tbf_header_lengths( + app: &'static [u8; 8], +) -> Result<(u16, u16, u32), types::InitialTbfParseError> { + // Version is the first 16 bits of the app TBF contents. We need this to + // correctly parse the other lengths. + // + // ## Safety + // We trust that the version number has been checked prior to running this + // parsing code. That is, whatever loaded this application has verified that + // the version is valid and therefore we can trust it. + let version = u16::from_le_bytes([app[0], app[1]]); + + match version { + 2 => { + // In version 2, the next 16 bits after the version represent + // the size of the TBF header in bytes. + let tbf_header_size = u16::from_le_bytes([app[2], app[3]]); + + // The next 4 bytes are the size of the entire app's TBF space + // including the header. This also must be checked before parsing + // this header and we trust the value in flash. + let tbf_size = u32::from_le_bytes([app[4], app[5], app[6], app[7]]); + + // Check that the header length isn't greater than the entire app, + // and is at least as large as the v2 required header (which is 16 + // bytes). If that at least looks good then return the sizes. + if u32::from(tbf_header_size) > tbf_size || tbf_header_size < 16 { + Err(types::InitialTbfParseError::InvalidHeader(tbf_size)) + } else { + Ok((version, tbf_header_size, tbf_size)) + } + } + + // Since we have to trust the total size, and by extension the version + // number, if we don't know how to handle the version this must not be + // an actual app. Likely this is just the end of the app linked list. + _ => Err(types::InitialTbfParseError::UnableToParse), + } +} + +/// Parse a TBF header stored in flash. +/// +/// The `header` must be a slice that only contains the TBF header. The caller +/// should use the `parse_tbf_header_lengths()` function to determine this +/// length to create the correct sized slice. +pub fn parse_tbf_header( + header: &'static [u8], + version: u16, +) -> Result { + match version { + 2 => { + // Get the required base. This will succeed because we parsed the + // first bit of the header already in `parse_tbf_header_lengths()`. + let tbf_header_base: types::TbfHeaderV2Base = header.try_into()?; + + // Calculate checksum. The checksum is the XOR of each 4 byte word + // in the header. + let mut checksum: u32 = 0; + + // Get an iterator across 4 byte fields in the header. + let header_iter = header.chunks_exact(4); + + // Iterate all chunks and XOR the chunks to compute the checksum. + for (i, chunk) in header_iter.enumerate() { + let word = u32::from_le_bytes(chunk.try_into()?); + if i == 3 { + // Skip the checksum field. + } else { + checksum ^= word; + } + } + + // Verify the header matches. + if checksum != tbf_header_base.checksum { + return Err(types::TbfParseError::ChecksumMismatch( + tbf_header_base.checksum, + checksum, + )); + } + + // Get the rest of the header. The `remaining` variable will + // continue to hold the remainder of the header we have not + // processed. + let mut remaining = header + .get(16..) + .ok_or(types::TbfParseError::NotEnoughFlash)?; + + // If there is nothing left in the header then this is just a + // padding "app" between two other apps. + if remaining.len() == 0 { + // Just padding. + Ok(types::TbfHeader::Padding(tbf_header_base)) + } else { + // This is an actual app. + + // Places to save fields that we parse out of the header + // options. + let mut main_pointer: Option = None; + let mut program_pointer: Option = None; + let mut wfr_pointer: [Option; 4] = + Default::default(); + let mut app_name_str = ""; + let mut fixed_address_pointer: Option = None; + let mut permissions_pointer: Option> = None; + let mut storage_permissions_pointer: Option< + types::TbfHeaderV2StoragePermissions<8>, + > = None; + let mut kernel_version: Option = None; + + // Iterate the remainder of the header looking for TLV entries. + while remaining.len() > 0 { + // Get the T and L portions of the next header (if it is + // there). + let tlv_header: types::TbfTlv = remaining + .get(0..4) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?; + remaining = remaining + .get(4..) + .ok_or(types::TbfParseError::NotEnoughFlash)?; + + match tlv_header.tipe { + types::TbfHeaderTypes::TbfHeaderMain => { + let entry_len = mem::size_of::(); + // If there is already a header do nothing: if this is a second Main + // keep the first one, if it's a Program we ignore the Main + if main_pointer.is_none() { + if tlv_header.length as usize == entry_len { + main_pointer = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(types::TbfParseError::BadTlvEntry( + tlv_header.tipe as usize, + )); + } + } + } + types::TbfHeaderTypes::TbfHeaderProgram => { + let entry_len = mem::size_of::(); + if program_pointer.is_none() { + if tlv_header.length as usize == entry_len { + program_pointer = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(types::TbfParseError::BadTlvEntry( + tlv_header.tipe as usize, + )); + } + } + } + types::TbfHeaderTypes::TbfHeaderWriteableFlashRegions => { + // Length must be a multiple of the size of a region definition. + if tlv_header.length as usize + % mem::size_of::() + == 0 + { + // Calculate how many writeable flash regions + // there are specified in this header. + let wfr_len = + mem::size_of::(); + let mut number_regions = tlv_header.length as usize / wfr_len; + + // Capture a slice with just the wfr information. + let wfr_slice = remaining + .get(0..tlv_header.length as usize) + .ok_or(types::TbfParseError::NotEnoughFlash)?; + + // To enable a static buffer, we only support up + // to four writeable flash regions. + if number_regions > 4 { + number_regions = 4; + } + + // Convert and store each wfr. + for i in 0..number_regions { + wfr_pointer[i] = Some( + wfr_slice + .get(i * wfr_len..(i + 1) * wfr_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } + } else { + return Err(types::TbfParseError::BadTlvEntry( + tlv_header.tipe as usize, + )); + } + } + + types::TbfHeaderTypes::TbfHeaderPackageName => { + let name_buf = remaining + .get(0..tlv_header.length as usize) + .ok_or(types::TbfParseError::NotEnoughFlash)?; + + str::from_utf8(name_buf) + .map(|name_str| { + app_name_str = name_str; + }) + .or(Err(types::TbfParseError::BadProcessName))?; + } + + types::TbfHeaderTypes::TbfHeaderFixedAddresses => { + let entry_len = mem::size_of::(); + if tlv_header.length as usize == entry_len { + fixed_address_pointer = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(types::TbfParseError::BadTlvEntry( + tlv_header.tipe as usize, + )); + } + } + + types::TbfHeaderTypes::TbfHeaderPermissions => { + permissions_pointer = Some(remaining.try_into()?); + } + + types::TbfHeaderTypes::TbfHeaderStoragePermissions => { + storage_permissions_pointer = Some(remaining.try_into()?); + } + + types::TbfHeaderTypes::TbfHeaderKernelVersion => { + let entry_len = mem::size_of::(); + if tlv_header.length as usize == entry_len { + kernel_version = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(types::TbfParseError::BadTlvEntry( + tlv_header.tipe as usize, + )); + } + } + + _ => {} + } + + // All TLV blocks are padded to 4 bytes, so we need to skip + // more if the length is not a multiple of 4. + let skip_len: usize = align4!(tlv_header.length as usize); + remaining = remaining + .get(skip_len..) + .ok_or(types::TbfParseError::NotEnoughFlash)?; + } + + let tbf_header = types::TbfHeaderV2 { + base: tbf_header_base, + main: main_pointer, + program: program_pointer, + package_name: Some(app_name_str), + writeable_regions: Some(wfr_pointer), + fixed_addresses: fixed_address_pointer, + permissions: permissions_pointer, + storage_permissions: storage_permissions_pointer, + kernel_version: kernel_version, + }; + + Ok(types::TbfHeader::TbfHeaderV2(tbf_header)) + } + } + _ => Err(types::TbfParseError::UnsupportedVersion(version)), + } +} + +pub fn parse_tbf_footer( + footers: &'static [u8], +) -> Result<(types::TbfFooterV2Credentials, u32), types::TbfParseError> { + let mut remaining = footers; + let tlv_header: types::TbfTlv = remaining.try_into()?; + remaining = remaining + .get(4..) + .ok_or(types::TbfParseError::NotEnoughFlash)?; + match tlv_header.tipe { + types::TbfHeaderTypes::TbfFooterCredentials => { + let credential: types::TbfFooterV2Credentials = remaining + .get(0..tlv_header.length as usize) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?; + // Check length here + let length = tlv_header.length; + Ok((credential, length as u32)) + } + _ => Err(types::TbfParseError::BadTlvEntry(tlv_header.tipe as usize)), + } +} \ No newline at end of file diff --git a/tbf-parser/src/types.rs b/tbf-parser/src/types.rs new file mode 100644 index 0000000..2143938 --- /dev/null +++ b/tbf-parser/src/types.rs @@ -0,0 +1,958 @@ +// Adapted from tock-tbf (https://github.com/tock/tock) +// === ORIGINAL LICENSE === +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// ======================== + + +//! Types and Data Structures for TBFs. + +use core::convert::TryInto; +use core::fmt; +use core::mem::size_of; + +/// We only support up to a fixed number of storage permissions for each of read +/// and modify. This simplification enables us to use fixed sized buffers. +const NUM_STORAGE_PERMISSIONS: usize = 8; + +/// Error when parsing just the beginning of the TBF header. This is only used +/// when establishing the linked list structure of apps installed in flash. +pub enum InitialTbfParseError { + /// We were unable to parse the beginning of the header. This either means + /// we ran out of flash, or the trusted values are invalid meaning this is + /// just empty flash after the end of the last app. This error is fine, as + /// it just means we must have hit the end of the linked list of apps. + UnableToParse, + + /// Some length or value in the header is invalid. The header parsing has + /// failed at this point. However, the total app length value is a trusted + /// field, so we return that value with this error so that we can skip over + /// this invalid app and continue to check for additional apps. + InvalidHeader(u32), +} + +impl From for InitialTbfParseError { + // Convert a slice to a parsed type. Since we control how long we make our + // slices, this conversion should never fail. If it does, then this is a bug + // in this library that must be fixed. + fn from(_error: core::array::TryFromSliceError) -> Self { + InitialTbfParseError::UnableToParse + } +} + +/// Error when parsing an app's TBF header. +pub enum TbfParseError { + /// Not enough bytes in the buffer to parse the expected field. + NotEnoughFlash, + + /// Unknown version of the TBF header. + UnsupportedVersion(u16), + + /// Checksum calculation did not match what is stored in the TBF header. + /// First value is the checksum provided, second value is the checksum we + /// calculated. + ChecksumMismatch(u32, u32), + + /// One of the TLV entries did not parse correctly. This could happen if the + /// TLV.length does not match the size of a fixed-length entry. The `usize` + /// is the value of the "tipe" field. + BadTlvEntry(usize), + + /// The app name in the TBF header could not be successfully parsed as a + /// UTF-8 string. + BadProcessName, + + /// Internal kernel error. This is a bug inside of this library. Likely this + /// means that for some reason a slice was not sized properly for parsing a + /// certain type, which is something completely controlled by this library. + /// If the slice passed in is not long enough, then a `get()` call will + /// fail and that will trigger a different error. + InternalError, + + /// The number of variable length entries (for example the number of + /// `TbfHeaderDriverPermission` entries in `TbfHeaderV2Permissions`) is + /// too long for Tock to parse. + /// This can be fixed by increasing the number in `TbfHeaderV2`. + TooManyEntries(usize), +} + +impl From for TbfParseError { + // Convert a slice to a parsed type. Since we control how long we make our + // slices, this conversion should never fail. If it does, then this is a bug + // in this library that must be fixed. + fn from(_error: core::array::TryFromSliceError) -> Self { + TbfParseError::InternalError + } +} + +impl fmt::Debug for TbfParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TbfParseError::NotEnoughFlash => write!(f, "Buffer too short to parse TBF header"), + TbfParseError::UnsupportedVersion(version) => { + write!(f, "TBF version {} unsupported", version) + } + TbfParseError::ChecksumMismatch(app, calc) => write!( + f, + "Checksum verification failed: app:{:#x}, calc:{:#x}", + app, calc + ), + TbfParseError::BadTlvEntry(tipe) => write!(f, "TLV entry type {} is invalid", tipe), + TbfParseError::BadProcessName => write!(f, "Process name not UTF-8"), + TbfParseError::InternalError => write!(f, "Internal kernel error. This is a bug."), + TbfParseError::TooManyEntries(tipe) => { + write!( + f, + "There are too many variable entries of {} for Tock to parse", + tipe + ) + } + } + } +} + +// TBF structure + +/// TBF fields that must be present in all v2 headers. +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2Base { + pub(crate) version: u16, + pub(crate) header_size: u16, + pub(crate) total_size: u32, + pub(crate) flags: u32, + pub(crate) checksum: u32, +} + +/// Types in TLV structures for each optional block of the header. +#[derive(Clone, Copy, Debug)] +pub enum TbfHeaderTypes { + TbfHeaderMain = 1, + TbfHeaderWriteableFlashRegions = 2, + TbfHeaderPackageName = 3, + TbfHeaderFixedAddresses = 5, + TbfHeaderPermissions = 6, + TbfHeaderStoragePermissions = 7, + TbfHeaderKernelVersion = 8, + TbfHeaderProgram = 9, + TbfFooterCredentials = 128, + + /// Some field in the header that we do not understand. Since the TLV format + /// specifies the length of each section, if we get a field we do not + /// understand we just skip it, rather than throwing an error. + Unknown, +} + +/// The TLV header (T and L). +#[derive(Clone, Copy, Debug)] +pub struct TbfTlv { + pub(crate) tipe: TbfHeaderTypes, + pub(crate) length: u16, +} + +/// The v2 Main Header for apps. +/// +/// All apps must have either a Main Header or a Program Header. Without +/// either, the TBF object is considered padding. Main and Program Headers +/// differ in whether they specify the endpoint of the process binary; Main +/// Headers do not, while Program Headers do. A TBF with a Main Header cannot +/// have any Credentials Footers, while a TBF with a Program Header can. +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2Main { + init_fn_offset: u32, + protected_trailer_size: u32, + minimum_ram_size: u32, +} + +/// The v2 Program Header for apps. +/// +/// All apps must have either a Main Header or a Program Header. Without +/// either, the TBF object is considered padding. Main and Program Headers +/// differ in whether they specify the endpoint of the process binary; Main +/// Headers do not, while Program Headers do. A Program Header includes +/// the binary end offset so that a Verifier knows where Credentials Headers +/// start. The region between the end of the binary and the end of the TBF +/// is reserved for Credentials Footers. +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2Program { + init_fn_offset: u32, + protected_trailer_size: u32, + minimum_ram_size: u32, + binary_end_offset: u32, + version: u32, +} + +/// Writeable flash regions only need an offset and size. +/// +/// There can be multiple (or zero) flash regions defined, so this is its own +/// struct. +#[derive(Clone, Copy, Debug, Default)] +pub struct TbfHeaderV2WriteableFlashRegion { + writeable_flash_region_offset: u32, + writeable_flash_region_size: u32, +} + +/// Optional fixed addresses for flash and RAM for this process. +/// +/// If a process is compiled for a specific address this header entry lets the +/// kernel know what those addresses are. +/// +/// If this header is omitted the kernel will assume that the process is +/// position-independent and can be loaded at any (reasonably aligned) flash +/// address and can be given any (reasonable aligned) memory segment. +/// +/// If this header is included, the kernel will check these values when setting +/// up the process. If a process wants to set one fixed address but not the other, the unused one +/// can be set to 0xFFFFFFFF. +#[derive(Clone, Copy, Debug, Default)] +pub struct TbfHeaderV2FixedAddresses { + /// The absolute address of the start of RAM that the process expects. For + /// example, if the process was linked with a RAM region starting at + /// address `0x00023000`, then this would be set to `0x00023000`. + start_process_ram: u32, + /// The absolute address of the start of the process binary. This does _not_ + /// include the TBF header. This is the address the process used for the + /// start of flash with the linker. + start_process_flash: u32, +} + +#[derive(Clone, Copy, Debug, Default)] +struct TbfHeaderDriverPermission { + driver_number: u32, + offset: u32, + allowed_commands: u64, +} + +/// A list of permissions for this app +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2Permissions { + length: u16, + perms: [TbfHeaderDriverPermission; L], +} + +/// A list of storage (read/write/modify) permissions for this app. +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2StoragePermissions { + write_id: Option, + read_length: u16, + read_ids: [u32; L], + modify_length: u16, + modify_ids: [u32; L], +} + +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2KernelVersion { + major: u16, + minor: u16, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum TbfFooterV2CredentialsType { + Reserved = 0, + Rsa3072Key = 1, + Rsa4096Key = 2, + SHA256 = 3, + SHA384 = 4, + SHA512 = 5, +} + +#[derive(Clone, Copy, Debug)] +pub struct TbfFooterV2Credentials { + format: TbfFooterV2CredentialsType, + data: &'static [u8], +} + +impl TbfFooterV2Credentials { + pub fn format(&self) -> TbfFooterV2CredentialsType { + self.format + } + + pub fn data(&self) -> &'static [u8] { + self.data + } +} + +// Conversion functions from slices to the various TBF fields. + +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Base { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result { + if b.len() < 16 { + return Err(TbfParseError::InternalError); + } + Ok(TbfHeaderV2Base { + version: u16::from_le_bytes( + b.get(0..2) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + header_size: u16::from_le_bytes( + b.get(2..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + total_size: u32::from_le_bytes( + b.get(4..8) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + flags: u32::from_le_bytes( + b.get(8..12) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + checksum: u32::from_le_bytes( + b.get(12..16) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + +impl core::convert::TryFrom for TbfHeaderTypes { + type Error = TbfParseError; + + fn try_from(h: u16) -> Result { + match h { + 1 => Ok(TbfHeaderTypes::TbfHeaderMain), + 2 => Ok(TbfHeaderTypes::TbfHeaderWriteableFlashRegions), + 3 => Ok(TbfHeaderTypes::TbfHeaderPackageName), + 5 => Ok(TbfHeaderTypes::TbfHeaderFixedAddresses), + 6 => Ok(TbfHeaderTypes::TbfHeaderPermissions), + 7 => Ok(TbfHeaderTypes::TbfHeaderStoragePermissions), + 8 => Ok(TbfHeaderTypes::TbfHeaderKernelVersion), + 9 => Ok(TbfHeaderTypes::TbfHeaderProgram), + 128 => Ok(TbfHeaderTypes::TbfFooterCredentials), + _ => Ok(TbfHeaderTypes::Unknown), + } + } +} + +impl core::convert::TryFrom<&[u8]> for TbfTlv { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result { + Ok(TbfTlv { + tipe: u16::from_le_bytes( + b.get(0..2) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ) + .try_into()?, + length: u16::from_le_bytes( + b.get(2..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Main { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result { + // For 3 or more fields, this shortcut check reduces code size + if b.len() < 12 { + return Err(TbfParseError::InternalError); + } + Ok(TbfHeaderV2Main { + init_fn_offset: u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + protected_trailer_size: u32::from_le_bytes( + b.get(4..8) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + minimum_ram_size: u32::from_le_bytes( + b.get(8..12) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Program { + type Error = TbfParseError; + fn try_from(b: &[u8]) -> Result { + // For 3 or more fields, this shortcut check reduces code size + if b.len() < 20 { + return Err(TbfParseError::InternalError); + } + Ok(TbfHeaderV2Program { + init_fn_offset: u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + protected_trailer_size: u32::from_le_bytes( + b.get(4..8) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + minimum_ram_size: u32::from_le_bytes( + b.get(8..12) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + binary_end_offset: u32::from_le_bytes( + b.get(12..16) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + version: u32::from_le_bytes( + b.get(16..20) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2WriteableFlashRegion { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result { + Ok(TbfHeaderV2WriteableFlashRegion { + writeable_flash_region_offset: u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + writeable_flash_region_size: u32::from_le_bytes( + b.get(4..8) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2FixedAddresses { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result { + Ok(TbfHeaderV2FixedAddresses { + start_process_ram: u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + start_process_flash: u32::from_le_bytes( + b.get(4..8) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + +impl core::convert::TryFrom<&[u8]> for TbfHeaderDriverPermission { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result { + // For 3 or more fields, this shortcut check reduces code size + if b.len() < 16 { + return Err(TbfParseError::InternalError); + } + Ok(TbfHeaderDriverPermission { + driver_number: u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + offset: u32::from_le_bytes( + b.get(4..8) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + allowed_commands: u64::from_le_bytes( + b.get(8..16) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Permissions { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result, Self::Error> { + let number_perms = u16::from_le_bytes( + b.get(0..2) + .ok_or(TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + + let mut perms: [TbfHeaderDriverPermission; L] = [TbfHeaderDriverPermission { + driver_number: 0, + offset: 0, + allowed_commands: 0, + }; L]; + for i in 0..number_perms as usize { + let start = 2 + (i * size_of::()); + let end = start + size_of::(); + if let Some(perm) = perms.get_mut(i) { + *perm = b + .get(start..end as usize) + .ok_or(TbfParseError::NotEnoughFlash)? + .try_into()?; + } else { + return Err(TbfParseError::BadTlvEntry( + TbfHeaderTypes::TbfHeaderPermissions as usize, + )); + } + } + + Ok(TbfHeaderV2Permissions { + length: number_perms, + perms, + }) + } +} + +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2StoragePermissions { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result, Self::Error> { + let mut read_end = 6; + + let write_id = core::num::NonZeroU32::new(u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::NotEnoughFlash)? + .try_into()?, + )); + + let read_length = u16::from_le_bytes( + b.get(4..6) + .ok_or(TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + + let mut read_ids: [u32; L] = [0; L]; + for i in 0..read_length as usize { + let start = 6 + (i * size_of::()); + read_end = start + size_of::(); + if let Some(read_id) = read_ids.get_mut(i) { + *read_id = u32::from_le_bytes( + b.get(start..read_end as usize) + .ok_or(TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(TbfParseError::BadTlvEntry( + TbfHeaderTypes::TbfHeaderStoragePermissions as usize, + )); + } + } + + let modify_length = u16::from_le_bytes( + b.get(read_end..(read_end + 2)) + .ok_or(TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + + let mut modify_ids: [u32; L] = [0; L]; + for i in 0..modify_length as usize { + let start = read_end + 2 + (i * size_of::()); + let modify_end = start + size_of::(); + if let Some(modify_id) = modify_ids.get_mut(i) { + *modify_id = u32::from_le_bytes( + b.get(start..modify_end as usize) + .ok_or(TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(TbfParseError::BadTlvEntry( + TbfHeaderTypes::TbfHeaderStoragePermissions as usize, + )); + } + } + + Ok(TbfHeaderV2StoragePermissions { + write_id, + read_length, + read_ids, + modify_length, + modify_ids, + }) + } +} + +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2KernelVersion { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result { + Ok(TbfHeaderV2KernelVersion { + major: u16::from_le_bytes( + b.get(0..2) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + minor: u16::from_le_bytes( + b.get(2..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + +impl core::convert::TryFrom<&'static [u8]> for TbfFooterV2Credentials { + type Error = TbfParseError; + + fn try_from(b: &'static [u8]) -> Result { + let format = u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ); + let ftype = match format { + 0 => TbfFooterV2CredentialsType::Reserved, + 1 => TbfFooterV2CredentialsType::Rsa3072Key, + 2 => TbfFooterV2CredentialsType::Rsa4096Key, + 3 => TbfFooterV2CredentialsType::SHA256, + 4 => TbfFooterV2CredentialsType::SHA384, + 5 => TbfFooterV2CredentialsType::SHA512, + _ => { + return Err(TbfParseError::InternalError); + } + }; + let length = match ftype { + TbfFooterV2CredentialsType::Reserved => 0, + TbfFooterV2CredentialsType::Rsa3072Key => 768, + TbfFooterV2CredentialsType::Rsa4096Key => 1024, + TbfFooterV2CredentialsType::SHA256 => 32, + TbfFooterV2CredentialsType::SHA384 => 48, + TbfFooterV2CredentialsType::SHA512 => 64, + }; + let data = &b + .get(4..(length + 4)) + .ok_or(TbfParseError::NotEnoughFlash)?; + Ok(TbfFooterV2Credentials { + format: ftype, + data: data, + }) + } +} + +/// The command permissions specified by the TBF header. +/// +/// Use the `get_command_permissions()` function to retrieve these. +pub enum CommandPermissions { + /// The TBF header did not specify any permissions for any driver numbers. + NoPermsAtAll, + /// The TBF header did specify permissions for at least one driver number, + /// but not for the requested driver number. + NoPermsThisDriver, + /// The bitmask of allowed command numbers starting from the offset provided + /// when this enum was created. + Mask(u64), +} + +/// Single header that can contain all parts of a v2 header. +/// +/// Note, this struct limits the number of writeable regions an app can have to +/// four since we need to statically know the length of the array to store in +/// this type. +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2 { + pub(crate) base: TbfHeaderV2Base, + pub(crate) main: Option, + pub(crate) program: Option, + pub(crate) package_name: Option<&'static str>, + pub(crate) writeable_regions: Option<[Option; 4]>, + pub(crate) fixed_addresses: Option, + pub(crate) permissions: Option>, + pub(crate) storage_permissions: Option>, + pub(crate) kernel_version: Option, +} + +/// Type that represents the fields of the Tock Binary Format header. +/// +/// This specifies the locations of the different code and memory sections +/// in the tock binary, as well as other information about the application. +/// The kernel can also use this header to keep persistent state about +/// the application. +#[derive(Debug)] +pub enum TbfHeader { + TbfHeaderV2(TbfHeaderV2), + Padding(TbfHeaderV2Base), +} + +impl TbfHeader { + /// Return the length of the header. + pub fn length(&self) -> u16 { + match *self { + TbfHeader::TbfHeaderV2(hd) => hd.base.header_size, + TbfHeader::Padding(base) => base.header_size, + } + } + + /// Return whether this is an app or just padding between apps. + pub fn is_app(&self) -> bool { + match *self { + TbfHeader::TbfHeaderV2(_) => true, + TbfHeader::Padding(_) => false, + } + } + + /// Return whether the application is enabled or not. + /// Disabled applications are not started by the kernel. + pub fn enabled(&self) -> bool { + match *self { + TbfHeader::TbfHeaderV2(hd) => { + // Bit 1 of flags is the enable/disable bit. + hd.base.flags & 0x00000001 == 1 + } + TbfHeader::Padding(_) => false, + } + } + + /// Add up all of the relevant fields in header version 1, or just used the + /// app provided value in version 2 to get the total amount of RAM that is + /// needed for this app. + pub fn get_minimum_app_ram_size(&self) -> u32 { + match *self { + TbfHeader::TbfHeaderV2(hd) => { + if hd.program.is_some() { + hd.program.map_or(0, |p| p.minimum_ram_size) + } else if hd.main.is_some() { + hd.main.map_or(0, |m| m.minimum_ram_size) + } else { + 0 + } + } + _ => 0, + } + } + + /// Get the number of bytes from the start of the app's region in flash that + /// is for kernel use only. The app cannot write this region. + pub fn get_protected_size(&self) -> u32 { + match *self { + TbfHeader::TbfHeaderV2(hd) => { + if hd.program.is_some() { + hd.program.map_or(0, |p| { + (hd.base.header_size as u32) + p.protected_trailer_size + }) + } else if hd.main.is_some() { + hd.main.map_or(0, |m| { + (hd.base.header_size as u32) + m.protected_trailer_size + }) + } else { + 0 + } + } + _ => 0, + } + } + + /// Get the start offset of the application binary from the beginning + /// of the process binary (start of the TBF header). Only valid if this + /// is an app. + pub fn get_app_start_offset(&self) -> u32 { + // The application binary starts after the header plus any + // additional protected space. + self.get_protected_size() + } + + /// Get the offset from the beginning of the app's flash region where the + /// app should start executing. + pub fn get_init_function_offset(&self) -> u32 { + match *self { + TbfHeader::TbfHeaderV2(hd) => { + if hd.program.is_some() { + hd.program + .map_or(0, |p| p.init_fn_offset + (hd.base.header_size as u32)) + } else if hd.main.is_some() { + hd.main + .map_or(0, |m| m.init_fn_offset + (hd.base.header_size as u32)) + } else { + 0 + } + } + _ => 0, + } + } + + /// Get the name of the app. + pub fn get_package_name(&self) -> Option<&'static str> { + match *self { + TbfHeader::TbfHeaderV2(hd) => hd.package_name, + _ => None, + } + } + + /// Get the number of flash regions this app has specified in its header. + pub fn number_writeable_flash_regions(&self) -> usize { + match *self { + TbfHeader::TbfHeaderV2(hd) => hd.writeable_regions.map_or(0, |wrs| { + wrs.iter() + .fold(0, |acc, wr| if wr.is_some() { acc + 1 } else { acc }) + }), + _ => 0, + } + } + + /// Get the offset and size of a given flash region. + pub fn get_writeable_flash_region(&self, index: usize) -> (u32, u32) { + match *self { + TbfHeader::TbfHeaderV2(hd) => hd.writeable_regions.map_or((0, 0), |wrs| { + wrs.get(index).unwrap_or(&None).map_or((0, 0), |wr| { + ( + wr.writeable_flash_region_offset, + wr.writeable_flash_region_size, + ) + }) + }), + _ => (0, 0), + } + } + + /// Get the address in RAM this process was specifically compiled for. If + /// the process is position independent, return `None`. + pub fn get_fixed_address_ram(&self) -> Option { + let hd = match self { + TbfHeader::TbfHeaderV2(hd) => hd, + _ => return None, + }; + match hd.fixed_addresses.as_ref()?.start_process_ram { + 0xFFFFFFFF => None, + start => Some(start), + } + } + + /// Get the address in flash this process was specifically compiled for. If + /// the process is position independent, return `None`. + pub fn get_fixed_address_flash(&self) -> Option { + let hd = match self { + TbfHeader::TbfHeaderV2(hd) => hd, + _ => return None, + }; + match hd.fixed_addresses.as_ref()?.start_process_flash { + 0xFFFFFFFF => None, + start => Some(start), + } + } + + /// Get the permissions for a specified driver and offset. + /// + /// - `driver_num`: The driver to lookup. + /// - `offset`: The offset for the driver to find. An offset value of 1 will + /// find a header with offset 1, so the `allowed_commands` will cover + /// command numbers 64 to 127. + /// + /// If permissions are found for the driver number, this function will + /// return `CommandPermissions::Mask`. If there are permissions in the + /// header but not for this driver the function will return + /// `CommandPermissions::NoPermsThisDriver`. If the process does not have + /// any permissions specified, return `CommandPermissions::NoPermsAtAll`. + pub fn get_command_permissions(&self, driver_num: usize, offset: usize) -> CommandPermissions { + match self { + TbfHeader::TbfHeaderV2(hd) => match hd.permissions { + Some(permissions) => { + let mut found_driver_num: bool = false; + for perm in permissions.perms { + if perm.driver_number == driver_num as u32 { + found_driver_num = true; + if perm.offset == offset as u32 { + return CommandPermissions::Mask(perm.allowed_commands); + } + } + } + if found_driver_num { + // We found this driver number but nothing matched the + // requested offset. Since permissions are default off, + // we can return a mask of all zeros. + CommandPermissions::Mask(0) + } else { + CommandPermissions::NoPermsThisDriver + } + } + _ => CommandPermissions::NoPermsAtAll, + }, + _ => CommandPermissions::NoPermsAtAll, + } + } + + /// Get the process `write_id`. + /// + /// Returns `None` if a `write_id` is not included. This indicates the TBF + /// does not have the ability to store new items. + pub fn get_storage_write_id(&self) -> Option { + match self { + TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { + Some(permissions) => permissions.write_id, + _ => None, + }, + _ => None, + } + } + + /// Get the number of valid `read_ids` and the `read_ids`. + /// Returns `None` if a `read_ids` is not included. + pub fn get_storage_read_ids(&self) -> Option<(usize, [u32; NUM_STORAGE_PERMISSIONS])> { + match self { + TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { + Some(permissions) => Some((permissions.read_length.into(), permissions.read_ids)), + _ => None, + }, + _ => None, + } + } + + /// Get the number of valid `access_ids` and the `access_ids`. + /// Returns `None` if a `access_ids` is not included. + pub fn get_storage_modify_ids(&self) -> Option<(usize, [u32; NUM_STORAGE_PERMISSIONS])> { + match self { + TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { + Some(permissions) => { + Some((permissions.modify_length.into(), permissions.modify_ids)) + } + _ => None, + }, + _ => None, + } + } + + /// Get the minimum compatible kernel version this process requires. + /// Returns `None` if the kernel compatibility header is not included. + pub fn get_kernel_version(&self) -> Option<(u16, u16)> { + match self { + TbfHeader::TbfHeaderV2(hd) => match hd.kernel_version { + Some(kernel_version) => Some((kernel_version.major, kernel_version.minor)), + _ => None, + }, + _ => None, + } + } + + /// Return the offset where the binary ends in the TBF or 0 if there + /// is no binary. If there is a Main header the end offset is the size + /// of the TBF, while if there is a Program header it can be smaller. + pub fn get_binary_end(&self) -> u32 { + match self { + TbfHeader::TbfHeaderV2(hd) => hd + .program + .map_or(hd.base.total_size as u32, |p| p.binary_end_offset), + _ => 0, + } + } + + /// Return the version number of the Userspace Binary in this TBF + /// Object, or 0 if there is no binary or no version number. + pub fn get_binary_version(&self) -> u32 { + match self { + TbfHeader::TbfHeaderV2(hd) => hd.program.map_or(0, |p| p.version), + _ => 0, + } + } +} \ No newline at end of file From cc7bbfd54d264ef48824b714d53617a91ed5b47b Mon Sep 17 00:00:00 2001 From: Cosma George Date: Thu, 27 Jul 2023 16:26:45 +0300 Subject: [PATCH 04/12] Ran cargo fmt --- output.log | 144 ++++++++++++++++++++++++++++++++++++++++ tbf-parser/Cargo.toml | 2 +- tbf-parser/src/lib.rs | 2 +- tbf-parser/src/parse.rs | 2 +- tbf-parser/src/types.rs | 3 +- 5 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 output.log diff --git a/output.log b/output.log new file mode 100644 index 0000000..9b1918a --- /dev/null +++ b/output.log @@ -0,0 +1,144 @@ +Checking formating of source files... +Running clippy on source files... + Checking tbf-parser v0.1.0 (/mnt/d/Programming/tockloader-rs/tbf-parser) + Checking clap_builder v4.3.17 +error: redundant field names in struct initialization + --> tbf-parser/src/parse.rs:307:21 + | +307 | kernel_version: kernel_version, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `kernel_version` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names + = note: `-D clippy::redundant-field-names` implied by `-D warnings` + +error: redundant field names in struct initialization + --> tbf-parser/src/types.rs:640:13 + | +640 | data: data, + | ^^^^^^^^^^ help: replace it with: `data` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names + +error: length comparison to zero + --> tbf-parser/src/parse.rs:127:16 + | +127 | if remaining.len() == 0 { + | ^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `remaining.is_empty()` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero + = note: `-D clippy::len-zero` implied by `-D warnings` + +error: length comparison to zero + --> tbf-parser/src/parse.rs:148:23 + | +148 | while remaining.len() > 0 { + | ^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!remaining.is_empty()` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero + +error: the loop variable `i` is used to index `wfr_pointer` + --> tbf-parser/src/parse.rs:220:42 + | +220 | ... for i in 0..number_regions { + | ^^^^^^^^^^^^^^^^^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop + = note: `-D clippy::needless-range-loop` implied by `-D warnings` +help: consider using an iterator + | +220 | for (i, ) in wfr_pointer.iter_mut().enumerate().take(number_regions) { + | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: casting to the same type is unnecessary (`usize` -> `usize`) + --> tbf-parser/src/types.rs:503:33 + | +503 | .get(start..end as usize) + | ^^^^^^^^^^^^ help: try: `end` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast + = note: `-D clippy::unnecessary-cast` implied by `-D warnings` + +error: casting to the same type is unnecessary (`usize` -> `usize`) + --> tbf-parser/src/types.rs:544:34 + | +544 | b.get(start..read_end as usize) + | ^^^^^^^^^^^^^^^^^ help: try: `read_end` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast + +error: casting to the same type is unnecessary (`usize` -> `usize`) + --> tbf-parser/src/types.rs:567:34 + | +567 | b.get(start..modify_end as usize) + | ^^^^^^^^^^^^^^^^^^^ help: try: `modify_end` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast + +error: large size difference between variants + --> tbf-parser/src/types.rs:684:1 + | +684 | / pub enum TbfHeader { +685 | | TbfHeaderV2(TbfHeaderV2), + | | ------------------------ the largest variant contains at least 360 bytes +686 | | Padding(TbfHeaderV2Base), + | | ------------------------ the second-largest variant contains at least 16 bytes +687 | | } + | |_^ the entire enum is at least 360 bytes + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant + = note: `-D clippy::large-enum-variant` implied by `-D warnings` +help: consider boxing the large fields to reduce the total size of the enum + | +685 | TbfHeaderV2(Box), + | ~~~~~~~~~~~~~~~~ + +error: manual implementation of `Option::map` + --> tbf-parser/src/types.rs:903:43 + | +903 | TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { + | ___________________________________________^ +904 | | Some(permissions) => Some((permissions.read_length.into(), permissions.read_ids)), +905 | | _ => None, +906 | | }, + | |_____________^ help: try this: `hd.storage_permissions.map(|permissions| (permissions.read_length.into(), permissions.read_ids))` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_map + = note: `-D clippy::manual-map` implied by `-D warnings` + +error: manual implementation of `Option::map` + --> tbf-parser/src/types.rs:915:43 + | +915 | TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { + | ___________________________________________^ +916 | | Some(permissions) => { +917 | | Some((permissions.modify_length.into(), permissions.modify_ids)) +918 | | } +919 | | _ => None, +920 | | }, + | |_____________^ help: try this: `hd.storage_permissions.map(|permissions| (permissions.modify_length.into(), permissions.modify_ids))` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_map + +error: manual implementation of `Option::map` + --> tbf-parser/src/types.rs:929:43 + | +929 | TbfHeader::TbfHeaderV2(hd) => match hd.kernel_version { + | ___________________________________________^ +930 | | Some(kernel_version) => Some((kernel_version.major, kernel_version.minor)), +931 | | _ => None, +932 | | }, + | |_____________^ help: try this: `hd.kernel_version.map(|kernel_version| (kernel_version.major, kernel_version.minor))` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_map + +error: casting to the same type is unnecessary (`u32` -> `u32`) + --> tbf-parser/src/types.rs:944:25 + | +944 | .map_or(hd.base.total_size as u32, |p| p.binary_end_offset), + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hd.base.total_size` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast + +error: could not compile `tbf-parser` due to 13 previous errors +warning: build failed, waiting for other jobs to finish... +make: *** [Makefile:14: ci-job-clippy] Error 101 diff --git a/tbf-parser/Cargo.toml b/tbf-parser/Cargo.toml index 303f71e..d535fb2 100644 --- a/tbf-parser/Cargo.toml +++ b/tbf-parser/Cargo.toml @@ -9,4 +9,4 @@ edition = "2021" [features] default = [] -std = [] \ No newline at end of file +std = [] diff --git a/tbf-parser/src/lib.rs b/tbf-parser/src/lib.rs index bc54262..5c01366 100644 --- a/tbf-parser/src/lib.rs +++ b/tbf-parser/src/lib.rs @@ -13,4 +13,4 @@ pub mod parse; #[allow(dead_code)] // Some fields not read on device, but read when creating headers -pub mod types; \ No newline at end of file +pub mod types; diff --git a/tbf-parser/src/parse.rs b/tbf-parser/src/parse.rs index e39dd9a..26c6e85 100644 --- a/tbf-parser/src/parse.rs +++ b/tbf-parser/src/parse.rs @@ -334,4 +334,4 @@ pub fn parse_tbf_footer( } _ => Err(types::TbfParseError::BadTlvEntry(tlv_header.tipe as usize)), } -} \ No newline at end of file +} diff --git a/tbf-parser/src/types.rs b/tbf-parser/src/types.rs index 2143938..1b207a9 100644 --- a/tbf-parser/src/types.rs +++ b/tbf-parser/src/types.rs @@ -5,7 +5,6 @@ // Copyright Tock Contributors 2022. // ======================== - //! Types and Data Structures for TBFs. use core::convert::TryInto; @@ -955,4 +954,4 @@ impl TbfHeader { _ => 0, } } -} \ No newline at end of file +} From b9b69644337a7a6520c0fe93524390687fff6c87 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Sun, 30 Jul 2023 19:02:03 +0300 Subject: [PATCH 05/12] Changed package name from static-lifetime string to same lifetime as struct --- output.log | 144 ---------------------------- tbf-parser/src/parse.rs | 4 +- tbf-parser/src/types.rs | 12 +-- tbf-parser/tests/flashes/simple.dat | Bin 0 -> 52 bytes tbf-parser/tests/parse.rs | 17 ++++ 5 files changed, 25 insertions(+), 152 deletions(-) delete mode 100644 output.log create mode 100644 tbf-parser/tests/flashes/simple.dat create mode 100644 tbf-parser/tests/parse.rs diff --git a/output.log b/output.log deleted file mode 100644 index 9b1918a..0000000 --- a/output.log +++ /dev/null @@ -1,144 +0,0 @@ -Checking formating of source files... -Running clippy on source files... - Checking tbf-parser v0.1.0 (/mnt/d/Programming/tockloader-rs/tbf-parser) - Checking clap_builder v4.3.17 -error: redundant field names in struct initialization - --> tbf-parser/src/parse.rs:307:21 - | -307 | kernel_version: kernel_version, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `kernel_version` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names - = note: `-D clippy::redundant-field-names` implied by `-D warnings` - -error: redundant field names in struct initialization - --> tbf-parser/src/types.rs:640:13 - | -640 | data: data, - | ^^^^^^^^^^ help: replace it with: `data` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names - -error: length comparison to zero - --> tbf-parser/src/parse.rs:127:16 - | -127 | if remaining.len() == 0 { - | ^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `remaining.is_empty()` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero - = note: `-D clippy::len-zero` implied by `-D warnings` - -error: length comparison to zero - --> tbf-parser/src/parse.rs:148:23 - | -148 | while remaining.len() > 0 { - | ^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!remaining.is_empty()` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero - -error: the loop variable `i` is used to index `wfr_pointer` - --> tbf-parser/src/parse.rs:220:42 - | -220 | ... for i in 0..number_regions { - | ^^^^^^^^^^^^^^^^^ - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop - = note: `-D clippy::needless-range-loop` implied by `-D warnings` -help: consider using an iterator - | -220 | for (i, ) in wfr_pointer.iter_mut().enumerate().take(number_regions) { - | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -error: casting to the same type is unnecessary (`usize` -> `usize`) - --> tbf-parser/src/types.rs:503:33 - | -503 | .get(start..end as usize) - | ^^^^^^^^^^^^ help: try: `end` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast - = note: `-D clippy::unnecessary-cast` implied by `-D warnings` - -error: casting to the same type is unnecessary (`usize` -> `usize`) - --> tbf-parser/src/types.rs:544:34 - | -544 | b.get(start..read_end as usize) - | ^^^^^^^^^^^^^^^^^ help: try: `read_end` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast - -error: casting to the same type is unnecessary (`usize` -> `usize`) - --> tbf-parser/src/types.rs:567:34 - | -567 | b.get(start..modify_end as usize) - | ^^^^^^^^^^^^^^^^^^^ help: try: `modify_end` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast - -error: large size difference between variants - --> tbf-parser/src/types.rs:684:1 - | -684 | / pub enum TbfHeader { -685 | | TbfHeaderV2(TbfHeaderV2), - | | ------------------------ the largest variant contains at least 360 bytes -686 | | Padding(TbfHeaderV2Base), - | | ------------------------ the second-largest variant contains at least 16 bytes -687 | | } - | |_^ the entire enum is at least 360 bytes - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant - = note: `-D clippy::large-enum-variant` implied by `-D warnings` -help: consider boxing the large fields to reduce the total size of the enum - | -685 | TbfHeaderV2(Box), - | ~~~~~~~~~~~~~~~~ - -error: manual implementation of `Option::map` - --> tbf-parser/src/types.rs:903:43 - | -903 | TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { - | ___________________________________________^ -904 | | Some(permissions) => Some((permissions.read_length.into(), permissions.read_ids)), -905 | | _ => None, -906 | | }, - | |_____________^ help: try this: `hd.storage_permissions.map(|permissions| (permissions.read_length.into(), permissions.read_ids))` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_map - = note: `-D clippy::manual-map` implied by `-D warnings` - -error: manual implementation of `Option::map` - --> tbf-parser/src/types.rs:915:43 - | -915 | TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { - | ___________________________________________^ -916 | | Some(permissions) => { -917 | | Some((permissions.modify_length.into(), permissions.modify_ids)) -918 | | } -919 | | _ => None, -920 | | }, - | |_____________^ help: try this: `hd.storage_permissions.map(|permissions| (permissions.modify_length.into(), permissions.modify_ids))` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_map - -error: manual implementation of `Option::map` - --> tbf-parser/src/types.rs:929:43 - | -929 | TbfHeader::TbfHeaderV2(hd) => match hd.kernel_version { - | ___________________________________________^ -930 | | Some(kernel_version) => Some((kernel_version.major, kernel_version.minor)), -931 | | _ => None, -932 | | }, - | |_____________^ help: try this: `hd.kernel_version.map(|kernel_version| (kernel_version.major, kernel_version.minor))` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_map - -error: casting to the same type is unnecessary (`u32` -> `u32`) - --> tbf-parser/src/types.rs:944:25 - | -944 | .map_or(hd.base.total_size as u32, |p| p.binary_end_offset), - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hd.base.total_size` - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast - -error: could not compile `tbf-parser` due to 13 previous errors -warning: build failed, waiting for other jobs to finish... -make: *** [Makefile:14: ci-job-clippy] Error 101 diff --git a/tbf-parser/src/parse.rs b/tbf-parser/src/parse.rs index 26c6e85..5bb29a9 100644 --- a/tbf-parser/src/parse.rs +++ b/tbf-parser/src/parse.rs @@ -36,7 +36,7 @@ macro_rules! align4 { /// we can skip over it and check for the next app. /// - Err(InitialTbfParseError::InvalidHeader(app_length)) pub fn parse_tbf_header_lengths( - app: &'static [u8; 8], + app: &[u8; 8], ) -> Result<(u16, u16, u32), types::InitialTbfParseError> { // Version is the first 16 bits of the app TBF contents. We need this to // correctly parse the other lengths. @@ -81,7 +81,7 @@ pub fn parse_tbf_header_lengths( /// should use the `parse_tbf_header_lengths()` function to determine this /// length to create the correct sized slice. pub fn parse_tbf_header( - header: &'static [u8], + header: &[u8], version: u16, ) -> Result { match version { diff --git a/tbf-parser/src/types.rs b/tbf-parser/src/types.rs index 1b207a9..4e3d027 100644 --- a/tbf-parser/src/types.rs +++ b/tbf-parser/src/types.rs @@ -662,11 +662,11 @@ pub enum CommandPermissions { /// four since we need to statically know the length of the array to store in /// this type. #[derive(Clone, Copy, Debug)] -pub struct TbfHeaderV2 { +pub struct TbfHeaderV2<'a> { pub(crate) base: TbfHeaderV2Base, pub(crate) main: Option, pub(crate) program: Option, - pub(crate) package_name: Option<&'static str>, + pub(crate) package_name: Option<&'a str>, pub(crate) writeable_regions: Option<[Option; 4]>, pub(crate) fixed_addresses: Option, pub(crate) permissions: Option>, @@ -681,12 +681,12 @@ pub struct TbfHeaderV2 { /// The kernel can also use this header to keep persistent state about /// the application. #[derive(Debug)] -pub enum TbfHeader { - TbfHeaderV2(TbfHeaderV2), +pub enum TbfHeader<'a> { + TbfHeaderV2(TbfHeaderV2<'a>), Padding(TbfHeaderV2Base), } -impl TbfHeader { +impl TbfHeader<'_> { /// Return the length of the header. pub fn length(&self) -> u16 { match *self { @@ -783,7 +783,7 @@ impl TbfHeader { } /// Get the name of the app. - pub fn get_package_name(&self) -> Option<&'static str> { + pub fn get_package_name(&self) -> Option<&str> { match *self { TbfHeader::TbfHeaderV2(hd) => hd.package_name, _ => None, diff --git a/tbf-parser/tests/flashes/simple.dat b/tbf-parser/tests/flashes/simple.dat new file mode 100644 index 0000000000000000000000000000000000000000..8b3e671816e8e864937d69cbdcb4329e5f288313 GIT binary patch literal 52 zcmZQ#FkxU&U|?WmU|{&K7oW(;z{8*kWP`v5AqEC!1~!KHjMT)U5(Wki1{MY;pf~`W Cng#~| literal 0 HcmV?d00001 diff --git a/tbf-parser/tests/parse.rs b/tbf-parser/tests/parse.rs new file mode 100644 index 0000000..fc7cd51 --- /dev/null +++ b/tbf-parser/tests/parse.rs @@ -0,0 +1,17 @@ +use tbf_parser::{parse::*, types::TbfHeader}; + +fn get_test<'a>() -> TbfHeader<'a> { + let mut buffer: Vec = include_bytes!("./flashes/simple.dat").to_vec(); + + let (ver, header_len, whole_len) = parse_tbf_header_lengths(&buffer[0..8].try_into().unwrap()).ok().unwrap(); + dbg!(ver, header_len, whole_len); + + let header = parse_tbf_header(&buffer[0..header_len as usize],2).unwrap(); + return header; +} + +#[test] +fn check_sum(){ + let header = get_test(); + dbg!(header); +} \ No newline at end of file From 1b85ec666ed4a2435ca8dcc272c6889f5e4e4116 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Sun, 30 Jul 2023 19:23:35 +0300 Subject: [PATCH 06/12] Added test for a simple app --- tbf-parser/tests/parse.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tbf-parser/tests/parse.rs b/tbf-parser/tests/parse.rs index fc7cd51..e06eacb 100644 --- a/tbf-parser/tests/parse.rs +++ b/tbf-parser/tests/parse.rs @@ -1,17 +1,20 @@ -use tbf_parser::{parse::*, types::TbfHeader}; +use tbf_parser::parse::*; -fn get_test<'a>() -> TbfHeader<'a> { - let mut buffer: Vec = include_bytes!("./flashes/simple.dat").to_vec(); +#[test] +fn check_sum(){ + let buffer: Vec = include_bytes!("./flashes/simple.dat").to_vec(); let (ver, header_len, whole_len) = parse_tbf_header_lengths(&buffer[0..8].try_into().unwrap()).ok().unwrap(); - dbg!(ver, header_len, whole_len); + assert_eq!(ver, 2); + assert_eq!(header_len, 52); + assert_eq!(whole_len, 8192); let header = parse_tbf_header(&buffer[0..header_len as usize],2).unwrap(); - return header; -} - -#[test] -fn check_sum(){ - let header = get_test(); - dbg!(header); + dbg!(&header); + assert_eq!(header.enabled(), true); + assert_eq!(header.get_minimum_app_ram_size(), 4848); + assert_eq!(header.get_init_function_offset(), 41 + header_len as u32); + assert_eq!(header.get_protected_size(), header_len as u32); + assert_eq!(header.get_package_name().unwrap(), "_heart"); + assert_eq!(header.get_kernel_version().unwrap(), (2,0)); } \ No newline at end of file From d096ba819b3c7a608cb7b6beed76a0b63b4af64f Mon Sep 17 00:00:00 2001 From: Cosma George Date: Sun, 30 Jul 2023 19:34:27 +0300 Subject: [PATCH 07/12] ran cargo fmt --- tbf-parser/tests/parse.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tbf-parser/tests/parse.rs b/tbf-parser/tests/parse.rs index e06eacb..9c5bdcf 100644 --- a/tbf-parser/tests/parse.rs +++ b/tbf-parser/tests/parse.rs @@ -1,20 +1,22 @@ use tbf_parser::parse::*; #[test] -fn check_sum(){ +fn check_sum() { let buffer: Vec = include_bytes!("./flashes/simple.dat").to_vec(); - - let (ver, header_len, whole_len) = parse_tbf_header_lengths(&buffer[0..8].try_into().unwrap()).ok().unwrap(); + + let (ver, header_len, whole_len) = parse_tbf_header_lengths(&buffer[0..8].try_into().unwrap()) + .ok() + .unwrap(); assert_eq!(ver, 2); assert_eq!(header_len, 52); assert_eq!(whole_len, 8192); - - let header = parse_tbf_header(&buffer[0..header_len as usize],2).unwrap(); + + let header = parse_tbf_header(&buffer[0..header_len as usize], 2).unwrap(); dbg!(&header); assert_eq!(header.enabled(), true); assert_eq!(header.get_minimum_app_ram_size(), 4848); assert_eq!(header.get_init_function_offset(), 41 + header_len as u32); assert_eq!(header.get_protected_size(), header_len as u32); assert_eq!(header.get_package_name().unwrap(), "_heart"); - assert_eq!(header.get_kernel_version().unwrap(), (2,0)); -} \ No newline at end of file + assert_eq!(header.get_kernel_version().unwrap(), (2, 0)); +} From e989e8671e50f294e1fe6f4ab68750a3eb2b8d30 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Mon, 7 Aug 2023 19:19:37 +0300 Subject: [PATCH 08/12] Made footer parser work with non-static-lifetime byte array --- Makefile | 1 + tbf-parser/src/parse.rs | 2 +- tbf-parser/src/types.rs | 12 ++++---- tbf-parser/tests/flashes/footerSHA256.dat | Bin 0 -> 8192 bytes tbf-parser/tests/parse.rs | 34 +++++++++++++++++++++- 5 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 tbf-parser/tests/flashes/footerSHA256.dat diff --git a/Makefile b/Makefile index 9cc1259..b8589bf 100644 --- a/Makefile +++ b/Makefile @@ -19,3 +19,4 @@ ci-runner-github: ci-job-format ci-job-clippy @cargo check @echo "Running tests..." @cargo test + @cargo test --workspace diff --git a/tbf-parser/src/parse.rs b/tbf-parser/src/parse.rs index 5bb29a9..47dddd6 100644 --- a/tbf-parser/src/parse.rs +++ b/tbf-parser/src/parse.rs @@ -315,7 +315,7 @@ pub fn parse_tbf_header( } pub fn parse_tbf_footer( - footers: &'static [u8], + footers: &[u8], ) -> Result<(types::TbfFooterV2Credentials, u32), types::TbfParseError> { let mut remaining = footers; let tlv_header: types::TbfTlv = remaining.try_into()?; diff --git a/tbf-parser/src/types.rs b/tbf-parser/src/types.rs index 4e3d027..6348089 100644 --- a/tbf-parser/src/types.rs +++ b/tbf-parser/src/types.rs @@ -256,17 +256,17 @@ pub enum TbfFooterV2CredentialsType { } #[derive(Clone, Copy, Debug)] -pub struct TbfFooterV2Credentials { +pub struct TbfFooterV2Credentials<'a> { format: TbfFooterV2CredentialsType, - data: &'static [u8], + data: &'a [u8], } -impl TbfFooterV2Credentials { +impl TbfFooterV2Credentials<'_> { pub fn format(&self) -> TbfFooterV2CredentialsType { self.format } - pub fn data(&self) -> &'static [u8] { + pub fn data(&self) -> &[u8] { self.data } } @@ -604,10 +604,10 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2KernelVersion { } } -impl core::convert::TryFrom<&'static [u8]> for TbfFooterV2Credentials { +impl<'b, 'a: 'b> core::convert::TryFrom<&'a [u8]> for TbfFooterV2Credentials<'b> { type Error = TbfParseError; - fn try_from(b: &'static [u8]) -> Result { + fn try_from(b: &'a [u8]) -> Result, Self::Error> { let format = u32::from_le_bytes( b.get(0..4) .ok_or(TbfParseError::InternalError)? diff --git a/tbf-parser/tests/flashes/footerSHA256.dat b/tbf-parser/tests/flashes/footerSHA256.dat new file mode 100644 index 0000000000000000000000000000000000000000..0c4a122a8eb7b7fca1f3f1e9758993ba9c3d82cf GIT binary patch literal 8192 zcmeHLeRNbsmcP}nzml|}Ljra>o$$IJ!43`B(BPmjOL)z6AdLembEXx=>IC$40PR6{ zWY3Ok2XrM4o)O}Vkf4j{gC7H)>5cRr_YCdC5$EX9nR$JR-~y}IT+*??}F_81?|;vs5xXk z$c6hO;Bivxq1~Z)(|lx|P5&VT>2g>e+L9vPN`*93C_?kp@V>}!L}-3K;*`jAo8m>n zYYIL9&-F0EL-w=0A(c4vuc2(Fnu5m%#oqIIM_7$G!rzsKBcu~%R>+gXry?*FvPUkG zrx#M+J>WCQJo0KUkh#Y1R?oPgoKVXdP4=~mWY6DFL8oA=-BBR(j0>x0UQpgr8AME0 z_Cw@hDHbCZga!O4|8ms85(YQpb39K_zH@n_&h-};f#(ms81iRqNNdN!^le7^N#Tc6 z1CbdL%qWw&7a+T1gxk$aEmE1YOEe#^KteiV9M#b$fzK?%HFdFB^i>h$2clD=&q*-r zq&zH5bU>#cK!EJ@!n!asK<#3=Z3?DIuXr*B~k-1o9REU8OA}B`;}(L z)<32U2RMKo$i>6CAv?QZ*v0KOcA2^%`(EChvS+|^nQA8woSV#n><#0nk~&pBZG@n7 z6};&Hw;=u5k(9cvKNYKw39**gImfVc+JO!_l8k}-41Pm)(g=8jw$qOC+A^f<6ek|j za)0^)vZwN&tB=9Uku47Jaf7R?D=C?MOif&~$UInGQ=L;gAsg{JIJ65k%2C6d_r{HW zSyqEC>)HHYG`ew|MzGUxz%Yk{3LQaZ9Mugv6|+}n-zM0Ss$5Ds`AFpovlMsDZ!`|r zuOagv66Xhrf5Hm&)h@!l;|Q z8Te-+_U5@#S7f5}q*x`FH~;TJDb;sUr&E;Uu#$BM6(B<=Zn_F018L)?-E7onYuDC%1Sl z3K4`j2vOQYh5LB0XAE8wYS%*fc#^vByPzT`j4$Ko8BX$X^la#%!?d`@7)Rs(m}4r> zfj!OUnNB7co*rlXHtN9j+YwLj+)ABs#M5g~s^by*pSl#k$d9uz^qLCLa!+Yv7N+Hv zXv}Bq>1`?Fs9%{q;MQ`_YTSP4H10C(Hg{RNwVX?{GT(Ch(Y_uy|qR0BbMrS~}&0nJX^b4J?VXjOBlt4~&4>h-2J;Cjx?LF9jXS6+pZL~4Dri92PCBv#6?1$n_B#lC6 z%kqW{G~{It_x&}k{E!&za5|d$@RaKdu^4zG+L<+Mg!2&=>A(%A;5Vl5`G`Z>+QzJ6 zR}*TUja`ZWz)CD2*wbAiDE7=dw;x_b0(q5#Esjp?i8ocpNuZz>?bih5umX957kvdI zD^VbO=g9up0gMd>{oIU=7%J4N|hB#$p&?5m->%dYt^{Ja}tm=8wihJMcVp z>9j;SAe~d4di$*MfpkVC*CjDV8&CAay2Qytr43cGAl>Hj(CT|}h6(f|E?Vwq z8j}e!8*q&ID$}*V^rI`|MjbW^jlO2N=%RuQY(e|Wpv(BOZ4^|h|cS3k#8Ad z`G!oU{aF07I$kd(`-;$lFr+U=%up-gU0|k3-jBOb+@VJc^K@Qphit-`HJ!Z`mf1&- znrUW!2keBh#!iuw-N0s+kJvoPHH3G$5x|CPGU0!Le7J@g~oSGH$g#^9$NCcLL^O*t(s zN1G*>ZX7DzfOmo+;J?VIZE)>~JY7mmBRjz}ojT#iWKLs1oF~(b$&S?)^FXWA=WJb% z_ALt=`kV%Ew}IdVwe6LrXFGx}v*MF#eSu>$n@=?MTdo}#({r|Hl7lV-j-rn#bRFrA zuJ=)BID8o+W|?^B;A!ixRe~Jd$T8?Ttj~B&uEq5x<1UeIAN{4;IEvbH*eVN-s1ssv z?LWz-(gb>>hRNM+0i_xBC*8Q(&`$LRw0qL+ZhNjR)dXJiP$TYUPe-H3@OoG!-HRMG zPSUBW1R8l}BV{p$(D#`)`!MqIMVlM%*_-mV@F(cYCCpNAcffZZcgu^>cZH^^W_^tL zZdNtw?GMm8|NTq0=ud<2hma9iJmP4OZl&$*!Mm#Ax9was7~LARdTz&C(OZ`+;i)a> zBdslFj8RGb|8NV7S3)b3%AdI%_cxAZAr=Ws(RaD9f_KU1^F_a<*dI?z_04rZ2$o|; zaz93ehYH3zE5^U+Kxl~C{ClZ1N6YQF#6vt2Zz}$=cxl8Nqn68S$rwtC_W&*Ti5?C7 znMuvRj3n0%uNBr+hI>%P8YwB_DJvwSJnrR`JjL@6vNid=dVMvXR7UqHN->_aYs6FH z-ixiDXa>(j_bF^Wps|^MRF^<8?y`Nr+dLxriq;&Og853F^s?@$e(dYjmR@&Ix=$ht zi~|9*DPF!#`b@QX_N{@L4y7u*?yg~~<@RViSUn9;PxsMvv_85nx-ZI5t-G|Ybzkeg zwePRxN8^b!_qb|w&qvPdHDjHhzN=`t2Q{0pfPRQRv=U_^z~a0SGdH+qYl`exI=5BY zCqXB4W6bR|4ot-i;({_CzR2wFAU^Bya?k!XBw&;q(1%I)t|m)Rns-h=E6Sl3%7wSq zRKZgzXt10B!AD>2s4-7Gnh*xTe(K)P4T#%*jCUUoW`)0#Y7Etlrtn*iuz2g@2KoWo zJn#a&8gI^Us(AJora`*s)mu^lu>^gpDIj2`K=1}{NENNOEkyQfxjqecTl7=lA&ik$ zh1qVG>4T%3r!!a=e6Ibp+8nYY*85u$=wm!(9s=DwW)sYpu9Ced`RO$*lSDs~IX#*= zfe4IlZ-*uLeEaVZFSduwc1$eWXqYJ5rU=aMPn>lyFW**R`*|c%iG3EOl~z&K`wxYp zJtsx$akoSrTg8tRhqPK+>~N#bPFRi9L{5r@;}z0M$6m2a;iVGCN=F6VD!w;LSwU~1 z$5gyK9Je8+{c4d9cbQKsT}9tS`F?`(aRRfj-@Bd;TIZSdv&=TreZ*G(qR1a(QFmU! z8!kjjCCoWz)GE`$T4VuAV8qjijfV2`Dcf1dF2>VzreYKP5TI!71@08g96DhW@M|<~ z2-51B>Z4^o?zlkDI_fERs#WR{%at<9<{Jt;SUhEZq4tU3Z=JiuvvQ3yx$?An3r24b ztd)MFa>8NPp5R{bwCr}W8CG0zP+?~_(_`}})-0E0_nLtS2v+c+EY?EM#T6s2EYKpl zeW6CAkEd-=Ucc;4$R0*3;09Ya);L<%#WTqcLvv+gvb})3wU0C{pPm9myP6DxCer}3 zTJRV!$K9S!c9@#Iz^LcwHn9LDo089G*u1~+-;8u3N@3FZ#xn`e&4k$sU6&tS!=Bc; zVw|vNn9ZLRw^uET%5>CSsZV&r>pe*1+ur~S+>INknyI3&`p33Q_SS}V);U~ zr?;dwgbYC3JVv@V3oH*Z%aDCBZxcAm=1tB}>2o!ko(#nEvy|Z_*kOYym{oB4+16R` z;S8hV#oX<~`~-|Z#AALbfaB=FPieW^^zn~m5Qqml1t>jG0c@qVLg}uu-L|f)Fw^MI zpF#e`e9wZpter0hd?U)0g!}v<_uI`5} zh3u|E`+F=h8~H!4nd+>A6=BlVA6ZCUc-~ks%9j>+39>&xOdxx6-V0+nFVqjwg&0#x z>Nkqn(f@s+xqyz21=k*ZOCt4fQR+m)L0WZvEA5%Ook`1jP?C*)mg-Wm9aI^E8$c8HY_5LXY~&W ze9r0y0t^oWKD9B-Se?M = include_bytes!("./flashes/simple.dat").to_vec(); let (ver, header_len, whole_len) = parse_tbf_header_lengths(&buffer[0..8].try_into().unwrap()) @@ -20,3 +20,35 @@ fn check_sum() { assert_eq!(header.get_package_name().unwrap(), "_heart"); assert_eq!(header.get_kernel_version().unwrap(), (2, 0)); } + +#[test] +fn footer_sha256() { + let buffer: Vec = include_bytes!("./flashes/footerSHA256.dat").to_vec(); + + let (ver, header_len, whole_len) = parse_tbf_header_lengths(&buffer[0..8].try_into().unwrap()) + .ok() + .unwrap(); + assert_eq!(ver, 2); + assert_eq!(header_len, 76); + assert_eq!(whole_len, 8192); + + let header = parse_tbf_header(&buffer[0..header_len as usize], 2).unwrap(); + dbg!(&header); + assert_eq!(header.enabled(), true); + assert_eq!(header.get_minimum_app_ram_size(), 4848); + assert_eq!(header.get_init_function_offset(), 41 + header_len as u32); + assert_eq!(header.get_protected_size(), header_len as u32); + assert_eq!(header.get_package_name().unwrap(), "_heart"); + assert_eq!(header.get_kernel_version().unwrap(), (2, 0)); + let binary_offset = header.get_binary_end() as usize; + assert_eq!(binary_offset, 5836); + + let (footer, footer_size) = parse_tbf_footer(&buffer[binary_offset..]).unwrap(); + dbg!(footer); + assert_eq!(footer_size, 36); + let correct_sha256 = [ + 214u8, 17, 81, 32, 51, 178, 249, 35, 161, 33, 109, 184, 195, 46, 238, 158, 141, 54, 63, 94, + 60, 245, 50, 228, 239, 107, 231, 127, 220, 158, 77, 160, + ]; + assert_eq!(footer.data(), correct_sha256); +} From 5f5fcf2cab3d22eb88c2ba1385bd9f8b49b1d67e Mon Sep 17 00:00:00 2001 From: Cosma George Date: Mon, 7 Aug 2023 20:58:40 +0300 Subject: [PATCH 09/12] fixed clippy issues, and allowed redundant field names --- tbf-parser/src/parse.rs | 10 ++++++---- tbf-parser/src/types.rs | 34 ++++++++++++++++------------------ tools/run_clippy.sh | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tbf-parser/src/parse.rs b/tbf-parser/src/parse.rs index 47dddd6..47a1a2b 100644 --- a/tbf-parser/src/parse.rs +++ b/tbf-parser/src/parse.rs @@ -124,7 +124,7 @@ pub fn parse_tbf_header( // If there is nothing left in the header then this is just a // padding "app" between two other apps. - if remaining.len() == 0 { + if remaining.is_empty() { // Just padding. Ok(types::TbfHeader::Padding(tbf_header_base)) } else { @@ -145,7 +145,7 @@ pub fn parse_tbf_header( let mut kernel_version: Option = None; // Iterate the remainder of the header looking for TLV entries. - while remaining.len() > 0 { + while !remaining.is_empty() { // Get the T and L portions of the next header (if it is // there). let tlv_header: types::TbfTlv = remaining @@ -217,8 +217,10 @@ pub fn parse_tbf_header( } // Convert and store each wfr. - for i in 0..number_regions { - wfr_pointer[i] = Some( + for (i, region) in + wfr_pointer.iter_mut().enumerate().take(number_regions) + { + *region = Some( wfr_slice .get(i * wfr_len..(i + 1) * wfr_len) .ok_or(types::TbfParseError::NotEnoughFlash)? diff --git a/tbf-parser/src/types.rs b/tbf-parser/src/types.rs index 6348089..6a8f062 100644 --- a/tbf-parser/src/types.rs +++ b/tbf-parser/src/types.rs @@ -500,7 +500,7 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Permissions let end = start + size_of::(); if let Some(perm) = perms.get_mut(i) { *perm = b - .get(start..end as usize) + .get(start..end) .ok_or(TbfParseError::NotEnoughFlash)? .try_into()?; } else { @@ -541,7 +541,7 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2StoragePermiss read_end = start + size_of::(); if let Some(read_id) = read_ids.get_mut(i) { *read_id = u32::from_le_bytes( - b.get(start..read_end as usize) + b.get(start..read_end) .ok_or(TbfParseError::NotEnoughFlash)? .try_into()?, ); @@ -564,7 +564,7 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2StoragePermiss let modify_end = start + size_of::(); if let Some(modify_id) = modify_ids.get_mut(i) { *modify_id = u32::from_le_bytes( - b.get(start..modify_end as usize) + b.get(start..modify_end) .ok_or(TbfParseError::NotEnoughFlash)? .try_into()?, ); @@ -681,6 +681,9 @@ pub struct TbfHeaderV2<'a> { /// The kernel can also use this header to keep persistent state about /// the application. #[derive(Debug)] +// Clippy suggests we box TbfHeaderV2. We can't really do that, since +// we are runnning under no_std, and I don't think it's that big of a issue. +#[allow(clippy::large_enum_variant)] pub enum TbfHeader<'a> { TbfHeaderV2(TbfHeaderV2<'a>), Padding(TbfHeaderV2Base), @@ -900,10 +903,9 @@ impl TbfHeader<'_> { /// Returns `None` if a `read_ids` is not included. pub fn get_storage_read_ids(&self) -> Option<(usize, [u32; NUM_STORAGE_PERMISSIONS])> { match self { - TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { - Some(permissions) => Some((permissions.read_length.into(), permissions.read_ids)), - _ => None, - }, + TbfHeader::TbfHeaderV2(hd) => hd + .storage_permissions + .map(|permissions| (permissions.read_length.into(), permissions.read_ids)), _ => None, } } @@ -912,12 +914,9 @@ impl TbfHeader<'_> { /// Returns `None` if a `access_ids` is not included. pub fn get_storage_modify_ids(&self) -> Option<(usize, [u32; NUM_STORAGE_PERMISSIONS])> { match self { - TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { - Some(permissions) => { - Some((permissions.modify_length.into(), permissions.modify_ids)) - } - _ => None, - }, + TbfHeader::TbfHeaderV2(hd) => hd + .storage_permissions + .map(|permissions| (permissions.modify_length.into(), permissions.modify_ids)), _ => None, } } @@ -926,10 +925,9 @@ impl TbfHeader<'_> { /// Returns `None` if the kernel compatibility header is not included. pub fn get_kernel_version(&self) -> Option<(u16, u16)> { match self { - TbfHeader::TbfHeaderV2(hd) => match hd.kernel_version { - Some(kernel_version) => Some((kernel_version.major, kernel_version.minor)), - _ => None, - }, + TbfHeader::TbfHeaderV2(hd) => hd + .kernel_version + .map(|kernel_version| (kernel_version.major, kernel_version.minor)), _ => None, } } @@ -941,7 +939,7 @@ impl TbfHeader<'_> { match self { TbfHeader::TbfHeaderV2(hd) => hd .program - .map_or(hd.base.total_size as u32, |p| p.binary_end_offset), + .map_or(hd.base.total_size, |p| p.binary_end_offset), _ => 0, } } diff --git a/tools/run_clippy.sh b/tools/run_clippy.sh index ca138b2..c818e1c 100755 --- a/tools/run_clippy.sh +++ b/tools/run_clippy.sh @@ -11,6 +11,6 @@ if ! rustup component list | grep 'clippy.*(installed)' -q; then fi # TODO: What arguments do we want to pass to clippy? -CLIPPY_ARGS="-D warnings" +CLIPPY_ARGS="-D warnings -A clippy::redundant_field_names" cargo clippy -- $CLIPPY_ARGS From 34a1c3d97a879dc8eb7830e0ab252807575d4594 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Tue, 8 Aug 2023 18:50:02 +0300 Subject: [PATCH 10/12] Package Name now owned by tbf header --- tbf-parser/src/parse.rs | 12 ++++------ tbf-parser/src/types.rs | 53 ++++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/tbf-parser/src/parse.rs b/tbf-parser/src/parse.rs index 47a1a2b..2f82209 100644 --- a/tbf-parser/src/parse.rs +++ b/tbf-parser/src/parse.rs @@ -9,7 +9,7 @@ use core::convert::TryInto; use core::iter::Iterator; -use core::{mem, str}; +use core::mem; use crate::types; @@ -136,7 +136,7 @@ pub fn parse_tbf_header( let mut program_pointer: Option = None; let mut wfr_pointer: [Option; 4] = Default::default(); - let mut app_name_str = ""; + let mut package_name_pointer: Option> = None; let mut fixed_address_pointer: Option = None; let mut permissions_pointer: Option> = None; let mut storage_permissions_pointer: Option< @@ -239,11 +239,7 @@ pub fn parse_tbf_header( .get(0..tlv_header.length as usize) .ok_or(types::TbfParseError::NotEnoughFlash)?; - str::from_utf8(name_buf) - .map(|name_str| { - app_name_str = name_str; - }) - .or(Err(types::TbfParseError::BadProcessName))?; + package_name_pointer = Some(name_buf.try_into()?); } types::TbfHeaderTypes::TbfHeaderFixedAddresses => { @@ -301,7 +297,7 @@ pub fn parse_tbf_header( base: tbf_header_base, main: main_pointer, program: program_pointer, - package_name: Some(app_name_str), + package_name: package_name_pointer, writeable_regions: Some(wfr_pointer), fixed_addresses: fixed_address_pointer, permissions: permissions_pointer, diff --git a/tbf-parser/src/types.rs b/tbf-parser/src/types.rs index 6a8f062..416f5cf 100644 --- a/tbf-parser/src/types.rs +++ b/tbf-parser/src/types.rs @@ -8,8 +8,8 @@ //! Types and Data Structures for TBFs. use core::convert::TryInto; -use core::fmt; use core::mem::size_of; +use core::{fmt, str}; /// We only support up to a fixed number of storage permissions for each of read /// and modify. This simplification enables us to use fixed sized buffers. @@ -74,6 +74,10 @@ pub enum TbfParseError { /// too long for Tock to parse. /// This can be fixed by increasing the number in `TbfHeaderV2`. TooManyEntries(usize), + + /// The package name is too long for Tock to parse. + /// Consider a shorter name, or increasing the maximum size. + PackageNameTooLong, } impl From for TbfParseError { @@ -107,6 +111,7 @@ impl fmt::Debug for TbfParseError { tipe ) } + TbfParseError::PackageNameTooLong => write!(f, "The package name is too long."), } } } @@ -181,6 +186,12 @@ pub struct TbfHeaderV2Program { version: u32, } +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2PackageName { + size: u32, + buffer: [u8; L], +} + /// Writeable flash regions only need an offset and size. /// /// There can be multiple (or zero) flash regions defined, so this is its own @@ -414,6 +425,28 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Program { } } +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2PackageName { + type Error = TbfParseError; + + fn try_from(value: &[u8]) -> Result { + if value.len() > L { + return Err(TbfParseError::PackageNameTooLong); + } + + if str::from_utf8(value).is_err() { + return Err(TbfParseError::BadProcessName); + } + + let mut buffer = [0u8; L]; + buffer[..value.len()].copy_from_slice(value); + + Ok(TbfHeaderV2PackageName { + size: value.len() as u32, + buffer: buffer, + }) + } +} + impl core::convert::TryFrom<&[u8]> for TbfHeaderV2WriteableFlashRegion { type Error = TbfParseError; @@ -662,11 +695,11 @@ pub enum CommandPermissions { /// four since we need to statically know the length of the array to store in /// this type. #[derive(Clone, Copy, Debug)] -pub struct TbfHeaderV2<'a> { +pub struct TbfHeaderV2 { pub(crate) base: TbfHeaderV2Base, pub(crate) main: Option, pub(crate) program: Option, - pub(crate) package_name: Option<&'a str>, + pub(crate) package_name: Option>, pub(crate) writeable_regions: Option<[Option; 4]>, pub(crate) fixed_addresses: Option, pub(crate) permissions: Option>, @@ -684,12 +717,12 @@ pub struct TbfHeaderV2<'a> { // Clippy suggests we box TbfHeaderV2. We can't really do that, since // we are runnning under no_std, and I don't think it's that big of a issue. #[allow(clippy::large_enum_variant)] -pub enum TbfHeader<'a> { - TbfHeaderV2(TbfHeaderV2<'a>), +pub enum TbfHeader { + TbfHeaderV2(TbfHeaderV2), Padding(TbfHeaderV2Base), } -impl TbfHeader<'_> { +impl TbfHeader { /// Return the length of the header. pub fn length(&self) -> u16 { match *self { @@ -786,9 +819,13 @@ impl TbfHeader<'_> { } /// Get the name of the app. + // Note: We could return Result instead. So far, no editing methods have been implemented, and when the PackageName struct is created + // the str::from_utf8 function is ran beforehand to make sure the bytes are valid UTF-8. pub fn get_package_name(&self) -> Option<&str> { - match *self { - TbfHeader::TbfHeaderV2(hd) => hd.package_name, + match self { + TbfHeader::TbfHeaderV2(hd) => hd.package_name.as_ref().map(|name| { + str::from_utf8(&name.buffer[..name.size as usize]).expect("Package name is not valid UTF8. Conversion should have been checked beforehand.") + }), _ => None, } } From 2027cef427e827c60cb0e1914729b89938689aa7 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Tue, 8 Aug 2023 23:14:34 +0300 Subject: [PATCH 11/12] Footer now owns the hash/key --- tbf-parser/src/types.rs | 110 ++++++++++++++++++++++++++++++++------ tbf-parser/tests/parse.rs | 25 ++++++++- 2 files changed, 117 insertions(+), 18 deletions(-) diff --git a/tbf-parser/src/types.rs b/tbf-parser/src/types.rs index 416f5cf..92c922c 100644 --- a/tbf-parser/src/types.rs +++ b/tbf-parser/src/types.rs @@ -266,19 +266,59 @@ pub enum TbfFooterV2CredentialsType { SHA512 = 5, } +/// Reference: https://github.com/tock/tock/blob/master/doc/reference/trd-appid.md#52-credentials-footer #[derive(Clone, Copy, Debug)] -pub struct TbfFooterV2Credentials<'a> { - format: TbfFooterV2CredentialsType, - data: &'a [u8], +#[allow(clippy::large_enum_variant)] +pub enum TbfFooterV2Credentials { + Reserved(u32), + Rsa3072Key(TbfFooterV2RSA<384>), + Rsa4096Key(TbfFooterV2RSA<512>), + SHA256(TbfFooterV2SHA<32>), + SHA384(TbfFooterV2SHA<48>), + SHA512(TbfFooterV2SHA<64>), +} + +#[derive(Clone, Copy, Debug)] +pub struct TbfFooterV2SHA { + hash: [u8; L], +} + +#[derive(Clone, Copy, Debug)] +pub struct TbfFooterV2RSA { + public_key: [u8; L], + signature: [u8; L], +} + +impl TbfFooterV2SHA { + pub fn get_format(&self) -> Result { + match L { + 32 => Ok(TbfFooterV2CredentialsType::SHA256), + 48 => Ok(TbfFooterV2CredentialsType::SHA384), + 64 => Ok(TbfFooterV2CredentialsType::SHA512), + _ => Err(TbfParseError::InternalError), + } + } + + pub fn get_hash(&self) -> &[u8; L] { + &self.hash + } } -impl TbfFooterV2Credentials<'_> { - pub fn format(&self) -> TbfFooterV2CredentialsType { - self.format +impl TbfFooterV2RSA { + pub fn get_format(&self) -> Result { + match L { + 384 => Ok(TbfFooterV2CredentialsType::Rsa3072Key), + 512 => Ok(TbfFooterV2CredentialsType::Rsa4096Key), + _ => Err(TbfParseError::InternalError), + } + } + + pub fn get_public_key(&self) -> &[u8; L] { + &self.public_key } - pub fn data(&self) -> &[u8] { - self.data + pub fn get_signature(&self) -> &[u8; L] { + &self.signature } } @@ -637,11 +677,11 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2KernelVersion { } } -impl<'b, 'a: 'b> core::convert::TryFrom<&'a [u8]> for TbfFooterV2Credentials<'b> { +impl core::convert::TryFrom<&[u8]> for TbfFooterV2Credentials { type Error = TbfParseError; - fn try_from(b: &'a [u8]) -> Result, Self::Error> { - let format = u32::from_le_bytes( + fn try_from(b: &[u8]) -> Result { + let format: u32 = u32::from_le_bytes( b.get(0..4) .ok_or(TbfParseError::InternalError)? .try_into()?, @@ -665,13 +705,51 @@ impl<'b, 'a: 'b> core::convert::TryFrom<&'a [u8]> for TbfFooterV2Credentials<'b> TbfFooterV2CredentialsType::SHA384 => 48, TbfFooterV2CredentialsType::SHA512 => 64, }; - let data = &b + + let data = b .get(4..(length + 4)) .ok_or(TbfParseError::NotEnoughFlash)?; - Ok(TbfFooterV2Credentials { - format: ftype, - data: data, - }) + + match ftype { + TbfFooterV2CredentialsType::Reserved => { + Ok(TbfFooterV2Credentials::Reserved(b.len() as u32)) + } + TbfFooterV2CredentialsType::SHA256 => { + Ok(TbfFooterV2Credentials::SHA256(TbfFooterV2SHA { + hash: data.try_into().map_err(|_| TbfParseError::InternalError)?, + })) + } + TbfFooterV2CredentialsType::SHA384 => { + Ok(TbfFooterV2Credentials::SHA384(TbfFooterV2SHA { + hash: data.try_into().map_err(|_| TbfParseError::InternalError)?, + })) + } + TbfFooterV2CredentialsType::SHA512 => { + Ok(TbfFooterV2Credentials::SHA512(TbfFooterV2SHA { + hash: data.try_into().map_err(|_| TbfParseError::InternalError)?, + })) + } + TbfFooterV2CredentialsType::Rsa3072Key => { + Ok(TbfFooterV2Credentials::Rsa3072Key(TbfFooterV2RSA { + public_key: data[0..length / 2] + .try_into() + .map_err(|_| TbfParseError::InternalError)?, + signature: data[length / 2..] + .try_into() + .map_err(|_| TbfParseError::InternalError)?, + })) + } + TbfFooterV2CredentialsType::Rsa4096Key => { + Ok(TbfFooterV2Credentials::Rsa4096Key(TbfFooterV2RSA { + public_key: data[0..length / 2] + .try_into() + .map_err(|_| TbfParseError::InternalError)?, + signature: data[length / 2..] + .try_into() + .map_err(|_| TbfParseError::InternalError)?, + })) + } + } } } diff --git a/tbf-parser/tests/parse.rs b/tbf-parser/tests/parse.rs index 84a9f5f..34b3d69 100644 --- a/tbf-parser/tests/parse.rs +++ b/tbf-parser/tests/parse.rs @@ -1,4 +1,7 @@ -use tbf_parser::parse::*; +use tbf_parser::{ + parse::*, + types::{TbfFooterV2Credentials, TbfFooterV2CredentialsType}, +}; #[test] fn simple_tbf() { @@ -50,5 +53,23 @@ fn footer_sha256() { 214u8, 17, 81, 32, 51, 178, 249, 35, 161, 33, 109, 184, 195, 46, 238, 158, 141, 54, 63, 94, 60, 245, 50, 228, 239, 107, 231, 127, 220, 158, 77, 160, ]; - assert_eq!(footer.data(), correct_sha256); + if let TbfFooterV2Credentials::SHA256(creds) = footer { + assert_eq!( + creds.get_format().unwrap(), + TbfFooterV2CredentialsType::SHA256 + ); + assert_eq!(creds.get_hash(), &correct_sha256); + } else { + panic!("Footer is not of type SHA256!"); + } + + let second_footer_offset = binary_offset + footer_size as usize + 4; + let (footer, footer_size) = parse_tbf_footer(&buffer[second_footer_offset..]).unwrap(); + dbg!(footer); + assert_eq!(footer_size, 2312); + if let TbfFooterV2Credentials::Reserved(padding) = footer { + assert_eq!(padding, 2312); + } else { + panic!("Footer is not of type 'Reserved'!"); + } } From 8cfc20c7b71bf320f08d7ff675074914e86c3028 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Wed, 9 Aug 2023 00:03:45 +0300 Subject: [PATCH 12/12] Added RSA4096 test --- tbf-parser/tests/flashes/RSA4096.key | Bin 0 -> 512 bytes tbf-parser/tests/flashes/RSA4096.sig | Bin 0 -> 512 bytes tbf-parser/tests/flashes/footerRSA4096.dat | Bin 0 -> 4096 bytes tbf-parser/tests/parse.rs | 49 +++++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 tbf-parser/tests/flashes/RSA4096.key create mode 100644 tbf-parser/tests/flashes/RSA4096.sig create mode 100644 tbf-parser/tests/flashes/footerRSA4096.dat diff --git a/tbf-parser/tests/flashes/RSA4096.key b/tbf-parser/tests/flashes/RSA4096.key new file mode 100644 index 0000000000000000000000000000000000000000..bd9959cb8ad0c785e18313d3a0542163370e4fd7 GIT binary patch literal 512 zcmV+b0{{KAM4w^Th!kEbaAnqviO4<)*r+yN`yzOww_?J^jx=&XaAy^(aPhfN9JUme ztT(9SSqkqGXAGTWnynMNU`5ehYY8}{x9G(6Vfdw!$Xg=fW4=mU7i#B_%hYZVbtK=~ zVmYHNDoBQglb5i{iO+rIwrwc>0Mm7>J_J(eSN7C-VegI>cc+tRGbaqtmPz2C75=DE zTMYdmQgLJ}0wiQ;7A!n!k_ZGw@g|wEg6B3 zKGma_Ybh2jDY7XRM@QtV5y>a{}4DPQ>DD zM0vB)7u+oLDXIoLI-T0Q!=XWuv4vT7#}-q{;M|?^M|bp*xG;Hnbfd=`kI_hlGa$|F zehuG~-$KP|!G@p2nB|c=9s(=MJ)L<Nw;o?31|=7OLn35gymY8|x{gdjaHbBuet!qC?o Ci~C~$ literal 0 HcmV?d00001 diff --git a/tbf-parser/tests/flashes/RSA4096.sig b/tbf-parser/tests/flashes/RSA4096.sig new file mode 100644 index 0000000000000000000000000000000000000000..44b711305a4b770c1c5bf030888da1b7220cf58e GIT binary patch literal 512 zcmV+b0{{I@zIg~?IasJ)52Q2^;g+&)-e$74kpyI}t%@U(q*=rgX;s0X+j9b*TP4Ut z(EKfLKfW008VE%uQSIqiD9mUh4gN*|Z(VfqSYEZr*ta;iBLs+LfW-=PR`R2nz7ifX z7_YUy9t+x5L1KMz91O*>pAvrmLY?m!f>olA>sFZ*Wli$fKLQp zMlv#)XfQ73n{NH#13g~2x`TwD*-a;Aq}lru86oCJoM9->v|01)|2;T8PTT&;FuuWc z6tGG6xnaoV;upXlE6-NgEHG_?Srzl$%L!FdZ_i7CF382OIHdrh~EH)MW~U^KtMQ{%-kE|+;}+{+R#DuWE@LAITNQ2f#=h@ucZ_J!@?!Tp=D zzdw=hh%EuHv_;G%!Poo^;>l zotlv%e1~9}>LHTHCHCH*y)HPcJJ6nzH03SeG$V!$^80cA7~yNMpMPhe$_ C#`h)w literal 0 HcmV?d00001 diff --git a/tbf-parser/tests/flashes/footerRSA4096.dat b/tbf-parser/tests/flashes/footerRSA4096.dat new file mode 100644 index 0000000000000000000000000000000000000000..dc1f981cedc07f94e4b6f3198a33bbf8410e354a GIT binary patch literal 4096 zcmeHHdsq`!7Qd4W5FkK!8eVadfV{+Jd8|l_LWmO-Q7VEU76Cy6BE$kxRQz~9+-gB6 z*xCRqxMGWfl<-)~%LQdc3kWQzEK#fms+0g$20^lS>~=r9f9)UL|G3|G=6BAy=bX9o zn{zG&Yy*G+00IDzSouaGfDUY+PA*gr0CiyS8#^EUWseHf!2W%r$e5ToKm#bCK)09* zA2a~0A)JG{HPlm~UIlS4d;ycpg}NXbol!&S0yRsEA63w-sGCvF0xf|m&;b%Y7`=!C z%m%2)F5(&(8=~f0ODu8`q~ScU0RY7|LfI-7DwPZ=%{z#ox??y{Y+6wDMsR3h(BcAx z8UZjy3uuF%_I8P-XKD>`gaZobI1AUssW_zyDDD#yg2r=fmJkL&yS8OQ><$#cO$f&h z2gSO$DvT(D5lo&wjIaPxa#*<#;c#({Y7Eu@ifW>0Lgzo39!**}P3j?{ics7}z!U&Z zg{ba%p^ovu;3Sn19>o?|3xHyf7@Sn405++Q66|O;jMFyPd>hFaP|g=AfnqPQB=;pa zJYd(*Mx6nk!N#V>9?(V{hU5VR2iOI^yJP1|aTt@k^GQxJYqe$=bowOZk^sC^JC(lo z;n`7u)+*fV0OFaV%$6 z2JYcg6gm{DLWdgI=Ev6z?VF@>`z8^jT!@eiV@vXX|5n&J@b^lU^8-o*r!^xOkkMKg zQWU2_u1s*sc|?|Ot=5llJ0}r6t{8B+@fgl%p;)W*0}T4On3x^^vmE9rY>9XCC^Ae; z<~#`{fR{XBRu_UfL${OQdP~pX@a%YeEs36rB3IocozTXSo z;aNzrh6M!>DR}^Rk$4t>Z6u}vP);JeE68(`mW7Yn6jAbNR$R31msIa&7%FZq2I)|M-WlX#O_X=)RGm-_+8N**=3#Qo));W zC|&FrwJGBJy@d4YWk+yLXDZ2Of5j7fi?(CYHDz0eZvVs>yb$4jTVeZ=T)Qsj>EN#2 zNvL0}>5ax&*R?kdWn=8!Ngc;lpL87EWK{Oz^9+{jm67~XIqmvt)=)WS_L@7!*>!-v z$7Ff1c-G(FzjRNm`?K_NEA>2~(n00S`(A*29n~U4_hs%HGwRR*L)NYw?4(on8zN&q-U2nk1THfLG zAj7$T_Ne>sjO+DITi4xH>-z001Z7<|_$D{4Tk8kYQ(I57Opp6Co?ahu^0QET0>kZ9*3{#UH|MXlZ7P_~wMS~|H{Y@Bc#!kL z-t4*Rc)i*jon_YJHg8609&pJS3&%jlg1_OY=?J|vLH11YWdY|zPGV7%+da#SiOkoA zFIv*LB^yk3-imyF#kqd*{M`Pfn3r)CD&|+SEKtxwFCLRnU>5!v)H^R-#;AZGe38Y$~iaL zlz7ScRf5>^jdQW{fOo4wifW5SXh>3c`+6!8>7;W#k5#uLG%^)LsQq5|&`O;Z238nY QVPJ)U6$Vxq_$>qf0s!k0pa1{> literal 0 HcmV?d00001 diff --git a/tbf-parser/tests/parse.rs b/tbf-parser/tests/parse.rs index 34b3d69..22fe06c 100644 --- a/tbf-parser/tests/parse.rs +++ b/tbf-parser/tests/parse.rs @@ -73,3 +73,52 @@ fn footer_sha256() { panic!("Footer is not of type 'Reserved'!"); } } + +#[test] +fn footer_rsa4096() { + let buffer: Vec = include_bytes!("./flashes/footerRSA4096.dat").to_vec(); + + let (ver, header_len, whole_len) = parse_tbf_header_lengths(&buffer[0..8].try_into().unwrap()) + .ok() + .unwrap(); + assert_eq!(ver, 2); + assert_eq!(header_len, 76); + assert_eq!(whole_len, 4096); + + let header = parse_tbf_header(&buffer[0..header_len as usize], 2).unwrap(); + dbg!(&header); + assert_eq!(header.enabled(), true); + assert_eq!(header.get_minimum_app_ram_size(), 4612); + assert_eq!(header.get_init_function_offset(), 41 + header_len as u32); + assert_eq!(header.get_protected_size(), header_len as u32); + assert_eq!(header.get_package_name().unwrap(), "c_hello"); + assert_eq!(header.get_kernel_version().unwrap(), (2, 0)); + let binary_offset = header.get_binary_end() as usize; + assert_eq!(binary_offset, 1168); + + let (footer, footer_size) = parse_tbf_footer(&buffer[binary_offset..]).unwrap(); + dbg!(footer); + assert_eq!(footer_size, 1028); + let correct_key = include_bytes!("./flashes/RSA4096.key"); + let correct_signature = include_bytes!("./flashes/RSA4096.sig"); + if let TbfFooterV2Credentials::Rsa4096Key(creds) = footer { + assert_eq!( + creds.get_format().unwrap(), + TbfFooterV2CredentialsType::Rsa4096Key + ); + assert_eq!(creds.get_public_key(), correct_key); + assert_eq!(creds.get_signature(), correct_signature); + } else { + panic!("Footer is not of type SHA256!"); + } + + let second_footer_offset = binary_offset + footer_size as usize + 4; + let (footer, footer_size) = parse_tbf_footer(&buffer[second_footer_offset..]).unwrap(); + dbg!(footer); + assert_eq!(footer_size, 1892); + if let TbfFooterV2Credentials::Reserved(padding) = footer { + assert_eq!(padding, 1892); + } else { + panic!("Footer is not of type 'Reserved'!"); + } +}