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