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::{TpmCast, TpmCastMut, TpmError, TpmMarshal, TpmResult, TpmSized, basic::TpmUint32};
6use core::{
7    convert::TryFrom,
8    fmt::Debug,
9    marker::PhantomData,
10    mem::{MaybeUninit, size_of},
11    ops::Deref,
12    slice,
13};
14
15const TPML_COUNT_LEN: usize = size_of::<TpmUint32>();
16
17/// A zero-copy TPML wire view over caller-owned bytes.
18#[repr(transparent)]
19pub struct Tpml<const CAPACITY: usize>([u8]);
20
21impl<const CAPACITY: usize> Tpml<CAPACITY> {
22    /// Casts a byte slice into a TPML wire view.
23    ///
24    /// # Errors
25    ///
26    /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
27    /// `buf` is shorter than the TPML count field.
28    /// Returns [`TooManyItems`](crate::TpmError::TooManyItems) when
29    /// the declared item count exceeds `CAPACITY`.
30    pub fn cast(buf: &[u8]) -> TpmResult<&Self> {
31        Self::validate(buf)?;
32
33        // SAFETY: `validate` checked the TPML header and count limit for this
34        // transparent wire view.
35        Ok(unsafe { Self::cast_unchecked(buf) })
36    }
37
38    /// Casts a byte slice into a typed TPML wire view.
39    ///
40    /// # Errors
41    ///
42    /// Returns `Err(TpmError)` when the TPML header is malformed, the declared
43    /// count exceeds `CAPACITY`, or the typed item area is malformed.
44    pub fn cast_items<T: TpmCast + ?Sized>(buf: &[u8]) -> TpmResult<&Self> {
45        Self::validate_items::<T>(buf)?;
46
47        // SAFETY: `validate_items` checked the TPML header, count limit, and
48        // typed item boundaries for this transparent wire view.
49        Ok(unsafe { Self::cast_unchecked(buf) })
50    }
51
52    /// Casts the first typed TPML wire value in a byte slice into a wire view.
53    ///
54    /// # Errors
55    ///
56    /// Returns `Err(TpmError)` when the first typed TPML value is malformed.
57    pub fn cast_prefix_items<T: TpmCast + ?Sized>(buf: &[u8]) -> TpmResult<(&Self, &[u8])> {
58        let wire_len = Self::validate_prefix_items::<T>(buf)?;
59        if buf.len() < wire_len {
60            return Err(TpmError::UnexpectedEnd {
61                offset: 0,
62                needed: wire_len,
63                available: buf.len(),
64            });
65        }
66
67        let (head, tail) = buf.split_at(wire_len);
68
69        // SAFETY: `validate_prefix_items` checked the TPML header, count limit,
70        // and typed item boundaries for `head`.
71        Ok((unsafe { Self::cast_unchecked(head) }, tail))
72    }
73
74    /// Casts a mutable byte slice into a mutable TPML wire view.
75    ///
76    /// # Errors
77    ///
78    /// Returns [`UnexpectedEnd`](crate::TpmError::UnexpectedEnd) when
79    /// `buf` is shorter than the TPML count field.
80    /// Returns [`TooManyItems`](crate::TpmError::TooManyItems) when
81    /// the declared item count exceeds `CAPACITY`.
82    pub fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
83        Self::validate(buf)?;
84
85        // SAFETY: `validate` checked the TPML header and count limit for this
86        // transparent wire view. The `&mut` input provides exclusive access.
87        Ok(unsafe { Self::cast_mut_unchecked(buf) })
88    }
89
90    /// Casts a mutable byte slice into a typed TPML wire view.
91    ///
92    /// # Errors
93    ///
94    /// Returns `Err(TpmError)` when the TPML header is malformed, the declared
95    /// count exceeds `CAPACITY`, or the typed item area is malformed.
96    pub fn cast_items_mut<T: TpmCast + ?Sized>(buf: &mut [u8]) -> TpmResult<&mut Self> {
97        Self::validate_items::<T>(buf)?;
98
99        // SAFETY: `validate_items` checked the TPML header, count limit, and
100        // typed item boundaries for this transparent wire view.
101        Ok(unsafe { Self::cast_mut_unchecked(buf) })
102    }
103
104    /// Casts the first mutable typed TPML wire value in a byte slice into a wire view.
105    ///
106    /// # Errors
107    ///
108    /// Returns `Err(TpmError)` when the first typed TPML value is malformed.
109    pub fn cast_prefix_items_mut<T: TpmCast + ?Sized>(
110        buf: &mut [u8],
111    ) -> TpmResult<(&mut Self, &mut [u8])> {
112        let wire_len = Self::validate_prefix_items::<T>(buf)?;
113        if buf.len() < wire_len {
114            return Err(TpmError::UnexpectedEnd {
115                offset: 0,
116                needed: wire_len,
117                available: buf.len(),
118            });
119        }
120
121        let (head, tail) = buf.split_at_mut(wire_len);
122
123        // SAFETY: `validate_prefix_items` checked the TPML header, count limit,
124        // and typed item boundaries for `head`.
125        Ok((unsafe { Self::cast_mut_unchecked(head) }, tail))
126    }
127
128    /// Returns the complete TPML byte representation.
129    #[must_use]
130    pub const fn as_bytes(&self) -> &[u8] {
131        &self.0
132    }
133
134    /// Returns the complete mutable TPML byte representation.
135    #[must_use]
136    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
137        &mut self.0
138    }
139
140    /// Returns the declared item count.
141    #[must_use]
142    pub fn count(&self) -> usize {
143        Self::read_count(&self.0)
144    }
145
146    /// Returns the bytes after the count field.
147    #[must_use]
148    pub fn items_bytes(&self) -> &[u8] {
149        &self.0[TPML_COUNT_LEN..]
150    }
151
152    /// Returns an iterator over typed borrowed items.
153    #[must_use]
154    pub fn items<T: TpmCast + ?Sized>(&self) -> TpmlIter<'_, T> {
155        TpmlIter {
156            buf: self.items_bytes(),
157            remaining: self.count(),
158            _marker: PhantomData,
159        }
160    }
161
162    /// Returns the mutable bytes after the count field.
163    #[must_use]
164    pub fn items_bytes_mut(&mut self) -> &mut [u8] {
165        &mut self.0[TPML_COUNT_LEN..]
166    }
167
168    /// Returns the complete TPML wire length.
169    #[must_use]
170    pub const fn len(&self) -> usize {
171        self.0.len()
172    }
173
174    /// Returns `true` when the declared item count is zero.
175    #[must_use]
176    pub fn is_empty(&self) -> bool {
177        self.count() == 0
178    }
179
180    /// Validates a TPML header and declared count.
181    ///
182    /// # Errors
183    ///
184    /// Returns `Err(TpmError)` when the count field is missing or exceeds
185    /// `CAPACITY`.
186    pub fn validate(buf: &[u8]) -> TpmResult<()> {
187        Self::validate_header(buf).map(|_| ())
188    }
189
190    /// Validates a typed TPML wire value.
191    ///
192    /// # Errors
193    ///
194    /// Returns `Err(TpmError)` when the TPML header or typed item area is malformed.
195    pub fn validate_items<T: TpmCast + ?Sized>(buf: &[u8]) -> TpmResult<()> {
196        let wire_len = Self::validate_prefix_items::<T>(buf)?;
197
198        if buf.len() > wire_len {
199            return Err(TpmError::TrailingData {
200                offset: wire_len,
201                actual: buf.len() - wire_len,
202            });
203        }
204
205        Ok(())
206    }
207
208    /// Validates the first typed TPML wire value and returns its wire length.
209    ///
210    /// # Errors
211    ///
212    /// Returns `Err(TpmError)` when the TPML header or typed item area is malformed.
213    pub fn validate_prefix_items<T: TpmCast + ?Sized>(buf: &[u8]) -> TpmResult<usize> {
214        let count = Self::validate_header(buf)?;
215        let mut cursor = &buf[TPML_COUNT_LEN..];
216        let mut consumed = TPML_COUNT_LEN;
217
218        for _ in 0..count {
219            let before = cursor.len();
220            let (_, tail) = T::cast_prefix(cursor)?;
221            let item_len = before
222                .checked_sub(tail.len())
223                .ok_or(TpmError::IntegerTooLarge {
224                    offset: consumed,
225                    value: crate::tpm_value(tail.len()),
226                })?;
227            consumed = consumed
228                .checked_add(item_len)
229                .ok_or(TpmError::IntegerTooLarge {
230                    offset: consumed,
231                    value: crate::tpm_value(before),
232                })?;
233            cursor = tail;
234        }
235
236        Ok(consumed)
237    }
238
239    fn validate_header(buf: &[u8]) -> TpmResult<usize> {
240        if buf.len() < TPML_COUNT_LEN {
241            return Err(TpmError::UnexpectedEnd {
242                offset: 0,
243                needed: TPML_COUNT_LEN,
244                available: buf.len(),
245            });
246        }
247
248        let item_count = Self::read_count(buf);
249        if item_count > CAPACITY {
250            return Err(TpmError::TooManyItems {
251                offset: 0,
252                limit: CAPACITY,
253                actual: item_count,
254            });
255        }
256
257        Ok(item_count)
258    }
259
260    fn read_count(buf: &[u8]) -> usize {
261        let raw = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
262
263        raw as usize
264    }
265}
266
267/// Borrowed iterator over a typed TPML item area.
268pub struct TpmlIter<'a, T: TpmCast + ?Sized> {
269    buf: &'a [u8],
270    remaining: usize,
271    _marker: PhantomData<&'a T>,
272}
273
274impl<'a, T: TpmCast + ?Sized> Iterator for TpmlIter<'a, T> {
275    type Item = TpmResult<&'a T>;
276
277    fn next(&mut self) -> Option<Self::Item> {
278        if self.remaining == 0 {
279            return None;
280        }
281
282        self.remaining -= 1;
283
284        match T::cast_prefix(self.buf) {
285            Ok((item, tail)) => {
286                self.buf = tail;
287                Some(Ok(item))
288            }
289            Err(err) => {
290                self.buf = &[];
291                self.remaining = 0;
292                Some(Err(err))
293            }
294        }
295    }
296}
297
298impl<const CAPACITY: usize> TpmCast for Tpml<CAPACITY> {
299    fn cast(buf: &[u8]) -> TpmResult<&Self> {
300        Self::cast(buf)
301    }
302
303    unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
304        // SAFETY: The caller upholds the unchecked cast contract for `Tpml`.
305        unsafe { Self::cast_unchecked(buf) }
306    }
307}
308
309impl<const CAPACITY: usize> TpmCastMut for Tpml<CAPACITY> {
310    fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
311        Self::cast_mut(buf)
312    }
313
314    unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
315        // SAFETY: The caller upholds the unchecked mutable cast contract for
316        // `Tpml`.
317        unsafe { Self::cast_mut_unchecked(buf) }
318    }
319}
320
321impl<'a, T: crate::TpmField<'a> + Copy, const CAPACITY: usize> crate::TpmField<'a>
322    for TpmList<T, CAPACITY>
323{
324    type View = &'a Tpml<CAPACITY>;
325
326    fn cast_prefix_field(buf: &'a [u8]) -> TpmResult<(Self::View, &'a [u8])> {
327        let count = Tpml::<CAPACITY>::validate_header(buf)?;
328        let mut cursor = &buf[TPML_COUNT_LEN..];
329        let mut consumed = TPML_COUNT_LEN;
330
331        for _ in 0..count {
332            let before = cursor.len();
333            let (_, tail) = T::cast_prefix_field(cursor)?;
334            let item_len = before
335                .checked_sub(tail.len())
336                .ok_or(TpmError::IntegerTooLarge {
337                    offset: consumed,
338                    value: crate::tpm_value(tail.len()),
339                })?;
340            consumed = consumed
341                .checked_add(item_len)
342                .ok_or(TpmError::IntegerTooLarge {
343                    offset: consumed,
344                    value: crate::tpm_value(before),
345                })?;
346            cursor = tail;
347        }
348
349        if buf.len() < consumed {
350            return Err(TpmError::UnexpectedEnd {
351                offset: 0,
352                needed: consumed,
353                available: buf.len(),
354            });
355        }
356
357        let (head, tail) = buf.split_at(consumed);
358
359        // SAFETY: The loop above checked the TPML count and all item boundaries.
360        Ok((unsafe { Tpml::<CAPACITY>::cast_unchecked(head) }, tail))
361    }
362}
363
364crate::tpm_byte_view!(Tpml<const CAPACITY: usize>);
365
366/// A fixed-capacity list for TPM structures, implemented over a fixed-size array.
367#[derive(Clone, Copy)]
368pub struct TpmList<T: Copy, const CAPACITY: usize> {
369    items: [MaybeUninit<T>; CAPACITY],
370    len: usize,
371}
372
373impl<T: Copy, const CAPACITY: usize> TpmList<T, CAPACITY> {
374    /// Creates a new, empty `TpmList`.
375    #[must_use]
376    pub fn new() -> Self {
377        Self {
378            items: [const { MaybeUninit::uninit() }; CAPACITY],
379            len: 0,
380        }
381    }
382
383    /// Returns `true` if the list contains no elements.
384    #[must_use]
385    pub fn is_empty(&self) -> bool {
386        self.len == 0
387    }
388
389    /// Appends an element to the back of the list.
390    ///
391    /// # Errors
392    ///
393    /// Returns [`TooManyItems`](crate::TpmError::TooManyItems) if the list is at
394    /// full capacity.
395    pub fn try_push(&mut self, item: T) -> Result<(), TpmError> {
396        if self.len >= CAPACITY {
397            return Err(TpmError::TooManyItems {
398                offset: 0,
399                limit: CAPACITY,
400                actual: self.len + 1,
401            });
402        }
403        self.items[self.len].write(item);
404        self.len += 1;
405        Ok(())
406    }
407
408    /// Appends a slice of elements to the back of the list.
409    ///
410    /// # Errors
411    ///
412    /// Returns [`TooManyItems`](crate::TpmError::TooManyItems) if the list cannot
413    /// fit all elements from the slice.
414    pub fn try_extend_from_slice(&mut self, slice: &[T]) -> Result<(), TpmError> {
415        let new_len = self
416            .len
417            .checked_add(slice.len())
418            .ok_or(TpmError::TooManyItems {
419                offset: 0,
420                limit: CAPACITY,
421                actual: usize::MAX,
422            })?;
423
424        if new_len > CAPACITY {
425            return Err(TpmError::TooManyItems {
426                offset: 0,
427                limit: CAPACITY,
428                actual: new_len,
429            });
430        }
431
432        for (dest, src) in self.items[self.len..new_len].iter_mut().zip(slice) {
433            dest.write(*src);
434        }
435        self.len = new_len;
436        Ok(())
437    }
438}
439
440impl<T: Copy, const CAPACITY: usize> Deref for TpmList<T, CAPACITY> {
441    type Target = [T];
442
443    fn deref(&self) -> &Self::Target {
444        // SAFETY: The first `self.len` items are initialized by the mutation APIs,
445        // and `MaybeUninit<T>` has the same layout as `T`.
446        unsafe { slice::from_raw_parts(self.items.as_ptr().cast::<T>(), self.len) }
447    }
448}
449
450impl<T: Copy, const CAPACITY: usize> Default for TpmList<T, CAPACITY> {
451    fn default() -> Self {
452        Self::new()
453    }
454}
455
456impl<T: Copy + Debug, const CAPACITY: usize> Debug for TpmList<T, CAPACITY> {
457    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
458        f.debug_list().entries(self.iter()).finish()
459    }
460}
461
462impl<T: Copy + PartialEq, const CAPACITY: usize> PartialEq for TpmList<T, CAPACITY> {
463    fn eq(&self, other: &Self) -> bool {
464        **self == **other
465    }
466}
467
468impl<T: Copy + Eq, const CAPACITY: usize> Eq for TpmList<T, CAPACITY> {}
469
470impl<T: TpmSized + Copy, const CAPACITY: usize> TpmSized for TpmList<T, CAPACITY> {
471    const SIZE: usize = size_of::<TpmUint32>() + (T::SIZE * CAPACITY);
472    fn len(&self) -> usize {
473        size_of::<TpmUint32>() + self.iter().map(TpmSized::len).sum::<usize>()
474    }
475}
476
477impl<T: TpmMarshal + Copy, const CAPACITY: usize> TpmMarshal for TpmList<T, CAPACITY> {
478    fn marshal(&self, writer: &mut crate::TpmWriter) -> TpmResult<()> {
479        let len = TpmUint32::try_from(self.len).map_err(|_| TpmError::IntegerTooLarge {
480            offset: 0,
481            value: crate::tpm_value(self.len),
482        })?;
483        TpmMarshal::marshal(&len, writer)?;
484        for item in &**self {
485            TpmMarshal::marshal(item, writer)?;
486        }
487        Ok(())
488    }
489}