blob: 5daac3ad4b26f404ea16059190b4dcae2d9e892c [file] [log] [blame]
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2025 Opinsys Oy
// Copyright (c) 2024-2025 Jarkko Sakkinen
use crate::{TpmBuild, TpmErrorKind, TpmParse, TpmResult, TpmSized};
use core::{
convert::TryFrom,
fmt::Debug,
mem::{size_of, MaybeUninit},
ops::Deref,
slice,
};
/// A fixed-capacity list for TPM structures, implemented over a fixed-size array.
#[derive(Clone, Copy)]
pub struct TpmList<T: Copy, const CAPACITY: usize> {
items: [MaybeUninit<T>; CAPACITY],
len: usize,
}
impl<T: Copy, const CAPACITY: usize> TpmList<T, CAPACITY> {
/// Creates a new, empty `TpmList`.
#[must_use]
pub fn new() -> Self {
Self {
items: [const { MaybeUninit::uninit() }; CAPACITY],
len: 0,
}
}
/// Returns `true` if the list contains no elements.
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
/// Appends an element to the back of the list.
///
/// # Errors
///
/// Returns a `TpmErrorKind::Capacity` error if the list is already at
/// full capacity.
pub fn try_push(&mut self, item: T) -> Result<(), TpmErrorKind> {
if self.len >= CAPACITY {
return Err(TpmErrorKind::Capacity(CAPACITY));
}
self.items[self.len].write(item);
self.len += 1;
Ok(())
}
}
#[allow(unsafe_code)]
impl<T: Copy, const CAPACITY: usize> Deref for TpmList<T, CAPACITY> {
type Target = [T];
/// # Safety
///
/// This implementation uses `unsafe` to provide a view into the initialized
/// portion of the list. The caller can rely on this being safe because:
/// 1. The first `self.len` items are guaranteed to be initialized by the `try_push` method.
/// 2. `MaybeUninit<T>` is guaranteed to have the same memory layout as `T`.
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.items.as_ptr().cast::<T>(), self.len) }
}
}
impl<T: Copy, const CAPACITY: usize> Default for TpmList<T, CAPACITY> {
fn default() -> Self {
Self::new()
}
}
impl<T: Copy + Debug, const CAPACITY: usize> Debug for TpmList<T, CAPACITY> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl<T: Copy + PartialEq, const CAPACITY: usize> PartialEq for TpmList<T, CAPACITY> {
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}
impl<T: Copy + Eq, const CAPACITY: usize> Eq for TpmList<T, CAPACITY> {}
impl<T: TpmSized + Copy, const CAPACITY: usize> TpmSized for TpmList<T, CAPACITY> {
const SIZE: usize = size_of::<u32>() + (T::SIZE * CAPACITY);
fn len(&self) -> usize {
size_of::<u32>() + self.iter().map(TpmSized::len).sum::<usize>()
}
}
impl<T: TpmBuild + Copy, const CAPACITY: usize> TpmBuild for TpmList<T, CAPACITY> {
fn build(&self, writer: &mut crate::TpmWriter) -> TpmResult<()> {
let len = u32::try_from(self.len)
.map_err(|_| TpmErrorKind::Capacity(usize::try_from(u32::MAX).unwrap_or(usize::MAX)))?;
TpmBuild::build(&len, writer)?;
for item in &**self {
TpmBuild::build(item, writer)?;
}
Ok(())
}
}
impl<T: TpmParse + Copy, const CAPACITY: usize> TpmParse for TpmList<T, CAPACITY> {
fn parse(buf: &[u8]) -> TpmResult<(Self, &[u8])> {
let (count_u32, mut buf) = u32::parse(buf)?;
let count = count_u32 as usize;
if count > CAPACITY {
return Err(TpmErrorKind::Capacity(CAPACITY));
}
let mut list = Self::new();
for _ in 0..count {
let (item, rest) = T::parse(buf)?;
list.try_push(item).map_err(|_| TpmErrorKind::Failure)?;
buf = rest;
}
Ok((list, buf))
}
}