use core::{
cmp::Ordering,
hash::{Hash, Hasher},
mem::size_of,
};
use zerocopy::{FromBytes, FromZeroes};
use super::{BlobError, Devicetree, DtUint, Item, Node, Property, Result};
use crate::util;
pub(super) const TOKEN_SIZE: DtUint = 4;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Token<'dtb> {
BeginNode(Node<'dtb>),
EndNode,
Property(Property<'dtb>),
}
impl<'dtb> Token<'dtb> {
pub fn into_item(self) -> Option<Item<'dtb>> {
match self {
Self::BeginNode(node) => Some(Item::Child(node)),
Self::EndNode => None,
Self::Property(prop) => Some(Item::Property(prop)),
}
}
}
#[derive(Clone, Copy, Debug, Eq)]
pub struct Cursor {
pub(super) depth: DtUint,
pub(super) offset: DtUint,
}
impl PartialOrd for Cursor {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Ord::cmp(self, other))
}
}
impl Ord for Cursor {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
let res = Ord::cmp(&self.offset, &other.offset);
if res == Ordering::Equal {
debug_assert_eq!(self.depth, other.depth);
}
res
}
}
impl PartialEq for Cursor {
#[inline]
fn eq(&self, other: &Self) -> bool {
Ord::cmp(self, other) == Ordering::Equal
}
}
impl Hash for Cursor {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.offset.hash(state);
}
}
impl Cursor {
fn increase_offset(&mut self, add: DtUint, blob: &[u8]) -> Result<()> {
let offset = DtUint::checked_add(self.offset, add)
.and_then(|o| o.checked_next_multiple_of(TOKEN_SIZE))
.ok_or(BlobError::UnexpectedEnd)?;
blob.get(offset as usize..)
.ok_or(BlobError::UnexpectedEnd)?;
self.offset = offset;
Ok(())
}
}
#[repr(u32)]
enum RawToken {
BeginNode = 1_u32.to_be(),
EndNode = 2_u32.to_be(),
Prop = 3_u32.to_be(),
Nop = 4_u32.to_be(),
End = 9_u32.to_be(),
}
impl Devicetree {
pub fn root_node(&self) -> Result<Node<'_>> {
let mut cursor = Cursor {
depth: 0,
offset: u32::from_be(self.header().off_dt_struct) as DtUint,
};
match self.next_token(&mut cursor)? {
Some(Token::BeginNode(node)) if node.name.is_empty() => Ok(node),
_ => Err(BlobError::InvalidRootNode),
}
}
pub fn next_token(&self, cursor: &mut Cursor) -> Result<Option<Token<'_>>> {
#[derive(FromBytes, FromZeroes)]
#[repr(C)]
struct PropHeader {
len: u32,
nameoff: u32,
}
let blob = self.blob_with_struct_block_end();
loop {
let token = next_raw(blob, cursor)?.ok_or(BlobError::UnexpectedEnd)?;
let offset = cursor.offset as usize;
let token = match token {
RawToken::BeginNode => {
let name = &blob[offset..];
let name = util::get_c_str(name)?;
cursor.increase_offset(name.len() as DtUint + 1, blob)?;
cursor.depth += 1;
Token::BeginNode(Node {
dt: self,
name,
contents: *cursor,
})
}
RawToken::EndNode => {
let depth = DtUint::checked_sub(cursor.depth, 1)
.ok_or(BlobError::UnexpectedEndNodeToken)?;
cursor.depth = depth;
Token::EndNode
}
RawToken::Prop => {
let header = PropHeader::read_from_prefix(&blob[offset..])
.ok_or(BlobError::UnexpectedEnd)?;
let name_blob = usize::try_from(u32::from_be(header.nameoff))
.ok()
.and_then(|offset| self.strings_blob().get(offset..))
.ok_or(BlobError::InvalidString)?;
cursor.offset += size_of::<PropHeader>() as DtUint;
let offset = cursor.offset as usize;
let len = DtUint::try_from(u32::from_be(header.len))
.map_err(|_| BlobError::InvalidPropertyHeader)?;
let value = util::slice_get_with_len(blob, offset, len as usize)
.ok_or(BlobError::InvalidPropertyHeader)?;
cursor.increase_offset(len, blob)?;
Token::Property(Property { name_blob, value })
}
RawToken::Nop => continue,
RawToken::End => {
if cursor.depth == 0 {
return Ok(None);
} else {
return Err(BlobError::UnexpectedEndToken);
}
}
};
return Ok(Some(token));
}
}
}
fn next_raw(blob: &[u8], cursor: &mut Cursor) -> Result<Option<RawToken>> {
const BEGIN_NODE: u32 = RawToken::BeginNode as u32;
const END_NODE: u32 = RawToken::EndNode as u32;
const PROP: u32 = RawToken::Prop as u32;
const NOP: u32 = RawToken::Nop as u32;
const END: u32 = RawToken::End as u32;
let offset = cursor.offset as usize;
let Some(token) = util::slice_get_with_len(blob, offset, TOKEN_SIZE as usize) else {
return Ok(None);
};
let token = u32::from_ne_bytes(token.try_into().unwrap());
cursor.offset += TOKEN_SIZE;
let token = match token {
BEGIN_NODE => RawToken::BeginNode,
END_NODE => RawToken::EndNode,
PROP => RawToken::Prop,
NOP => RawToken::Nop,
END => RawToken::End,
_ => return Err(BlobError::UnknownToken),
};
Ok(Some(token))
}