Skip to main content

fbl/
name.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7use ksync::{KCell, KMutex, guarded, lock};
8use pin_init::{PinInit, pin_init};
9
10pub type NameLock = ksync::RawSpinlock;
11
12/// A fixed-size, thread-safe name buffer with automatic truncation and null-termination.
13///
14/// Names include the trailing null byte as part of their `SIZE`-sized buffer.
15/// Constructors and setters automatically truncate inputs to `SIZE - 1` bytes to ensure
16/// the string is always null-terminated.
17///
18/// Corresponds to `fbl::Name<Size>` in C++.
19#[guarded]
20#[repr(C)]
21pub struct Name<const SIZE: usize> {
22    #[mutex]
23    lock: KMutex<NameLock>,
24    #[guarded_by(lock)]
25    name: [u8; SIZE],
26}
27
28impl<const SIZE: usize> Name<SIZE> {
29    /// Asserts that `SIZE >= 1` at compile time.
30    const CHECK_SIZE: () = assert!(SIZE >= 1, "Names must have SIZE >= 1");
31
32    /// Copies `name` into `dst`, truncating at the first null byte or `SIZE - 1`
33    /// (whichever comes first) and zeroing the remainder of `dst`.
34    fn copy_and_truncate(dst: &mut [u8; SIZE], name: &[u8]) {
35        let nul_idx = name.iter().position(|&b| b == 0).unwrap_or(name.len());
36        let len = nul_idx.min(SIZE - 1);
37        dst[..len].copy_from_slice(&name[..len]);
38        dst[len..].fill(0);
39    }
40
41    /// Creates a `PinInit` initializer for `Name` initialized to an empty string.
42    ///
43    /// Initializes storage directly in-place without stack copies.
44    pub fn init() -> impl PinInit<Self, core::convert::Infallible> {
45        let () = Self::CHECK_SIZE;
46        pin_init!(Self {
47            lock <- KMutex::init(),
48            name: KCell::new([0u8; SIZE]),
49        })
50    }
51
52    /// Returns the length of the string (excluding the null terminator).
53    pub fn len(&self) -> usize {
54        lock!(let guard = self.lock_lock());
55        let fields = guard.fields();
56        fields.name.iter().position(|&b| b == 0).unwrap_or(SIZE - 1)
57    }
58
59    /// Returns true if the string is empty.
60    pub fn is_empty(&self) -> bool {
61        self.len() == 0
62    }
63
64    /// Copies the name out into `out_name`.
65    ///
66    /// `out_name` is zeroed first. If `out_name` is non-empty, up to
67    /// `min(out_name.len() - 1, SIZE - 1)` characters are copied up to the first null byte.
68    /// If `out_name` is empty, no data is written.
69    pub fn get(&self, out_name: &mut [u8]) {
70        out_name.fill(0);
71        let Some((_nul, dst)) = out_name.split_last_mut() else {
72            return;
73        };
74        lock!(let guard = self.lock_lock());
75        let fields = guard.fields();
76        let nul_idx = fields.name.iter().position(|&b| b == 0).unwrap_or(SIZE - 1);
77        let copy_len = dst.len().min(nul_idx);
78        dst[..copy_len].copy_from_slice(&fields.name[..copy_len]);
79    }
80
81    /// Resets the `Name` to the given data under lock.
82    ///
83    /// Any characters after the first null byte are ignored. If `name` is longer than
84    /// `SIZE - 1`, it will be truncated to ensure null-termination. The remainder of the
85    /// internal buffer is filled with null bytes.
86    pub fn set(&self, name: &[u8]) {
87        lock!(let guard = self.lock_lock());
88        let fields = guard.fields_mut();
89        Self::copy_and_truncate(fields.name, name);
90    }
91
92    /// Resets the `Name` to the given string slice under lock.
93    pub fn set_str(&self, name: &str) {
94        self.set(name.as_bytes())
95    }
96
97    /// Copies the internal name into a newly stack-allocated buffer of size `SIZE`.
98    pub fn copy_name(&self) -> [u8; SIZE] {
99        let mut buf = [0u8; SIZE];
100        self.get(&mut buf);
101        buf
102    }
103
104    /// Copies the contents of `other` into `self`.
105    pub fn copy_from(&self, other: &Self) {
106        if !core::ptr::eq(self, other) {
107            let mut buf = [0u8; SIZE];
108            other.get(&mut buf);
109            self.set(&buf);
110        }
111    }
112}
113
114impl<const SIZE: usize> core::fmt::Debug for Name<SIZE> {
115    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116        let mut buf = [0u8; SIZE];
117        self.get(&mut buf);
118        let nul_idx = buf.iter().position(|&b| b == 0).unwrap_or(SIZE);
119        let s = core::str::from_utf8(&buf[..nul_idx]).unwrap_or("<invalid utf-8>");
120        f.debug_tuple("Name").field(&s).finish()
121    }
122}