| // Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| use crate::structures::Header; | |
| use zx_status::Status; | |
| /// Utility for working with the table of null-terminated strings after each SMBIOS structure. | |
| #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] | |
| pub struct StringTable<'a> { | |
| data: &'a [u8], | |
| } | |
| impl<'a> StringTable<'a> { | |
| /// Creates an empty `StringTable`. | |
| pub const fn empty() -> Self { | |
| Self { data: &[] } | |
| } | |
| /// Initializes a `StringTable` from a structure header and the buffer containing the | |
| /// structure data (both header and trailing strings) bounded by `max_struct_len`. | |
| pub fn init(hdr: &Header, max_struct_len: usize, raw_data: &'a [u8]) -> Result<Self, Status> { | |
| let hdr_len = hdr.length as usize; | |
| if hdr_len > max_struct_len || hdr_len > raw_data.len() { | |
| return Err(Status::IO_DATA_INTEGRITY); | |
| } | |
| let available_len = core::cmp::min(max_struct_len, raw_data.len()) - hdr_len; | |
| // Make sure the table is big enough to include the two trailing NULs | |
| if available_len < 2 { | |
| return Err(Status::IO_DATA_INTEGRITY); | |
| } | |
| let string_bytes = &raw_data[hdr_len..hdr_len + available_len]; | |
| // Check if the string table is empty | |
| if string_bytes[0] == 0 && string_bytes[1] == 0 { | |
| return Ok(Self { data: &string_bytes[..2] }); | |
| } | |
| let start_idx = if string_bytes[0] == 0 { 1 } else { 0 }; | |
| let mut i = start_idx; | |
| while i < available_len { | |
| let remaining = &string_bytes[i..]; | |
| let len = remaining.iter().position(|&b| b == 0).unwrap_or(remaining.len()); | |
| if len == 0 { | |
| let table_len = i + 1; // Include the trailing null | |
| return Ok(Self { data: &string_bytes[..table_len] }); | |
| } | |
| // strnlen returns the length not including the NUL. Note that if | |
| // no NUL was found, it returns remaining.len(), which will exceed | |
| // available_len when incremented by len + 1. | |
| i += len + 1; | |
| } | |
| Err(Status::IO_DATA_INTEGRITY) | |
| } | |
| /// Returns the length of the `StringTable` in bytes, including terminating null bytes. | |
| pub fn length(&self) -> usize { | |
| self.data.len() | |
| } | |
| /// Returns true if the string table contains no string entries. | |
| pub fn is_empty(&self) -> bool { | |
| self.data.is_empty() || (self.data.len() == 2 && self.data[0] == 0 && self.data[1] == 0) | |
| } | |
| /// Retrieves the null-terminated C string at 1-based index `idx`. | |
| /// | |
| /// If `idx == 0`, returns `Ok(c"<null>")` indicating an unassigned string field. | |
| /// Returns `Err(Status::NOT_FOUND)` if `idx` exceeds the available strings in the table. | |
| /// Returns `Err(Status::IO_DATA_INTEGRITY)` on corrupt data. | |
| pub fn get_c_str(&self, mut idx: usize) -> Result<&'a core::ffi::CStr, Status> { | |
| if idx == 0 { | |
| return Ok(c"<null>"); | |
| } | |
| let mut i = 0; | |
| while i < self.data.len() { | |
| let remaining = &self.data[i..]; | |
| let Some(len) = remaining.iter().position(|&b| b == 0) else { | |
| return Err(Status::IO_DATA_INTEGRITY); | |
| }; | |
| if len == 0 { | |
| if i != 0 { | |
| return Err(Status::NOT_FOUND); | |
| } | |
| if self.data.len() - i < 2 { | |
| return Err(Status::IO_DATA_INTEGRITY); | |
| } | |
| if self.data[i + 1] == 0 { | |
| return Err(Status::NOT_FOUND); | |
| } | |
| } | |
| if idx == 1 { | |
| return core::ffi::CStr::from_bytes_with_nul(&self.data[i..=i + len]) | |
| .map_err(|_| Status::IO_DATA_INTEGRITY); | |
| } | |
| idx -= 1; | |
| i += len + 1; | |
| } | |
| Err(Status::NOT_FOUND) | |
| } | |
| /// Convenience version of `get_c_str` that returns `c"<missing string>"` on error, | |
| /// matching C++ `GetString(size_t idx, const char** out)`. | |
| pub fn get_c_str_or_default(&self, idx: usize) -> &'a core::ffi::CStr { | |
| self.get_c_str(idx).unwrap_or(c"<missing string>") | |
| } | |
| /// Retrieves the UTF-8 string at 1-based index `idx`. | |
| /// | |
| /// If `idx == 0`, returns `Ok("<null>")` indicating an unassigned string field. | |
| /// Returns `Err(Status::NOT_FOUND)` if `idx` exceeds the available strings in the table. | |
| /// Returns `Err(Status::IO_DATA_INTEGRITY)` on corrupt data or invalid UTF-8. | |
| pub fn get_string(&self, idx: usize) -> Result<&'a str, Status> { | |
| self.get_c_str(idx)?.to_str().map_err(|_| Status::IO_DATA_INTEGRITY) | |
| } | |
| /// Convenience version of `get_string` that returns `"<missing string>"` on error, | |
| /// matching C++ `GetString(size_t idx)`. | |
| pub fn get_string_or_default(&self, idx: usize) -> &'a str { | |
| self.get_string(idx).unwrap_or("<missing string>") | |
| } | |
| /// Dumps the string table contents to the provided writer. | |
| pub fn dump<W: core::fmt::Write>(&self, writer: &mut W) -> core::fmt::Result { | |
| let mut i = 1; | |
| while let Ok(str_val) = self.get_string(i) { | |
| writeln!(writer, " str {}: {}", i, str_val)?; | |
| i += 1; | |
| } | |
| Ok(()) | |
| } | |
| } |