Skip to main content

smbios/
string_table.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::structures::Header;
6use zx_status::Status;
7
8/// Utility for working with the table of null-terminated strings after each SMBIOS structure.
9#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
10pub struct StringTable<'a> {
11    data: &'a [u8],
12}
13
14impl<'a> StringTable<'a> {
15    /// Creates an empty `StringTable`.
16    pub const fn empty() -> Self {
17        Self { data: &[] }
18    }
19
20    /// Initializes a `StringTable` from a structure header and the buffer containing the
21    /// structure data (both header and trailing strings) bounded by `max_struct_len`.
22    pub fn init(hdr: &Header, max_struct_len: usize, raw_data: &'a [u8]) -> Result<Self, Status> {
23        let hdr_len = hdr.length as usize;
24        if hdr_len > max_struct_len || hdr_len > raw_data.len() {
25            return Err(Status::IO_DATA_INTEGRITY);
26        }
27
28        let available_len = core::cmp::min(max_struct_len, raw_data.len()) - hdr_len;
29        // Make sure the table is big enough to include the two trailing NULs
30        if available_len < 2 {
31            return Err(Status::IO_DATA_INTEGRITY);
32        }
33
34        let string_bytes = &raw_data[hdr_len..hdr_len + available_len];
35
36        // Check if the string table is empty
37        if string_bytes[0] == 0 && string_bytes[1] == 0 {
38            return Ok(Self { data: &string_bytes[..2] });
39        }
40
41        let start_idx = if string_bytes[0] == 0 { 1 } else { 0 };
42        let mut i = start_idx;
43
44        while i < available_len {
45            let remaining = &string_bytes[i..];
46            let len = remaining.iter().position(|&b| b == 0).unwrap_or(remaining.len());
47
48            if len == 0 {
49                let table_len = i + 1; // Include the trailing null
50                return Ok(Self { data: &string_bytes[..table_len] });
51            }
52
53            // strnlen returns the length not including the NUL. Note that if
54            // no NUL was found, it returns remaining.len(), which will exceed
55            // available_len when incremented by len + 1.
56            i += len + 1;
57        }
58
59        Err(Status::IO_DATA_INTEGRITY)
60    }
61
62    /// Returns the length of the `StringTable` in bytes, including terminating null bytes.
63    pub fn length(&self) -> usize {
64        self.data.len()
65    }
66
67    /// Returns true if the string table contains no string entries.
68    pub fn is_empty(&self) -> bool {
69        self.data.is_empty() || (self.data.len() == 2 && self.data[0] == 0 && self.data[1] == 0)
70    }
71
72    /// Retrieves the string at 1-based index `idx`.
73    ///
74    /// If `idx == 0`, returns `Ok("<null>")` indicating an unassigned string field.
75    /// Returns `Err(Status::NOT_FOUND)` if `idx` exceeds the available strings in the table.
76    /// Returns `Err(Status::IO_DATA_INTEGRITY)` on corrupt data or invalid UTF-8.
77    pub fn get_string(&self, mut idx: usize) -> Result<&str, Status> {
78        if idx == 0 {
79            return Ok("<null>");
80        }
81
82        let mut i = 0;
83        while i < self.data.len() {
84            let remaining = &self.data[i..];
85            let len = remaining.iter().position(|&b| b == 0).unwrap_or(remaining.len());
86
87            if len == 0 {
88                if i != 0 {
89                    return Err(Status::NOT_FOUND);
90                }
91
92                if self.data.len() - i < 2 {
93                    return Err(Status::IO_DATA_INTEGRITY);
94                }
95                if self.data[i + 1] == 0 {
96                    return Err(Status::NOT_FOUND);
97                }
98            }
99
100            if idx == 1 {
101                let str_bytes = &self.data[i..i + len];
102                return core::str::from_utf8(str_bytes).map_err(|_| Status::IO_DATA_INTEGRITY);
103            }
104
105            idx -= 1;
106            i += len + 1;
107        }
108
109        Err(Status::NOT_FOUND)
110    }
111
112    /// Convenience version of `get_string` that returns `"<missing string>"` on error,
113    /// matching C++ `GetString(size_t idx)`.
114    pub fn get_string_or_default(&self, idx: usize) -> &str {
115        self.get_string(idx).unwrap_or("<missing string>")
116    }
117
118    /// Dumps the string table contents to the provided writer.
119    pub fn dump<W: core::fmt::Write>(&self, writer: &mut W) -> core::fmt::Result {
120        let mut i = 1;
121        while let Ok(str_val) = self.get_string(i) {
122            writeln!(writer, "  str {}: {}", i, str_val)?;
123            i += 1;
124        }
125        Ok(())
126    }
127}