Skip to content

Commit f83bfa2

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 <fogti+devel@ytrizja.de>
1 parent ef5bd73 commit f83bfa2

File tree

1 file changed

+69
-31
lines changed

1 file changed

+69
-31
lines changed

src/writer.rs

Lines changed: 69 additions & 31 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::Cow;
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")]
@@ -215,20 +217,15 @@ fn node_name_valid_first_char(c: char) -> bool {
215217

216218
// Check if `name` is a valid property name.
217219
// https://devicetree-specification.readthedocs.io/en/stable/devicetree-basics.html#property-names
218-
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-
}
226-
227-
true
220+
fn property_name_valid(name: &CStr) -> bool {
221+
let name_bytes = name.to_bytes();
222+
!(name_bytes.is_empty()
223+
|| name_bytes.len() > PROPERTY_NAME_MAX_LEN
224+
|| name_bytes.iter().any(|&c| !property_name_valid_byte(c)))
228225
}
229226

230-
fn property_name_valid_char(c: char) -> bool {
231-
matches!(c, '0'..='9' | 'a'..='z' | 'A'..='Z' | ',' | '.' | '_' | '+' | '?' | '#' | '-')
227+
fn property_name_valid_byte(c: u8) -> bool {
228+
matches!(c, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b',' | b'.' | b'_' | b'+' | b'?' | b'#' | b'-')
232229
}
233230

234231
/// Handle to an open node created by `FdtWriter::begin_node`.
@@ -390,19 +387,19 @@ impl FdtWriter {
390387

391388
// Find an existing instance of a string `s`, or add it to the strings block.
392389
// 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)
390+
fn intern_string(&mut self, s: Cow<'_, CStr>) -> Result<u32> {
391+
Ok(if let Some(off) = self.string_offsets.get(&*s) {
392+
*off
396393
} else {
397394
let off = self
398395
.strings
399396
.len()
400397
.try_into()
401398
.map_err(|_| Error::TotalSizeTooLarge)?;
402399
self.strings.extend_from_slice(s.to_bytes_with_nul());
403-
self.string_offsets.insert(s, off);
404-
Ok(off)
405-
}
400+
self.string_offsets.insert(s.into_owned(), off);
401+
off
402+
})
406403
}
407404

408405
/// Write a property.
@@ -422,7 +419,7 @@ impl FdtWriter {
422419

423420
let name_cstr = CString::new(name).map_err(|_| Error::InvalidString)?;
424421

425-
if !property_name_valid(name) {
422+
if !property_name_valid(&name_cstr) {
426423
return Err(Error::InvalidPropertyName);
427424
}
428425

@@ -431,11 +428,45 @@ impl FdtWriter {
431428
.try_into()
432429
.map_err(|_| Error::PropertyValueTooLarge)?;
433430

434-
let nameoff = self.intern_string(name_cstr)?;
431+
let nameoff = self.intern_string(name_cstr.into())?;
432+
self.append_u32(FDT_PROP);
433+
self.append_u32(len);
434+
self.append_u32(nameoff);
435+
self.data.extend_from_slice(val);
436+
self.align(4);
437+
Ok(())
438+
}
439+
440+
/// Write a property, but add a NUL byte after `val` and make sure that `val` doesn't contain NUL bytes.
441+
/// Otherwise identical to [`FdtWriter::property`].
442+
///
443+
/// The purpose of this is to avoid unnecessary intermediate allocations.
444+
fn property_and_nul(&mut self, name: Cow<'_, CStr>, val: &[u8]) -> Result<()> {
445+
if self.node_ended {
446+
return Err(Error::PropertyAfterEndNode);
447+
}
448+
449+
if self.node_depth == 0 {
450+
return Err(Error::PropertyBeforeBeginNode);
451+
}
452+
453+
if !property_name_valid(&name) {
454+
return Err(Error::InvalidPropertyName);
455+
}
456+
if val.contains(&0x00) {
457+
return Err(Error::InvalidString);
458+
}
459+
460+
let len = (val.len() + 1)
461+
.try_into()
462+
.map_err(|_| Error::PropertyValueTooLarge)?;
463+
464+
let nameoff = self.intern_string(name)?;
435465
self.append_u32(FDT_PROP);
436466
self.append_u32(len);
437467
self.append_u32(nameoff);
438468
self.data.extend_from_slice(val);
469+
self.data.push(0x00);
439470
self.align(4);
440471
Ok(())
441472
}
@@ -447,16 +478,22 @@ impl FdtWriter {
447478

448479
/// Write a string property.
449480
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())
481+
self.property_and_nul(
482+
CString::new(name).map_err(|_| Error::InvalidString)?.into(),
483+
val.as_bytes(),
484+
)
452485
}
453486

454487
/// Write a stringlist property.
455488
pub fn property_string_list(&mut self, name: &str, values: Vec<String>) -> Result<()> {
456489
let mut bytes = Vec::new();
457490
for s in values {
458-
let cstr = CString::new(s).map_err(|_| Error::InvalidString)?;
459-
bytes.extend_from_slice(cstr.to_bytes_with_nul());
491+
let s = s.as_bytes();
492+
if s.contains(&0x00) {
493+
return Err(Error::InvalidString);
494+
}
495+
bytes.extend_from_slice(s);
496+
bytes.push(0x00);
460497
}
461498
self.property(name, &bytes)
462499
}
@@ -1119,17 +1156,18 @@ mod tests {
11191156

11201157
#[test]
11211158
fn test_property_name_valid() {
1122-
assert!(property_name_valid("abcdef"));
1123-
assert!(property_name_valid("01234"));
1124-
assert!(property_name_valid("azAZ09,._+?#-"));
1125-
assert!(property_name_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
1159+
let f = |x: &[u8]| property_name_valid(CStr::from_bytes_with_nul(x).unwrap());
1160+
assert!(f(b"abcdef\0"));
1161+
assert!(f(b"01234\0"));
1162+
assert!(f(b"azAZ09,._+?#-\0"));
1163+
assert!(f(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0"));
11261164

11271165
// Name contains invalid characters.
1128-
assert!(!property_name_valid("abc!def"));
1129-
assert!(!property_name_valid("abc@1234"));
1166+
assert!(!f(b"abc!def\0"));
1167+
assert!(!f(b"abc@1234\0"));
11301168

11311169
// Name too long.
1132-
assert!(!property_name_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
1170+
assert!(!f(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0"));
11331171
}
11341172

11351173
#[test]

0 commit comments

Comments
 (0)