Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 185 additions & 56 deletions src/fdt.rs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/fdt/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

use core::fmt::{self, Display, Formatter};

use super::{FDT_TAGSIZE, Fdt, FdtToken};
use super::{FDT_TAGSIZE, Fdt, FdtToken, NameValidation};
use crate::Node;
use crate::fdt::property::{FdtPropIter, FdtProperty, InnerPropIter};
use crate::standard::{AddressSpaceProperties, NodeStandard};
Expand Down Expand Up @@ -171,7 +171,7 @@ impl InnerChildIter {
FdtToken::BeginNode => return Some(*offset),
FdtToken::Prop => {
*offset = fdt
.next_property_offset(*offset, false)
.next_property_offset(*offset, NameValidation::Skip)
.expect("Fdt should be valid");
}
FdtToken::EndNode | FdtToken::End => return None,
Expand Down
4 changes: 2 additions & 2 deletions src/fdt/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use core::mem::size_of;

use zerocopy::FromBytes;

use super::{FDT_TAGSIZE, Fdt, FdtPropertyHeader, FdtToken};
use super::{FDT_TAGSIZE, Fdt, FdtPropertyHeader, FdtToken, NameValidation};
use crate::Property;

/// A property of a device tree node.
Expand Down Expand Up @@ -136,7 +136,7 @@ impl InnerPropIter {
#[must_use]
fn next_property_parsed<'a>(fdt: Fdt<'a>, offset: &mut usize) -> Option<ParsedProperty<'a>> {
*offset = fdt
.next_property_offset(*offset, false)
.next_property_offset(*offset, NameValidation::Skip)
.expect("Fdt should be valid");
Self::find_property(fdt, offset)
}
Expand Down
79 changes: 55 additions & 24 deletions src/fdt_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use zerocopy::{FromBytes, big_endian};

use crate::ToPropertyValue;
use crate::error::{BufferError, FdtParseError};
use crate::fdt::{FDT_NOP, FDT_TAGSIZE, Fdt, FdtHeader, FdtToken};
use crate::fdt::{FDT_NOP, FDT_TAGSIZE, Fdt, FdtHeader, FdtOptions, FdtToken};

