tpm2_protocol/
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::{TpmBuild, TpmErrorKind, TpmParse, TpmResult, TpmSized};
6use core::{
7    convert::TryFrom,
8    fmt::Debug,
9    mem::{size_of, MaybeUninit},
10    ops::Deref,
11    slice,
12};
13
14/// A fixed-capacity list for TPM structures, implemented over a fixed-size array.
15#[derive(Clone, Copy)]
16pub struct TpmList<T: Copy, const CAPACITY: usize> {
17    items: [MaybeUninit<T>; CAPACITY],
18    len: usize,
19}
20
21impl<T: Copy, const CAPACITY: usize> TpmList<T, CAPACITY> {
22    /// Creates a new, empty `TpmList`.
23    ///
24    /// # Safety
25    ///
26    /// This function uses `unsafe` to create an uninitialized array of
27    /// `MaybeUninit<T>`. This is a standard and safe pattern as `MaybeUninit`
28    /// does not require its contents to be valid.
29    #[allow(unsafe_code)]
30    #[must_use]
31    pub fn new() -> Self {
32        Self {
33            items: unsafe { MaybeUninit::uninit().assume_init() },
34            len: 0,
35        }
36    }
37
38    /// Returns `true` if the list contains no elements.
39    #[must_use]
40    pub fn is_empty(&self) -> bool {
41        self.len == 0
42    }
43
44    /// Appends an element to the back of the list.
45    ///
46    /// # Errors
47    ///
48    /// Returns a `TpmErrorKind::CapacityExceeded` error if the list is already at
49    /// full capacity.
50    pub fn try_push(&mut self, item: T) -> Result<(), TpmErrorKind> {
51        if self.len >= CAPACITY {
52            return Err(TpmErrorKind::CapacityExceeded);
53        }
54        self.items[self.len].write(item);
55        self.len += 1;
56        Ok(())
57    }
58}
59
60#[allow(unsafe_code)]
61impl<T: Copy, const CAPACITY: usize> Deref for TpmList<T, CAPACITY> {
62    type Target = [T];
63
64    /// # Safety
65    ///
66    /// This implementation uses `unsafe` to provide a view into the initialized
67    /// portion of the list. The caller can rely on this being safe because:
68    /// 1. The first `self.len` items are guaranteed to be initialized by the `try_push` method.
69    /// 2. `MaybeUninit<T>` is guaranteed to have the same memory layout as `T`.
70    fn deref(&self) -> &Self::Target {
71        unsafe { slice::from_raw_parts(self.items.as_ptr().cast::<T>(), self.len) }
72    }
73}
74
75impl<T: Copy, const CAPACITY: usize> Default for TpmList<T, CAPACITY> {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81impl<T: Copy + Debug, const CAPACITY: usize> Debug for TpmList<T, CAPACITY> {
82    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        f.debug_list().entries(self.iter()).finish()
84    }
85}
86
87impl<T: Copy + PartialEq, const CAPACITY: usize> PartialEq for TpmList<T, CAPACITY> {
88    fn eq(&self, other: &Self) -> bool {
89        **self == **other
90    }
91}
92
93impl<T: Copy + Eq, const CAPACITY: usize> Eq for TpmList<T, CAPACITY> {}
94
95impl<T: TpmSized + Copy, const CAPACITY: usize> TpmSized for TpmList<T, CAPACITY> {
96    const SIZE: usize = size_of::<u32>() + (T::SIZE * CAPACITY);
97    fn len(&self) -> usize {
98        size_of::<u32>() + self.iter().map(TpmSized::len).sum::<usize>()
99    }
100}
101
102impl<T: TpmBuild + Copy, const CAPACITY: usize> TpmBuild for TpmList<T, CAPACITY> {
103    fn build(&self, writer: &mut crate::TpmWriter) -> TpmResult<()> {
104        let len_u32 = u32::try_from(self.len).map_err(|_| TpmErrorKind::ValueTooLarge)?;
105        TpmBuild::build(&len_u32, writer)?;
106        for item in &**self {
107            TpmBuild::build(item, writer)?;
108        }
109        Ok(())
110    }
111}
112
113impl<T: TpmParse + Copy, const CAPACITY: usize> TpmParse for TpmList<T, CAPACITY> {
114    fn parse(buf: &[u8]) -> TpmResult<(Self, &[u8])> {
115        let (count_u32, mut buf) = u32::parse(buf)?;
116        let count = count_u32 as usize;
117        if count > CAPACITY {
118            return Err(TpmErrorKind::ValueTooLarge);
119        }
120
121        let mut list = Self::new();
122        for _ in 0..count {
123            let (item, rest) = T::parse(buf)?;
124            list.try_push(item).map_err(|_| TpmErrorKind::Unreachable)?;
125            buf = rest;
126        }
127
128        Ok((list, buf))
129    }
130}