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