/// A mutable flattened device tree.
pub struct FdtMut<B> {
Expand All @@ -39,7 +39,18 @@ impl<B: FdtBuffer> FdtMut<B> {
///
/// Returns an [`FdtParseError`] if the data is not a valid device tree.
pub fn new(data: B) -> Result<Self, FdtParseError> {
Fdt::new(data.as_ref())?;
Self::with_options(data, FdtOptions::new())
}

/// Creates a new mutable FDT from a buffer using the specified
/// [`FdtOptions`].
///
/// # Errors
///
/// Returns an [`FdtParseError`] if the data is not a valid device tree
/// according to `options`.
pub fn with_options(data: B, options: FdtOptions) -> Result<Self, FdtParseError> {
Fdt::with_options(data.as_ref(), options)?;
Ok(Self { data })
}

Expand Down Expand Up @@ -393,8 +404,22 @@ impl<'a> FdtMut<SliceBuffer<'a>> {
/// let mut node = fdt.find_node_mut("/a/b/c").unwrap();
/// ```
pub fn from_slice(slice: &'a mut [u8]) -> Result<Self, FdtParseError> {
Self::from_slice_with_options(slice, FdtOptions::new())
}

/// Creates a new `FdtMut` from a mutable byte slice using the specified
/// [`FdtOptions`] by wrapping it in a [`SliceBuffer`].
///
/// # Errors
///
/// Returns an [`FdtParseError`] if `slice` does not contain a valid device
/// tree according to `options`.
pub fn from_slice_with_options(
slice: &'a mut [u8],
options: FdtOptions,
) -> Result<Self, FdtParseError> {
let buffer = SliceBuffer::new(slice)?;
Self::new(buffer)
Self::with_options(buffer, options)
}

/// Creates a new `FdtMut` from a mutable byte slice without validation.
Expand Down Expand Up @@ -445,53 +470,59 @@ impl<'a> FdtMut<SliceBuffer<'a>> {
Self::from_slice_unchecked(slice)
}

/// Creates a new `FdtMut` from the given pointer, verifying that the header
/// and `totalsize` fit within `max_len` bytes before reading the rest of
/// the blob.
/// Creates a new `FdtMut` from the given pointer using the specified
/// [`FdtOptions`], verifying that the header and `totalsize` fit within
/// `options.max_size` bytes before reading the rest of the blob.
///
/// The resulting [`SliceBuffer`] is sized to `totalsize` bytes (not
/// `max_len`); callers with a larger writable buffer that want to use the
/// remaining space for in-place expansion should use
/// `options.max_size`); callers with a larger writable buffer that want to
/// use the remaining space for in-place expansion should use
/// [`FdtMut::from_slice`].
///
/// # Safety
///
/// If `max_len >= size_of::<FdtHeader>()` (40 bytes), `data` must be
/// valid and accessible for reading at least 8 bytes (`magic` and
/// If `options.max_size >= size_of::<FdtHeader>()` (40 bytes), `data` must
/// be valid and accessible for reading at least 8 bytes (`magic` and
/// `totalsize`), and if the header contains a valid FDT magic number and
/// `40 <= totalsize <= max_len`, the memory region starting at `data` and
/// spanning `totalsize` bytes must be valid and accessible for reading and
/// writing for the lifetime `'a`.
/// `40 <= totalsize <= options.max_size`, the memory region starting at
/// `data` and spanning `totalsize` bytes must be valid and accessible for
/// reading and writing for the lifetime `'a`.
///
/// # Errors
///
/// This function can return the same errors as [`Fdt::from_raw_bounded`].
/// This function can return the same errors as
/// [`Fdt::from_raw_with_options`].
///
/// # Examples
///
/// ```
/// # use dtoolkit::fdt::FdtOptions;
/// # use dtoolkit::fdt_mut::FdtMut;
/// # let mut dtb = include_bytes!("../tests/dtb/test.dtb").to_vec();
/// let len = dtb.len();
/// let options = FdtOptions::new().max_size(dtb.len());
/// let ptr = dtb.as_mut_ptr();
/// let fdt = unsafe { FdtMut::from_raw_bounded(ptr, len).unwrap() };
/// let fdt = unsafe { FdtMut::from_raw_with_options(ptr, options).unwrap() };
/// ```
#[expect(
unsafe_code,
reason = "Having a method that reads a Device Tree from a raw pointer is useful for \
embedded applications, where the binary only gets a pointer to DT from the firmware or \
a bootloader. The user must ensure it trusts the data."
)]
pub unsafe fn from_raw_bounded(data: *mut u8, max_len: usize) -> Result<Self, FdtParseError> {
pub unsafe fn from_raw_with_options(
data: *mut u8,
options: FdtOptions,
) -> Result<Self, FdtParseError> {
// SAFETY: The caller guarantees that `data` points to a readable FDT
// header prefix (when `max_len >= size_of::<FdtHeader>()`) and spans
// `totalsize` writable bytes when the FDT is valid and
// `40 <= totalsize <= max_len`.
let size = unsafe { Fdt::from_raw_bounded(data.cast_const(), max_len)? }
// header prefix (when `options.max_size >= size_of::<FdtHeader>()`) and
// spans `totalsize` writable bytes when the FDT is valid and
// `40 <= totalsize <= options.max_size`.
let size = unsafe { Fdt::from_raw_with_options(data.cast_const(), options)? }
.data()
.len();
// SAFETY: `Fdt::from_raw_bounded` validated the entire `size`-byte FDT
// blob and verified `size <= max_len` (and `size <= isize::MAX`).
// SAFETY: `Fdt::from_raw_with_options` validated the entire `size`-byte
// FDT blob and verified `size <= options.max_size` (and
// `size <= isize::MAX`).
let slice = unsafe { core::slice::from_raw_parts_mut(data, size) };
Ok(Self::from_slice_unchecked(slice))
}
Expand Down Expand Up @@ -527,7 +558,7 @@ impl<'a> FdtMut<SliceBuffer<'a>> {
pub unsafe fn from_raw(data: *mut u8) -> Result<Self, FdtParseError> {
// SAFETY: The caller guarantees that `data` points to a readable FDT
// header and spans `totalsize` writable bytes when the header is valid.
unsafe { Self::from_raw_bounded(data, usize::MAX) }
unsafe { Self::from_raw_with_options(data, FdtOptions::new()) }
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/fdt_mut/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::error::FdtMutError;
use crate::fdt::node::InnerChildIter;
use crate::fdt::node::private::FdtChildIter;
use crate::fdt::property::{FdtPropIter, InnerPropIter};
use crate::fdt::{FDT_TAGSIZE, Fdt, FdtNode, FdtProperty, FdtPropertyHeader};
use crate::fdt::{FDT_TAGSIZE, Fdt, FdtNode, FdtProperty, FdtPropertyHeader, NameValidation};
use crate::fdt_mut::buffer::FdtBuffer;
use crate::fdt_mut::property::FdtPropMutIter;
use crate::fdt_mut::{FdtMut, FdtPropertyMut};
Expand Down Expand Up @@ -97,7 +97,9 @@ impl<B: FdtBuffer> FdtNodeMut<'_, B> {
let mut offset = self.offset + FDT_TAGSIZE;
let name_end = fdt.find_string_end(offset).expect("valid node name");
offset = Fdt::align_tag_offset(name_end);
let insert_offset = fdt.skip_props(offset, false).expect("valid dt");
let insert_offset = fdt
.skip_props(offset, NameValidation::Skip)
.expect("Fdt should be valid");

let val_len = value.property_value_len();
let padded_val_len = Fdt::align_tag_offset(val_len);
Expand Down
2 changes: 1 addition & 1 deletion src/model/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl DeviceTreeNode {
/// ```
pub fn new(name: impl Into<String>) -> Result<Self, ModelError> {
let name = name.into();
if !crate::validate::is_valid_node_name(&name) {
if !crate::validate::is_valid_node_name(&name, false) {
return Err(ModelError::InvalidNodeName(name));
}
Ok(Self::new_unchecked(name))
Expand Down
2 changes: 1 addition & 1 deletion src/model/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl DeviceTreeProperty {
/// ```
pub fn new<T: ToPropertyValue>(name: impl Into<String>, value: T) -> Result<Self, ModelError> {
let name = name.into();
if !crate::validate::is_valid_property_name(&name) {
if !crate::validate::is_valid_property_name(&name, false) {
return Err(ModelError::InvalidPropertyName(name));
}
Ok(Self::new_unchecked(name, value))
Expand Down
76 changes: 47 additions & 29 deletions src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

//! Validation functions for Device Tree node and property names.

const MAX_NAME_LEN: usize = 31;

const fn is_valid_node_char(c: char) -> bool {
c.is_ascii_alphanumeric() || matches!(c, ',' | '.' | '_' | '+' | '-')
}
Expand All @@ -18,8 +20,11 @@ const fn is_valid_property_char(c: char) -> bool {

/// Validates a node name according to the Devicetree Specification.
///
/// If `strict_length` is `true`, the node name (before `@`) must not exceed 31
/// characters.
///
/// Returns `true` if valid, `false` otherwise.
pub(crate) fn is_valid_node_name(name: &str) -> bool {
pub(crate) fn is_valid_node_name(name: &str, strict_length: bool) -> bool {
// The root node is a special case with an empty name.
if name.is_empty() {
return true;
Expand All @@ -33,7 +38,7 @@ pub(crate) fn is_valid_node_name(name: &str) -> bool {
return false;
}

if node_name.is_empty() || node_name.len() > 31 {
if node_name.is_empty() || (strict_length && node_name.len() > MAX_NAME_LEN) {
return false;
}

Expand All @@ -56,9 +61,12 @@ pub(crate) fn is_valid_node_name(name: &str) -> bool {

/// Validates a property name according to the Devicetree Specification.
///
/// If `strict_length` is `true`, the property name must not exceed 31
/// characters.
///
/// Returns `true` if valid, `false` otherwise.
pub(crate) fn is_valid_property_name(name: &str) -> bool {
if name.is_empty() || name.len() > 31 {
pub(crate) fn is_valid_property_name(name: &str, strict_length: bool) -> bool {
if name.is_empty() || (strict_length && name.len() > MAX_NAME_LEN) {
return false;
}

Expand All @@ -70,43 +78,53 @@ mod tests {
use super::*;

#[test]
fn test_valid_node_names() {
assert!(is_valid_node_name(""));
assert!(is_valid_node_name("cpu"));
assert!(is_valid_node_name("cpu@0"));
assert!(is_valid_node_name("memory@10000000"));
assert!(is_valid_node_name("serial@3f201000"));
assert!(is_valid_node_name("a,b.c_d+e-f"));
fn valid_node_names() {
assert!(is_valid_node_name("", true));
assert!(is_valid_node_name("cpu", true));
assert!(is_valid_node_name("cpu@0", true));
assert!(is_valid_node_name("memory@10000000", true));
assert!(is_valid_node_name("serial@3f201000", true));
assert!(is_valid_node_name("a,b.c_d+e-f", true));
assert!(is_valid_node_name(
"this-is-a-very-long-node-name-that-exceeds-thirty-one-characters",
false
));
}

#[test]
fn test_invalid_node_names() {
assert!(!is_valid_node_name("cpu@0@1")); // Too many '@'
assert!(!is_valid_node_name("cpu@")); // Empty unit address
assert!(!is_valid_node_name("@0")); // Empty node name
assert!(!is_valid_node_name("cpu name")); // Invalid space
assert!(!is_valid_node_name("cpu#name")); // Invalid '#'
fn invalid_node_names() {
assert!(!is_valid_node_name("cpu@0@1", false)); // Too many '@'
assert!(!is_valid_node_name("cpu@", false)); // Empty unit address
assert!(!is_valid_node_name("@0", false)); // Empty node name
assert!(!is_valid_node_name("cpu name", false)); // Invalid space
assert!(!is_valid_node_name("cpu#name", false)); // Invalid '#'
assert!(!is_valid_node_name(
"this-is-a-very-long-node-name-that-exceeds-thirty-one-characters"
"this-is-a-very-long-node-name-that-exceeds-thirty-one-characters",
true
)); // > 31 chars
}

#[test]
fn test_valid_property_names() {
assert!(is_valid_property_name("reg"));
assert!(is_valid_property_name("#address-cells"));
assert!(is_valid_property_name("device_type"));
assert!(is_valid_property_name("compatible"));
assert!(is_valid_property_name("a,b.c_d+e-f?#"));
fn valid_property_names() {
assert!(is_valid_property_name("reg", true));
assert!(is_valid_property_name("#address-cells", true));
assert!(is_valid_property_name("device_type", true));
assert!(is_valid_property_name("compatible", true));
assert!(is_valid_property_name("a,b.c_d+e-f?#", true));
assert!(is_valid_property_name(
"snps,dis-u2-freeclk-exists-quirk",
false
));
}

#[test]
fn test_invalid_property_names() {
assert!(!is_valid_property_name("")); // Empty
assert!(!is_valid_property_name("my prop")); // Invalid space
assert!(!is_valid_property_name("my@prop")); // Invalid '@'
fn invalid_property_names() {
assert!(!is_valid_property_name("", false)); // Empty
assert!(!is_valid_property_name("my prop", false)); // Invalid space
assert!(!is_valid_property_name("my@prop", false)); // Invalid '@'
assert!(!is_valid_property_name(
"this-is-a-very-long-property-name-that-exceeds-thirty-one-characters"
"this-is-a-very-long-property-name-that-exceeds-thirty-one-characters",
true
)); // > 31 chars
}
}
Loading
Loading