Skip to main content

tpm2_protocol/basic/
list.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2025 Jarkko Sakkinen
4
5use crate::{
6    TpmCast, TpmCastMut, TpmError, TpmMarshal, TpmResult, TpmSized, TpmUnmarshal, basic::TpmUint32,
7};
8use core::{
9    convert::TryFrom,
10    fmt::Debug,
11    mem::{MaybeUninit, size_of},
12    ops::Deref,
13    slice,
14};
15
16const TPML_COUNT_LEN: usize = size_of::<TpmUint32>();
17
18/// A zero-copy TPML wire view over caller-owned bytes.
19#[repr(transparent)]
20pub struct Tpml<const CAPACITY: usize>([u8]);
21
22impl<const CAPACITY: usize> Tpml<CAPACITY> {
23    /// Casts a byte slice into a TPML wire view.
24    ///
25    /// # Errors
26    ///
27    /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
28    /// `buf` is shorter than the TPML count field.
29    /// Returns [`TooManyItems`](crate::TpmError::TooManyItems) when
30    /// the declared item count exceeds `CAPACITY`.
31    pub fn cast(buf: &[u8]) -> TpmResult<&Self> {
32        Self::validate(buf)?;
33
34        // SAFETY: `validate` checked the TPML header and count limit for this
35        // transparent wire view.
36        Ok(unsafe { Self::cast_unchecked(buf) })
37    }
38
39    /// Casts a byte slice into a TPML wire view without validation.
40    ///
41    /// # Safety
42    ///
43    /// The caller must ensure that `buf` starts with a complete TPML count
44    /// field and that the declared item count does not exceed `CAPACITY`.
45    #[must_use]
46    pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
47        let ptr = core::ptr::from_ref(buf) as *const Self;
48
49        // SAFETY: `Tpml` is `repr(transparent)` over `[u8]`, so it has the
50        // same layout, metadata, and alignment as the referenced slice.
51        unsafe { &*ptr }
52    }
53
54    /// Casts a mutable byte slice into a mutable TPML wire view.
55    ///
56    /// # Errors
57    ///
58    /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
59    /// `buf` is shorter than the TPML count field.
60    /// Returns [`TooManyItems`](crate::TpmError::TooManyItems) when
61    /// the declared item count exceeds `CAPACITY`.
62    pub fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
63        Self::validate(buf)?;
64
65        // SAFETY: `validate` checked the TPML header and count limit for this
66        // transparent wire view. The `&mut` input provides exclusive access.
67        Ok(unsafe { Self::cast_mut_unchecked(buf) })
68    }
69
70    /// Casts a mutable byte slice into a mutable TPML wire view without validation.
71    ///
72    /// # Safety
73    ///
74    /// The caller must ensure that `buf` starts with a complete TPML count
75    /// field and that the declared item count does not exceed `CAPACITY`. The
76    /// returned reference inherits the exclusive access represented by `buf`.
77    #[must_use]
78    pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
79        let ptr = core::ptr::from_mut(buf) as *mut Self;
80
81        // SAFETY: `Tpml` is `repr(transparent)` over `[u8]`, so it has the
82        // same layout, metadata, and alignment as the referenced slice.
83        unsafe { &mut *ptr }
84    }
85
86    /// Returns the complete TPML byte representation.
87    #[must_use]
88    pub const fn as_bytes(&self) -> &[u8] {
89        &self.0
90    }
91
92    /// Returns the complete mutable TPML byte representation.
93    #[must_use]
94    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
95        &mut self.0
96    }
97
98    /// Returns the declared item count.
99    #[must_use]
100    pub fn count(&self) -> usize {
101        Self::read_count(&self.0)
102    }
103
104    /// Returns the bytes after the count field.
105    #[must_use]
106    pub fn items_bytes(&self) -> &[u8] {
107        &self.0[TPML_COUNT_LEN..]
108    }
109
110    /// Returns the mutable bytes after the count field.
111    #[must_use]
112    pub fn items_bytes_mut(&mut self) -> &mut [u8] {
113        &mut self.0[TPML_COUNT_LEN..]
114    }
115
116    /// Returns the complete TPML wire length.
117    #[must_use]
118    pub const fn len(&self) -> usize {
119        self.0.len()
120    }
121
122    /// Returns `true` when the declared item count is zero.
123    #[must_use]
124    pub fn is_empty(&self) -> bool {
125        self.count() == 0
126    }
127
128    fn validate(buf: &[u8]) -> TpmResult<()> {
129        if buf.len() < TPML_COUNT_LEN {
130            return Err(TpmError::UnexpectedEnd(
131                crate::TpmErrorValue::new(0).size(TPML_COUNT_LEN, buf.len()),
132            ));
133        }
134
135        let item_count = Self::read_count(buf);
136        if item_count > CAPACITY {
137            return Err(TpmError::TooManyItems(
138                crate::TpmErrorValue::new(0).limit(CAPACITY, item_count),
139            ));
140        }
141
142        Ok(())
143    }
144
145    fn read_count(buf: &[u8]) -> usize {
146        let raw = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
147
148        raw as usize
149    }
150}
151
152impl<const CAPACITY: usize> TpmCast for Tpml<CAPACITY> {
153    fn cast(buf: &[u8]) -> TpmResult<&Self> {
154        Self::cast(buf)
155    }
156
157    unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
158        // SAFETY: The caller upholds the unchecked cast contract for `Tpml`.
159        unsafe { Self::cast_unchecked(buf) }
160    }
161}
162
163impl<const CAPACITY: usize> TpmCastMut for Tpml<CAPACITY> {
164    fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
165        Self::cast_mut(buf)
166    }
167
168    unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
169        // SAFETY: The caller upholds the unchecked mutable cast contract for
170        // `Tpml`.
171        unsafe { Self::cast_mut_unchecked(buf) }
172    }
173}
174
175impl<const CAPACITY: usize> AsRef<[u8]> for Tpml<CAPACITY> {
176    fn as_ref(&self) -> &[u8] {
177        self.as_bytes()
178    }
179}
180
181impl<const CAPACITY: usize> AsMut<[u8]> for Tpml<CAPACITY> {
182    fn as_mut(&mut self) -> &mut [u8] {
183        self.as_bytes_mut()
184    }
185}
186
187/// A fixed-capacity list for TPM structures, implemented over a fixed-size array.
188#[derive(Clone, Copy)]
189pub struct TpmList<T: Copy, const CAPACITY: usize> {
190    items: [MaybeUninit<T>; CAPACITY],
191    len: usize,
192}
193
194impl<T: Copy, const CAPACITY: usize> TpmList<T, CAPACITY> {
195    /// Creates a new, empty `TpmList`.
196    #[must_use]
197    pub fn new() -> Self {
198        Self {
199            items: [const { MaybeUninit::uninit() }; CAPACITY],
200            len: 0,
201        }
202    }
203
204    /// Returns `true` if the list contains no elements.
205    #[must_use]
206    pub fn is_empty(&self) -> bool {
207        self.len == 0
208    }
209
210    /// Appends an element to the back of the list.
211    ///
212    /// # Errors
213    ///
214    /// Returns [`TooManyItems`](crate::TpmError::TooManyItems) if the list is at
215    /// full capacity.
216    pub fn try_push(&mut self, item: T) -> Result<(), TpmError> {
217        if self.len >= CAPACITY {
218            return Err(TpmError::TooManyItems(
219                crate::TpmErrorValue::new(0).limit(CAPACITY, self.len + 1),
220            ));
221        }
222        self.items[self.len].write(item);
223        self.len += 1;
224        Ok(())
225    }
226
227    /// Appends a slice of elements to the back of the list.
228    ///
229    /// # Errors
230    ///
231    /// Returns [`TooManyItems`](crate::TpmError::TooManyItems) if the list cannot
232    /// fit all elements from the slice.
233    pub fn try_extend_from_slice(&mut self, slice: &[T]) -> Result<(), TpmError> {
234        let new_len = self
235            .len
236            .checked_add(slice.len())
237            .ok_or(TpmError::TooManyItems(
238                crate::TpmErrorValue::new(0).limit(CAPACITY, usize::MAX),
239            ))?;
240
241        if new_len > CAPACITY {
242            return Err(TpmError::TooManyItems(
243                crate::TpmErrorValue::new(0).limit(CAPACITY, new_len),
244            ));
245        }
246
247        for (dest, src) in self.items[self.len..new_len].iter_mut().zip(slice) {
248            dest.write(*src);
249        }
250        self.len = new_len;
251        Ok(())
252    }
253}
254
255impl<T: Copy, const CAPACITY: usize> Deref for TpmList<T, CAPACITY> {
256    type Target = [T];
257
258    fn deref(&self) -> &Self::Target {
259        // SAFETY: The first `self.len` items are initialized by the mutation APIs,
260        // and `MaybeUninit<T>` has the same layout as `T`.
261        unsafe { slice::from_raw_parts(self.items.as_ptr().cast::<T>(), self.len) }
262    }
263}
264
265impl<T: Copy, const CAPACITY: usize> Default for TpmList<T, CAPACITY> {
266    fn default() -> Self {
267        Self::new()
268    }
269}
270
271impl<T: Copy + Debug, const CAPACITY: usize> Debug for TpmList<T, CAPACITY> {
272    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273        f.debug_list().entries(self.iter()).finish()
274    }
275}
276
277impl<T: Copy + PartialEq, const CAPACITY: usize> PartialEq for TpmList<T, CAPACITY> {
278    fn eq(&self, other: &Self) -> bool {
279        **self == **other
280    }
281}
282
283impl<T: Copy + Eq, const CAPACITY: usize> Eq for TpmList<T, CAPACITY> {}
284
285impl<T: TpmSized + Copy, const CAPACITY: usize> TpmSized for TpmList<T, CAPACITY> {
286    const SIZE: usize = size_of::<TpmUint32>() + (T::SIZE * CAPACITY);
287    fn len(&self) -> usize {
288        size_of::<TpmUint32>() + self.iter().map(TpmSized::len).sum::<usize>()
289    }
290}
291
292impl<T: TpmMarshal + Copy, const CAPACITY: usize> TpmMarshal for TpmList<T, CAPACITY> {
293    fn marshal(&self, writer: &mut crate::TpmWriter) -> TpmResult<()> {
294        let len = TpmUint32::try_from(self.len).map_err(|_| {
295            TpmError::IntegerTooLarge(crate::TpmErrorValue::new(0).value_usize(self.len))
296        })?;
297        TpmMarshal::marshal(&len, writer)?;
298        for item in &**self {
299            TpmMarshal::marshal(item, writer)?;
300        }
301        Ok(())
302    }
303}
304
305impl<T: TpmUnmarshal + Copy, const CAPACITY: usize> TpmUnmarshal for TpmList<T, CAPACITY> {
306    fn unmarshal(buf: &[u8]) -> TpmResult<(Self, &[u8])> {
307        let (count_u32, mut buf) = TpmUint32::unmarshal(buf)?;
308        let count = u32::from(count_u32) as usize;
309        if count > CAPACITY {
310            return Err(TpmError::TooManyItems(
311                crate::TpmErrorValue::new(0).limit(CAPACITY, count),
312            ));
313        }
314
315        let mut list = Self::new();
316        for _ in 0..count {
317            let (item, rest) = T::unmarshal(buf)?;
318            list.try_push(item)?;
319            buf = rest;
320        }
321
322        Ok((list, buf))
323    }
324}