Skip to content

Commit 37a1892

Browse files
committed
refactor(writer): avoid some intermediate allocations
This doesn't change the public interface at all, but should also make it easier to avoid intermediate allocations in the future. Signed-off-by: Ellen Εμιλία Άννα Zscheile <[email protected]>
1 parent ef5bd73 commit 37a1892

File tree

1 file changed

+68
-19
lines changed

1 file changed

+68
-19
lines changed

src/writer.rs

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
//! This module writes Flattened Devicetree blobs as defined here:
55
//! <https://devicetree-specification.readthedocs.io/en/stable/flattened-format.html>
66
7+
use alloc::borrow::ToOwned;
78
use alloc::collections::BTreeMap;
89
use alloc::ffi::CString;
910
use alloc::string::String;
1011
use alloc::vec::Vec;
1112
use core::cmp::{Ord, Ordering};
1213
use core::convert::TryInto;
14+
use core::ffi::CStr;
1315
use core::fmt;
1416
use core::mem::size_of_val;
1517
#[cfg(feature = "std")]
@@ -216,21 +218,28 @@ fn node_name_valid_first_char(c: char) -> bool {
216218
// Check if `name` is a valid property name.
217219
// https://devicetree-specification.readthedocs.io/en/stable/devicetree-basics.html#property-names
218220
fn property_name_valid(name: &str) -> bool {
219-
if name.is_empty() || name.len() > PROPERTY_NAME_MAX_LEN {
220-
return false;
221-
}
222-
223-
if name.contains(|c: char| !property_name_valid_char(c)) {
224-
return false;
225-
}
221+
!(name.is_empty()
222+
|| name.len() > PROPERTY_NAME_MAX_LEN
223+
|| name.contains(|c| !property_name_valid_char(c)))
224+
}
226225

227-
true
226+
// Check if `name` is a valid property name.
227+
// https://devicetree-specification.readthedocs.io/en/stable/devicetree-basics.html#property-names
228+
fn property_cstr_name_valid(name: &CStr) -> bool {
229+
let name_bytes = name.to_bytes();
230+
!(name_bytes.is_empty()
231+
|| name_bytes.len() > PROPERTY_NAME_MAX_LEN
232+
|| name_bytes.iter().any(|&c| !property_name_valid_byte(c)))
228233
}
229234

230235
fn property_name_valid_char(c: char) -> bool {
231236
matches!(c, '0'..='9' | 'a'..='z' | 'A'..='Z' | ',' | '.' | '_' | '+' | '?' | '#' | '-')
232237
}
233238

239+
fn property_name_valid_byte(c: u8) -> bool {
240+
matches!(c, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b',' | b'.' | b'_' | b'+' | b'?' | b'#' | b'-')
241+
}
242+
234243
/// Handle to an open node created by `FdtWriter::begin_node`.
235244
///
236245
/// This must be passed back to `FdtWriter::end_node` to close the nodes.
@@ -390,19 +399,19 @@ impl FdtWriter {
390399

391400
// Find an existing instance of a string `s`, or add it to the strings block.
392401
// Returns the offset into the strings block.
393-
fn intern_string(&mut self, s: CString) -> Result<u32> {
394-
if let Some(off) = self.string_offsets.get(&s) {
395-
Ok(*off)
402+
fn intern_string(&mut self, s: &CStr) -> Result<u32> {
403+
Ok(if let Some(off) = self.string_offsets.get(s) {
404+
*off
396405
} else {
397406
let off = self
398407
.strings
399408
.len()
400409
.try_into()
401410
.map_err(|_| Error::TotalSizeTooLarge)?;
402411
self.strings.extend_from_slice(s.to_bytes_with_nul());
403-
self.string_offsets.insert(s, off);
404-
Ok(off)
405-
}
412+
self.string_offsets.insert(s.to_owned(), off);
413+
off
414+
})
406415
}
407416

408417
/// Write a property.
@@ -431,11 +440,45 @@ impl FdtWriter {
431440
.try_into()
432441
.map_err(|_| Error::PropertyValueTooLarge)?;
433442

434-
let nameoff = self.intern_string(name_cstr)?;
443+
let nameoff = self.intern_string(&name_cstr)?;
444+
self.append_u32(FDT_PROP);
445+
self.append_u32(len);
446+
self.append_u32(nameoff);
447+
self.data.extend_from_slice(val);
448+
self.align(4);
449+
Ok(())
450+
}
451+
452+
/// Write a property, but add a NUL byte after `val` and make sure that `val` doesn't contain NUL bytes.
453+
/// Otherwise identical to [`FdtWriter::property`].
454+
///
455+
/// The purpose of this is to avoid unnecessary intermediate allocations.
456+
fn property_and_nul(&mut self, name: &CStr, val: &[u8]) -> Result<()> {
457+
if self.node_ended {
458+
return Err(Error::PropertyAfterEndNode);
459+
}
460+
461+
if self.node_depth == 0 {
462+
return Err(Error::PropertyBeforeBeginNode);
463+
}
464+
465+
if !property_cstr_name_valid(name) {
466+
return Err(Error::InvalidPropertyName);
467+
}
468+
if val.contains(&0x00) {
469+
return Err(Error::InvalidString);
470+
}
471+
472+
let len = (val.len() + 1)
473+
.try_into()
474+
.map_err(|_| Error::PropertyValueTooLarge)?;
475+
476+
let nameoff = self.intern_string(name)?;
435477
self.append_u32(FDT_PROP);
436478
self.append_u32(len);
437479
self.append_u32(nameoff);
438480
self.data.extend_from_slice(val);
481+
self.data.push(0x00);
439482
self.align(4);
440483
Ok(())
441484
}
@@ -447,16 +490,22 @@ impl FdtWriter {
447490

448491
/// Write a string property.
449492
pub fn property_string(&mut self, name: &str, val: &str) -> Result<()> {
450-
let cstr_value = CString::new(val).map_err(|_| Error::InvalidString)?;
451-
self.property(name, cstr_value.to_bytes_with_nul())
493+
self.property_and_nul(
494+
&CString::new(name).map_err(|_| Error::InvalidString)?,
495+
val.as_bytes(),
496+
)
452497
}
453498

454499
/// Write a stringlist property.
455500
pub fn property_string_list(&mut self, name: &str, values: Vec<String>) -> Result<()> {
456501
let mut bytes = Vec::new();
457502
for s in values {
458-
let cstr = CString::new(s).map_err(|_| Error::InvalidString)?;
459-
bytes.extend_from_slice(cstr.to_bytes_with_nul());
503+
let s = s.as_bytes();
504+
if s.contains(&0x00) {
505+
return Err(Error::InvalidString);
506+
}
507+
bytes.extend_from_slice(s);
508+
bytes.push(0x00);
460509
}
461510
self.property(name, &bytes)
462511
}

0 commit comments

Comments
 (0)