blob: e5b956372da32619370166fee7f4e50d764e72b8 [file]
// 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::{AsBytes, FromBytes, FromZeroes, LayoutVerified};
/// Rust wrapper for the dt table header
#[repr(transparent)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes, 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, AsBytes, FromBytes, FromZeroes, 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)
}
}
/// Device tree blob obtained from multidt table image
pub struct DtTableEntry<'a> {
/// id field from corresponding entry header
pub id: u32,
/// rev field from corresponding entry header
pub rev: u32,
/// dtb payload extracted from image
pub dtb: &'a [u8],
}
/// Represents entier multidt table image
pub struct DtTableImage<'a> {
buffer: &'a [u8],
header: LayoutVerified<&'a [u8], DtTableHeader>,
entries: LayoutVerified<&'a [u8], [DtTableHeaderEntry]>,
}
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, _) = LayoutVerified::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 =
LayoutVerified::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) -> u32 {
self.header.dt_entry_count()
}
/// 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 { id: entry.id(), rev: entry.rev(), dtb: dtb_buffer })
}
}
#[cfg(test)]
mod test {
use super::*;
use fdt::Fdt;
#[test]
fn test_dt_table_is_parsed() {
let dttable = include_bytes!("../test/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.id, 1, "First dttable entry id is incorrect");
assert_eq!(first_entry.rev, 0, "First dttable entry rev is incorrect");
assert_eq!(second_entry.id, 2, "Second dttable entry id is incorrect");
assert_eq!(second_entry.rev, 0, "Second dttable entry rev 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/corrupted_dttable.img").to_vec();
assert!(
DtTableImage::from_bytes(&dttable[..]).is_err(),
"Must fail when trying to parse corrupted dt table image"
);
}
}