| // Copyright 2024, The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! This library provides a wrapper APIs for libdttable_c |
| //! https://source.android.com/docs/core/architecture/dto/partitions |
| |
| #![cfg_attr(not(test), no_std)] |
| |
| use core::mem::size_of; |
| use libdttable_bindgen::{dt_table_entry, dt_table_header, DT_TABLE_MAGIC}; |
| use liberror::{Error, Result}; |
| use safemath::SafeNum; |
| use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref}; |
| |
| /// Rust wrapper for the dt table header |
| #[repr(transparent)] |
| #[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable, KnownLayout, PartialEq)] |
| struct DtTableHeader(dt_table_header); |
| |
| impl DtTableHeader { |
| /// Get magic handling the bytes order |
| fn magic(self) -> u32 { |
| u32::from_be(self.0.magic) |
| } |
| |
| /// Get dt_entry_count handling the bytes order |
| fn dt_entry_count(self) -> u32 { |
| u32::from_be(self.0.dt_entry_count) |
| } |
| |
| /// Get dt_entry_size handling the bytes order |
| fn dt_entry_size(self) -> u32 { |
| u32::from_be(self.0.dt_entry_size) |
| } |
| |
| /// Get dt_entries_offset handling the bytes order |
| fn dt_entries_offset(self) -> u32 { |
| u32::from_be(self.0.dt_entries_offset) |
| } |
| } |
| |
| /// Rust wrapper for the dt table entry |
| #[repr(transparent)] |
| #[derive(Debug, Copy, Clone, Immutable, IntoBytes, KnownLayout, FromBytes, PartialEq)] |
| struct DtTableHeaderEntry(dt_table_entry); |
| |
| impl DtTableHeaderEntry { |
| /// Get id handling the bytes order |
| fn id(self) -> u32 { |
| u32::from_be(self.0.id) |
| } |
| |
| /// Get rev handling the bytes order |
| fn rev(self) -> u32 { |
| u32::from_be(self.0.rev) |
| } |
| |
| /// Get dt_size handling the bytes order |
| fn dt_size(self) -> u32 { |
| u32::from_be(self.0.dt_size) |
| } |
| |
| /// Get dt_offset handling the bytes order |
| fn dt_offset(self) -> u32 { |
| u32::from_be(self.0.dt_offset) |
| } |
| } |
| |
| /// Metadata provided by entry header |
| #[derive(Copy, Default, Clone, Eq, PartialEq, Debug)] |
| pub struct DtTableMetadata { |
| /// id field from corresponding entry header |
| pub id: u32, |
| /// rev field from corresponding entry header |
| pub rev: u32, |
| /// custom field from corresponding entry header |
| pub custom: [u32; 4], |
| } |
| |
| /// Device tree blob obtained from multidt table image |
| #[derive(Copy, Clone, Eq, PartialEq, Debug)] |
| pub struct DtTableEntry<'a> { |
| /// dtb payload extracted from image |
| pub dtb: &'a [u8], |
| /// Metadata provided by corresponding entry header |
| pub metadata: DtTableMetadata, |
| } |
| |
| /// Represents entier multidt table image |
| pub struct DtTableImage<'a> { |
| buffer: &'a [u8], |
| header: Ref<&'a [u8], DtTableHeader>, |
| entries: Ref<&'a [u8], [DtTableHeaderEntry]>, |
| } |
| |
| /// To iterate over entries. |
| pub struct DtTableImageIterator<'a> { |
| table_image: &'a DtTableImage<'a>, |
| current_index: usize, |
| } |
| |
| impl<'a> Iterator for DtTableImageIterator<'a> { |
| type Item = DtTableEntry<'a>; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| if self.current_index < self.table_image.entries_count() { |
| let result = self.table_image.nth_entry(self.current_index).unwrap(); |
| self.current_index += 1; |
| Some(result) |
| } else { |
| None |
| } |
| } |
| } |
| |
| impl<'a> DtTableImage<'a> { |
| /// Verify and parse passed buffer following multidt table structure |
| pub fn from_bytes(buffer: &'a [u8]) -> Result<DtTableImage<'a>> { |
| let (header_layout, _) = Ref::new_from_prefix(buffer) |
| .ok_or(Error::BufferTooSmall(Some(size_of::<DtTableHeader>())))?; |
| |
| let header: &DtTableHeader = &header_layout; |
| if header.magic() != DT_TABLE_MAGIC { |
| return Err(Error::BadMagic); |
| } |
| |
| let entries_offset: SafeNum = header.dt_entries_offset().into(); |
| let entry_size: SafeNum = header.dt_entry_size().into(); |
| let entries_count: SafeNum = header.dt_entry_count().into(); |
| |
| let entries_start = entries_offset.try_into()?; |
| let entries_end = (entries_offset + entry_size * entries_count).try_into()?; |
| |
| let entries_buffer = buffer |
| .get(entries_start..entries_end) |
| .ok_or(Error::BufferTooSmall(Some(entries_end)))?; |
| let entries_layout = Ref::new_slice(entries_buffer).ok_or(Error::InvalidInput)?; |
| |
| Ok(DtTableImage { buffer: buffer, header: header_layout, entries: entries_layout }) |
| } |
| |
| /// Get amount of presented dt entries in the multidt table image |
| pub fn entries_count(&self) -> usize { |
| self.header.dt_entry_count().try_into().unwrap() |
| } |
| |
| /// Returns an iterator over the entries in the DT table image |
| pub fn entries(&'a self) -> DtTableImageIterator<'a> { |
| DtTableImageIterator { table_image: self, current_index: 0 } |
| } |
| |
| /// Get nth dtb buffer with multidt table structure metadata |
| pub fn nth_entry(&self, n: usize) -> Result<DtTableEntry<'a>> { |
| let entry = self.entries.get(n).ok_or(Error::BadIndex(n))?; |
| |
| let dtb_offset: SafeNum = entry.dt_offset().into(); |
| let dtb_size: SafeNum = entry.dt_size().into(); |
| |
| let dtb_start: usize = dtb_offset.try_into()?; |
| let dtb_end: usize = (dtb_offset + dtb_size).try_into()?; |
| |
| let dtb_buffer = |
| self.buffer.get(dtb_start..dtb_end).ok_or(Error::BufferTooSmall(Some(dtb_end)))?; |
| |
| Ok(DtTableEntry { |
| dtb: dtb_buffer, |
| metadata: DtTableMetadata { id: entry.id(), rev: entry.rev(), custom: entry.0.custom }, |
| }) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use fdt::Fdt; |
| |
| #[test] |
| fn test_dt_table_is_parsed() { |
| let dttable = include_bytes!("../test/data/dttable.img").to_vec(); |
| let table = DtTableImage::from_bytes(&dttable[..]).unwrap(); |
| |
| assert_eq!(table.entries_count(), 2, "Test data dttable image must have 2 dtb entries"); |
| |
| let first_entry = table.nth_entry(0).unwrap(); |
| let second_entry = table.nth_entry(1).unwrap(); |
| |
| assert_eq!( |
| first_entry.metadata, |
| DtTableMetadata { id: 1, rev: 0, custom: Default::default() }, |
| "First dttable entry is incorrect" |
| ); |
| assert_eq!( |
| second_entry.metadata, |
| DtTableMetadata { id: 2, rev: 0, custom: Default::default() }, |
| "Second dttable entry is incorrect" |
| ); |
| |
| // verify fdt headers are properly parsed |
| let _ = Fdt::new(first_entry.dtb).unwrap(); |
| let _ = Fdt::new(second_entry.dtb).unwrap(); |
| } |
| |
| #[test] |
| fn test_dt_table_is_parsed_iterator() { |
| let dttable = include_bytes!("../test/data/dttable.img").to_vec(); |
| let table = DtTableImage::from_bytes(&dttable[..]).unwrap(); |
| |
| // Collect entries from the iterator |
| let entries: Vec<_> = table.entries().collect(); |
| |
| // Verify that the iterator yields the correct number of entries |
| assert_eq!(entries.len(), 2, "Iterator should yield 2 entries"); |
| |
| // Unwrap the entries from Result |
| let first_entry = &entries[0]; |
| let second_entry = &entries[1]; |
| |
| assert_eq!( |
| first_entry.metadata, |
| DtTableMetadata { id: 1, rev: 0, custom: Default::default() }, |
| "First dttable entry metadata is incorrect" |
| ); |
| assert_eq!( |
| second_entry.metadata, |
| DtTableMetadata { id: 2, rev: 0, custom: Default::default() }, |
| "Second dttable entry metadata is incorrect" |
| ); |
| |
| // Verify FDT headers are properly parsed |
| let _ = Fdt::new(first_entry.dtb).unwrap(); |
| let _ = Fdt::new(second_entry.dtb).unwrap(); |
| } |
| |
| #[test] |
| fn test_failed_to_parse_corrupted_dt_table() { |
| let dttable = include_bytes!("../test/data/corrupted_dttable.img").to_vec(); |
| |
| assert!( |
| DtTableImage::from_bytes(&dttable[..]).is_err(), |
| "Must fail when trying to parse corrupted dt table image" |
| ); |
| } |
| } |