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