Skip to main content

tpm2_device/
lib.rs

1// SPDX-License-Identifier: GPL-3-0-or-later
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2025 Jarkko Sakkinen
4
5#![deny(clippy::all)]
6#![deny(clippy::pedantic)]
7
8use nix::{
9    fcntl,
10    poll::{PollFd, PollFlags, poll},
11};
12use rand::{RngCore, thread_rng};
13use std::{
14    cell::RefCell,
15    fs::{File, OpenOptions},
16    io::{Read, Write},
17    os::fd::{AsFd, AsRawFd},
18    path::{Path, PathBuf},
19    rc::Rc,
20    time::{Duration, Instant},
21};
22
23use core::fmt;
24use tpm2_crypto::TpmHash;
25use tpm2_protocol::{
26    TpmCast, TpmError, TpmField, TpmWriter,
27    basic::{Tpm2b as Tpm2bWire, TpmBuffer, TpmHandle, TpmList, TpmUint16, TpmUint32, TpmUint64},
28    constant::{MAX_HANDLES, TPM_MAX_COMMAND_SIZE},
29    data::{
30        Tpm2bDigest, Tpm2bEccParameter, Tpm2bEncryptedSecret, Tpm2bName, Tpm2bNonce,
31        Tpm2bPublicKeyRsa, Tpm2bSymKey, TpmAlgId, TpmCap, TpmCc, TpmEccCurve, TpmHt, TpmPt, TpmRc,
32        TpmRcBase, TpmRh, TpmSe, TpmSt, TpmaAlgorithm, TpmaCc, TpmaObject, TpmaSession, TpmiYesNo,
33        TpmsAlgProperty, TpmsAuthCommand, TpmsCapabilityData, TpmsContext, TpmsEccParms,
34        TpmsEccPoint, TpmsKeyedhashParms, TpmsPcrSelect, TpmsPcrSelection, TpmsRsaParms,
35        TpmsSchemeHash, TpmsSchemeXor, TpmsSymcipherParms, TpmsTaggedProperty, TpmtEccScheme,
36        TpmtKdfScheme, TpmtKeyedhashScheme, TpmtPublic, TpmtRsaScheme, TpmtSymDefObject,
37        TpmuAsymScheme, TpmuCapabilities, TpmuKdfScheme, TpmuKeyedhashScheme, TpmuPublicId,
38        TpmuPublicParms, TpmuSymKeyBits, TpmuSymMode,
39    },
40    frame::{
41        TpmAuthCommands, TpmCommandValue as TpmCommand, TpmContextLoadCommand,
42        TpmContextSaveCommand, TpmFlushContextCommand, TpmFrame, TpmGetCapabilityCommand,
43        TpmReadPublicCommand, TpmResponse, TpmResponseOutcome, TpmResponseView,
44        TpmStartAuthSessionCommand, tpm_marshal_command,
45    },
46};
47use tracing::{debug, trace};
48
49/// Errors that can occur when talking to a TPM device.
50///
51/// `Display` renders only the variant name as lowercase space-separated words
52/// (e.g. `UnexpectedEof` becomes `unexpected eof`).
53#[derive(Debug, strum::AsRefStr)]
54#[strum(serialize_all = "title_case")]
55#[non_exhaustive]
56pub enum TpmDeviceError {
57    /// The TPM device is already mutably borrowed.
58    AlreadyBorrowed,
59
60    /// The requested capability is not available from the TPM.
61    CapabilityMissing(TpmCap),
62
63    /// The operation was interrupted by the caller.
64    Interrupted,
65
66    /// An invalid command code was used.
67    InvalidCc(tpm2_protocol::data::TpmCc),
68
69    /// The TPM returned an invalid or malformed response.
70    InvalidResponse,
71
72    /// An I/O error occurred when accessing the TPM device.
73    Io(std::io::Error),
74
75    /// Marshaling a TPM protocol encoded object failed.
76    Marshal(TpmError),
77
78    /// No TPM device is available.
79    NotAvailable,
80
81    /// No PCR banks are available on the TPM.
82    PcrBanksNotAvailable,
83
84    /// The PCR selection masks differ between active banks.
85    PcrBankSelectionMismatch,
86
87    /// The TPM response did not match the expected command code.
88    ResponseMismatch(TpmCc),
89
90    /// The TPM command timed out.
91    Timeout,
92
93    /// The TPM returned an error code.
94    TpmRc(TpmRc),
95
96    /// Trailing data after the response.
97    TrailingData,
98
99    /// Unmarshaling a TPM protocol encoded object failed.
100    Unmarshal(TpmError),
101
102    /// An unexpected end-of-file was encountered.
103    UnexpectedEof,
104
105    /// The requested algorithm is not supported.
106    UnsupportedAlgorithm(TpmAlgId),
107}
108
109impl fmt::Display for TpmDeviceError {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        write!(f, "{}", self.as_ref().to_lowercase())
112    }
113}
114
115impl std::error::Error for TpmDeviceError {}
116
117impl PartialEq for TpmDeviceError {
118    fn eq(&self, other: &Self) -> bool {
119        match (self, other) {
120            (Self::CapabilityMissing(a), Self::CapabilityMissing(b)) => a == b,
121            (Self::InvalidCc(a), Self::InvalidCc(b))
122            | (Self::ResponseMismatch(a), Self::ResponseMismatch(b)) => a == b,
123            (Self::Io(a), Self::Io(b)) => a.kind() == b.kind(),
124            (Self::Marshal(a), Self::Marshal(b)) | (Self::Unmarshal(a), Self::Unmarshal(b)) => {
125                a == b
126            }
127            (Self::TpmRc(a), Self::TpmRc(b)) => a == b,
128            (Self::UnsupportedAlgorithm(a), Self::UnsupportedAlgorithm(b)) => a == b,
129            (Self::AlreadyBorrowed, Self::AlreadyBorrowed)
130            | (Self::Interrupted, Self::Interrupted)
131            | (Self::InvalidResponse, Self::InvalidResponse)
132            | (Self::NotAvailable, Self::NotAvailable)
133            | (Self::PcrBanksNotAvailable, Self::PcrBanksNotAvailable)
134            | (Self::PcrBankSelectionMismatch, Self::PcrBankSelectionMismatch)
135            | (Self::Timeout, Self::Timeout)
136            | (Self::TrailingData, Self::TrailingData)
137            | (Self::UnexpectedEof, Self::UnexpectedEof) => true,
138            _ => false,
139        }
140    }
141}
142
143impl Eq for TpmDeviceError {}
144
145impl From<TpmRc> for TpmDeviceError {
146    fn from(rc: TpmRc) -> Self {
147        Self::TpmRc(rc)
148    }
149}
150
151impl From<std::io::Error> for TpmDeviceError {
152    fn from(err: std::io::Error) -> Self {
153        Self::Io(err)
154    }
155}
156
157impl From<nix::Error> for TpmDeviceError {
158    fn from(err: nix::Error) -> Self {
159        Self::Io(std::io::Error::from_raw_os_error(err as i32))
160    }
161}
162
163/// Executes a closure with a mutable reference to a `TpmDevice`.
164///
165/// This helper function centralizes the boilerplate for safely acquiring a
166/// mutable borrow of a `TpmDevice` from the shared `Rc<RefCell<...>>`.
167///
168/// # Errors
169///
170/// Returns [`NotAvailable`](crate::TpmDeviceError::NotAvailable) when no device
171/// is present.
172/// Returns [`AlreadyBorrowed`](crate::TpmDeviceError::AlreadyBorrowed) when the
173/// device is already mutably borrowed.
174/// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants depending
175/// on function.
176pub fn with_device<F, T, E>(device: Option<&Rc<RefCell<TpmDevice>>>, function: F) -> Result<T, E>
177where
178    F: FnOnce(&mut TpmDevice) -> Result<T, E>,
179    E: From<TpmDeviceError>,
180{
181    let device_rc = device.ok_or(TpmDeviceError::NotAvailable)?;
182    let mut device_guard = device_rc
183        .try_borrow_mut()
184        .map_err(|_| TpmDeviceError::AlreadyBorrowed)?;
185    function(&mut device_guard)
186}
187
188/// A bidirectional, frame-oriented transport for marshaled TPM frames.
189///
190/// The two endpoints of a TPM exchange are symmetric on the wire: a host sends
191/// command frames and receives response frames, while a responder such as an
192/// emulator does the reverse. A `TpmTransport` therefore moves whole frames in
193/// either direction, decoupling both ends from any concrete byte stream.
194pub trait TpmTransport {
195    /// Sends one complete marshaled frame.
196    ///
197    /// # Errors
198    ///
199    /// Returns [`Io`](crate::TpmDeviceError::Io) when writing the frame fails.
200    fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError>;
201
202    /// Receives one complete frame into `buf`, replacing its contents.
203    ///
204    /// # Errors
205    ///
206    /// Returns [`Io`](crate::TpmDeviceError::Io) when reading fails,
207    /// [`Timeout`](crate::TpmDeviceError::Timeout) when no complete frame
208    /// arrives in time, [`Interrupted`](crate::TpmDeviceError::Interrupted)
209    /// when cancellation is requested, or
210    /// [`InvalidResponse`](crate::TpmDeviceError::InvalidResponse) /
211    /// [`TrailingData`](crate::TpmDeviceError::TrailingData) when the frame
212    /// envelope is malformed.
213    fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError>;
214}
215
216const TPM_HEADER_SIZE: usize = 10;
217
218/// Returns the total frame length declared by a TPM frame header.
219///
220/// # Errors
221///
222/// Returns [`InvalidResponse`](crate::TpmDeviceError::InvalidResponse) when the
223/// header is shorter than its size field or declares a size outside the range
224/// `[TPM_HEADER_SIZE, TPM_MAX_COMMAND_SIZE]`.
225fn frame_size(header: &[u8]) -> Result<usize, TpmDeviceError> {
226    let Some(size_bytes) = header.get(2..6) else {
227        return Err(TpmDeviceError::InvalidResponse);
228    };
229    let Ok(size_bytes): Result<[u8; 4], _> = size_bytes.try_into() else {
230        return Err(TpmDeviceError::InvalidResponse);
231    };
232    let size = u32::from_be_bytes(size_bytes) as usize;
233    if !(TPM_HEADER_SIZE..=TPM_MAX_COMMAND_SIZE).contains(&size) {
234        return Err(TpmDeviceError::InvalidResponse);
235    }
236    Ok(size)
237}
238
239fn fill_exact<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<(), TpmDeviceError> {
240    match reader.read_exact(buf) {
241        Ok(()) => Ok(()),
242        Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
243            Err(TpmDeviceError::UnexpectedEof)
244        }
245        Err(e) => Err(TpmDeviceError::Io(e)),
246    }
247}
248
249/// Reads one complete TPM frame from a blocking stream into `buf`.
250///
251/// The header is read first to learn the frame's declared size, then exactly
252/// that many bytes (header included) are read; `buf` is cleared beforehand. The
253/// framing is identical for command and response frames, so this serves a host
254/// reading responses and a responder reading commands alike.
255///
256/// # Errors
257///
258/// Returns [`UnexpectedEof`](crate::TpmDeviceError::UnexpectedEof) when the
259/// stream ends before a complete frame, [`Io`](crate::TpmDeviceError::Io) on any
260/// other read failure, or
261/// [`InvalidResponse`](crate::TpmDeviceError::InvalidResponse) when the header
262/// declares an out-of-range size.
263pub fn read_frame<R: Read>(reader: &mut R, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
264    buf.clear();
265    buf.resize(TPM_HEADER_SIZE, 0);
266    fill_exact(reader, buf)?;
267
268    let size = frame_size(buf)?;
269    buf.resize(size, 0);
270    fill_exact(reader, &mut buf[TPM_HEADER_SIZE..])?;
271
272    Ok(())
273}
274
275/// Writes one complete marshaled TPM frame to a blocking stream and flushes it.
276///
277/// # Errors
278///
279/// Returns [`Io`](crate::TpmDeviceError::Io) when writing or flushing fails.
280pub fn write_frame<W: Write>(writer: &mut W, frame: &[u8]) -> Result<(), TpmDeviceError> {
281    writer.write_all(frame).map_err(TpmDeviceError::Io)?;
282    writer.flush().map_err(TpmDeviceError::Io)?;
283    Ok(())
284}
285
286/// A [`TpmTransport`] over any blocking byte stream.
287///
288/// Suitable for TPM endpoints reached over TCP (such as a software TPM),
289/// Unix-domain sockets, or in-memory pipes.
290pub struct TpmStreamTransport<S: Read + Write> {
291    stream: S,
292}
293
294impl<S: Read + Write> TpmStreamTransport<S> {
295    /// Wraps a stream as a transport.
296    #[must_use]
297    pub fn new(stream: S) -> Self {
298        Self { stream }
299    }
300
301    /// Consumes the transport and returns the underlying stream.
302    #[must_use]
303    pub fn into_inner(self) -> S {
304        self.stream
305    }
306}
307
308impl<S: Read + Write> TpmTransport for TpmStreamTransport<S> {
309    fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
310        write_frame(&mut self.stream, frame)
311    }
312
313    fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
314        read_frame(&mut self.stream, buf)
315    }
316}
317
318/// A [`TpmTransport`] backed by a Linux TPM character device.
319pub struct TpmPosixDevice {
320    file: File,
321    interrupted: Box<dyn Fn() -> bool>,
322    timeout: Duration,
323}
324
325impl TpmPosixDevice {
326    /// Creates a new builder for a `TpmPosixDevice`.
327    #[must_use]
328    pub fn builder() -> TpmPosixDeviceBuilder {
329        TpmPosixDeviceBuilder::default()
330    }
331
332    fn receive(&mut self, buf: &mut [u8]) -> Result<usize, TpmDeviceError> {
333        let fd = self.file.as_fd();
334        let mut fds = [PollFd::new(fd, PollFlags::POLLIN)];
335
336        let num_events = match poll(&mut fds, 100u16) {
337            Ok(num) => num,
338            Err(nix::Error::EINTR) => return Ok(0),
339            Err(e) => return Err(e.into()),
340        };
341
342        if num_events == 0 {
343            return Ok(0);
344        }
345
346        let revents = fds[0].revents().unwrap_or(PollFlags::empty());
347
348        if revents.intersects(PollFlags::POLLERR | PollFlags::POLLNVAL) {
349            return Err(TpmDeviceError::UnexpectedEof);
350        }
351
352        if revents.contains(PollFlags::POLLIN) {
353            match self.file.read(buf) {
354                Ok(0) => Err(TpmDeviceError::UnexpectedEof),
355                Ok(n) => Ok(n),
356                Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
357                Err(e) if e.kind() == std::io::ErrorKind::Interrupted => Ok(0),
358                Err(e) => Err(e.into()),
359            }
360        } else if revents.contains(PollFlags::POLLHUP) {
361            Err(TpmDeviceError::UnexpectedEof)
362        } else {
363            Ok(0)
364        }
365    }
366}
367
368impl TpmTransport for TpmPosixDevice {
369    fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
370        self.file.write_all(frame)?;
371        self.file.flush()?;
372        Ok(())
373    }
374
375    fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
376        buf.clear();
377        let start_time = Instant::now();
378        let mut total_size: Option<usize> = None;
379        let mut temp_buf = [0u8; 1024];
380
381        loop {
382            if (self.interrupted)() {
383                return Err(TpmDeviceError::Interrupted);
384            }
385            if start_time.elapsed() > self.timeout {
386                return Err(TpmDeviceError::Timeout);
387            }
388
389            let n = self.receive(&mut temp_buf)?;
390            if n > 0 {
391                buf.extend_from_slice(&temp_buf[..n]);
392            }
393
394            if total_size.is_none() && buf.len() >= TPM_HEADER_SIZE {
395                total_size = Some(frame_size(buf)?);
396            }
397
398            if let Some(size) = total_size {
399                if buf.len() == size {
400                    break;
401                }
402                if buf.len() > size {
403                    return Err(TpmDeviceError::TrailingData);
404                }
405            }
406        }
407
408        Ok(())
409    }
410}
411
412/// A builder for constructing a [`TpmPosixDevice`].
413pub struct TpmPosixDeviceBuilder {
414    path: PathBuf,
415    timeout: Duration,
416    interrupted: Box<dyn Fn() -> bool>,
417}
418
419impl Default for TpmPosixDeviceBuilder {
420    fn default() -> Self {
421        Self {
422            path: PathBuf::from("/dev/tpmrm0"),
423            timeout: Duration::from_secs(120),
424            interrupted: Box::new(|| false),
425        }
426    }
427}
428
429impl TpmPosixDeviceBuilder {
430    /// Sets the device file path.
431    #[must_use]
432    pub fn with_path<P: AsRef<Path>>(mut self, path: P) -> Self {
433        self.path = path.as_ref().to_path_buf();
434        self
435    }
436
437    /// Sets the operation timeout.
438    #[must_use]
439    pub fn with_timeout(mut self, timeout: Duration) -> Self {
440        self.timeout = timeout;
441        self
442    }
443
444    /// Sets the interruption check callback.
445    #[must_use]
446    pub fn with_interrupted<F>(mut self, handler: F) -> Self
447    where
448        F: Fn() -> bool + 'static,
449    {
450        self.interrupted = Box::new(handler);
451        self
452    }
453
454    /// Opens the TPM character device and constructs the [`TpmPosixDevice`].
455    ///
456    /// # Errors
457    ///
458    /// Returns [`Io`](crate::TpmDeviceError::Io) when the device file cannot be
459    /// opened or when configuring the file descriptor flags fails.
460    pub fn build(self) -> Result<TpmPosixDevice, TpmDeviceError> {
461        let file = OpenOptions::new()
462            .read(true)
463            .write(true)
464            .open(&self.path)
465            .map_err(TpmDeviceError::Io)?;
466
467        let fd = file.as_raw_fd();
468        let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL)?;
469        let mut oflags = fcntl::OFlag::from_bits_truncate(flags);
470        oflags.insert(fcntl::OFlag::O_NONBLOCK);
471        fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(oflags))?;
472
473        Ok(TpmPosixDevice {
474            file,
475            interrupted: self.interrupted,
476            timeout: self.timeout,
477        })
478    }
479}
480
481pub struct TpmDevice {
482    transport: Box<dyn TpmTransport>,
483    command: Vec<u8>,
484    response: Vec<u8>,
485}
486
487impl std::fmt::Debug for TpmDevice {
488    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489        f.debug_struct("TpmDevice").finish_non_exhaustive()
490    }
491}
492
493impl TpmDevice {
494    const NO_SESSIONS: &'static [TpmsAuthCommand] = &[];
495
496    /// Number of items requested per paginated `GetCapability` query.
497    #[allow(clippy::cast_possible_truncation)]
498    const CAPABILITY_PAGE_SIZE: u32 = MAX_HANDLES as u32;
499
500    /// Creates a `TpmDevice` driving the given transport.
501    #[must_use]
502    pub fn new(transport: Box<dyn TpmTransport>) -> Self {
503        Self {
504            transport,
505            command: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
506            response: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
507        }
508    }
509
510    /// Performs the whole TPM command transmission process.
511    ///
512    /// # Errors
513    ///
514    /// Returns [`Interrupted`](crate::TpmDeviceError::Interrupted) when the
515    /// interrupt callback requests cancellation.
516    /// Returns [`Io`](crate::TpmDeviceError::Io) when a write, flush, or read
517    /// operation on the device file fails, or when polling the device file
518    /// descriptor fails.
519    /// Returns [`Marshal`](crate::TpmDeviceError::Marshal) when marshal
520    /// operation on TPM protocol compliant data fails.
521    /// Returns [`Timeout`](crate::TpmDeviceError::Timeout) when the TPM does
522    /// not respond within the configured timeout.
523    /// Returns [`TpmRc`](crate::TpmDeviceError::TpmRc) when the TPM returns an
524    /// error code.
525    /// Returns [`Unmarshal`](crate::TpmDeviceError::Unmarshal) when unmarshal
526    /// operation on TPM protocol compliant data fails.
527    pub fn transmit<C: TpmFrame>(
528        &mut self,
529        command: &C,
530        sessions: &[TpmsAuthCommand],
531    ) -> Result<&TpmResponse, TpmDeviceError> {
532        self.prepare_command(command, sessions)?;
533        let cc = command.cc();
534
535        self.transport.send(&self.command)?;
536        self.transport.recv(&mut self.response)?;
537
538        let response = TpmResponse::cast(&self.response).map_err(TpmDeviceError::Unmarshal)?;
539        let outcome = TpmResponseView::cast(cc, response).map_err(TpmDeviceError::Unmarshal)?;
540        trace!("{} R: {}", cc, hex::encode(&self.response));
541        match outcome {
542            TpmResponseOutcome::Dispatched(_) => Ok(response),
543            TpmResponseOutcome::Rejected(rc) => Err(TpmDeviceError::TpmRc(rc)),
544        }
545    }
546
547    fn prepare_command<C: TpmFrame>(
548        &mut self,
549        command: &C,
550        sessions: &[TpmsAuthCommand],
551    ) -> Result<(), TpmDeviceError> {
552        let cc = command.cc();
553        let tag = if sessions.is_empty() {
554            TpmSt::NoSessions
555        } else {
556            TpmSt::Sessions
557        };
558
559        self.command.resize(TPM_MAX_COMMAND_SIZE, 0);
560
561        let len = {
562            let mut writer = TpmWriter::new(&mut self.command);
563            tpm_marshal_command(command, tag, sessions, &mut writer)
564                .map_err(TpmDeviceError::Marshal)?;
565            writer.len()
566        };
567        self.command.truncate(len);
568
569        trace!("{} C: {}", cc, hex::encode(&self.command));
570        Ok(())
571    }
572
573    /// Fetches a complete list of capabilities from the TPM, handling
574    /// pagination.
575    ///
576    /// # Errors
577    ///
578    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
579    /// when receiving unepected TPM response.
580    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
581    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
582    fn get_capability<T, F, N>(
583        &mut self,
584        cap: TpmCap,
585        property_start: u32,
586        count: u32,
587        mut extract: F,
588        next_prop: N,
589    ) -> Result<Vec<T>, TpmDeviceError>
590    where
591        T: Copy,
592        F: for<'a> FnMut(&'a TpmuCapabilities) -> Result<&'a [T], TpmDeviceError>,
593        N: Fn(&T) -> u32,
594    {
595        let mut results = Vec::new();
596        let mut prop = property_start;
597        loop {
598            let (more_data, cap_data) =
599                self.get_capability_page(cap, TpmUint32::from(prop), TpmUint32::from(count))?;
600            let items: &[T] = extract(&cap_data.data)?;
601            results.extend_from_slice(items);
602
603            if more_data {
604                if let Some(last) = items.last() {
605                    prop = next_prop(last);
606                } else {
607                    break;
608                }
609            } else {
610                break;
611            }
612        }
613        Ok(results)
614    }
615
616    /// Retrieves all algorithm properties supported by the TPM.
617    ///
618    /// # Errors
619    ///
620    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
621    /// when receiving unepected TPM response.
622    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
623    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
624    pub fn fetch_algorithm_properties(&mut self) -> Result<Vec<TpmsAlgProperty>, TpmDeviceError> {
625        self.get_capability(
626            TpmCap::Algs,
627            0,
628            Self::CAPABILITY_PAGE_SIZE,
629            |caps| match caps {
630                TpmuCapabilities::Algs(algs) => Ok(algs),
631                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Algs)),
632            },
633            |last| u32::from(last.alg.value()) + 1,
634        )
635    }
636
637    /// Retrieves all handles of a specific type from the TPM.
638    ///
639    /// # Errors
640    ///
641    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
642    /// when receiving unepected TPM response.
643    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
644    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
645    pub fn fetch_handles(&mut self, class: TpmHt) -> Result<Vec<TpmHandle>, TpmDeviceError> {
646        self.get_capability(
647            TpmCap::Handles,
648            (class as u32) << 24,
649            Self::CAPABILITY_PAGE_SIZE,
650            |caps| match caps {
651                TpmuCapabilities::Handles(handles) => Ok(handles),
652                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Handles)),
653            },
654            |last| last.value() + 1,
655        )
656        .map(|handles| handles.into_iter().collect())
657    }
658
659    /// Retrieves all available ECC curves supported by the TPM.
660    ///
661    /// # Errors
662    ///
663    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
664    /// when receiving unepected TPM response.
665    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
666    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
667    pub fn fetch_ecc_curves(&mut self) -> Result<Vec<TpmEccCurve>, TpmDeviceError> {
668        self.get_capability(
669            TpmCap::EccCurves,
670            0,
671            Self::CAPABILITY_PAGE_SIZE,
672            |caps| match caps {
673                TpmuCapabilities::EccCurves(curves) => Ok(curves),
674                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::EccCurves)),
675            },
676            |last| u32::from(last.value()) + 1,
677        )
678    }
679
680    /// Retrieves the list of active PCR banks and the bank selection mask.
681    ///
682    /// # Errors
683    ///
684    /// Returns
685    /// [`PcrBanksNotAvailable`](crate::TpmDeviceError::PcrBanksNotAvailable)
686    /// when no PCR banks are available.
687    /// Return
688    /// [`PcrBankSelectionMismatch`](crate::TpmDeviceError::PcrBankSelectionMismatch)
689    /// when the PCR selection masks differ between active banks.
690    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
691    /// when receiving unepected TPM response.
692    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
693    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
694    pub fn fetch_pcr_bank_list(
695        &mut self,
696    ) -> Result<(Vec<TpmAlgId>, TpmsPcrSelect), TpmDeviceError> {
697        let pcrs: Vec<TpmsPcrSelection> = self.get_capability(
698            TpmCap::Pcrs,
699            0,
700            Self::CAPABILITY_PAGE_SIZE,
701            |caps| match caps {
702                TpmuCapabilities::Pcrs(pcrs) => Ok(pcrs),
703                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Pcrs)),
704            },
705            |last| last.hash as u32 + 1,
706        )?;
707
708        if pcrs.is_empty() {
709            return Err(TpmDeviceError::PcrBanksNotAvailable);
710        }
711
712        let mut common_select: Option<TpmsPcrSelect> = None;
713        let mut algs = Vec::with_capacity(pcrs.len());
714
715        for bank in pcrs {
716            if bank.pcr_select.iter().all(|&b| b == 0) {
717                debug!(
718                    "skipping unallocated bank {:?} (mask: {})",
719                    bank.hash,
720                    hex::encode(&*bank.pcr_select)
721                );
722                continue;
723            }
724
725            if let Some(ref select) = common_select {
726                if bank.pcr_select != *select {
727                    return Err(TpmDeviceError::PcrBankSelectionMismatch);
728                }
729            } else {
730                common_select = Some(bank.pcr_select);
731            }
732            algs.push(bank.hash);
733        }
734
735        let select = common_select.ok_or(TpmDeviceError::PcrBanksNotAvailable)?;
736
737        algs.sort();
738        Ok((algs, select))
739    }
740
741    /// Fetches and returns one page of capabilities of a certain type from the
742    /// TPM.
743    ///
744    /// # Errors
745    ///
746    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
747    /// when receiving unepected TPM response.
748    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
749    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
750    fn get_capability_page(
751        &mut self,
752        cap: TpmCap,
753        property: TpmUint32,
754        property_count: TpmUint32,
755    ) -> Result<(bool, TpmsCapabilityData), TpmDeviceError> {
756        let cmd = TpmGetCapabilityCommand {
757            cap,
758            property,
759            property_count,
760            handles: [],
761        };
762
763        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
764        let (_, parameters) = response_parts(response, 0)?;
765        let (more_data, parameters) = parse_field_value::<TpmiYesNo>(parameters)?;
766        let (capability_data, rest) = parse_capability_data(parameters)?;
767        ensure_empty(rest)?;
768
769        Ok((more_data.into(), capability_data))
770    }
771
772    /// Reads a specific TPM property.
773    ///
774    /// # Errors
775    ///
776    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
777    /// when receiving unepected TPM response.
778    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
779    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
780    pub fn fetch_tpm_property(&mut self, property: TpmPt) -> Result<u32, TpmDeviceError> {
781        let (_, cap_data) = self.get_capability_page(
782            TpmCap::TpmProperties,
783            TpmUint32::from(property as u32),
784            TpmUint32::from(1),
785        )?;
786
787        let TpmuCapabilities::TpmProperties(props) = &cap_data.data else {
788            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
789        };
790
791        let Some(prop) = props.iter().find(|prop| prop.property == property) else {
792            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
793        };
794
795        Ok(prop.value.value())
796    }
797
798    /// Reads the public area of a TPM object.
799    ///
800    /// # Errors
801    ///
802    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
803    /// when receiving unepected TPM response.
804    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
805    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
806    pub fn read_public(
807        &mut self,
808        handle: TpmHandle,
809    ) -> Result<(TpmtPublic, Tpm2bName), TpmDeviceError> {
810        let cmd = TpmReadPublicCommand { handles: [handle] };
811        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
812        let (_, parameters) = response_parts(response, 0)?;
813        let (public, parameters) = parse_tpm2b_public(parameters)?;
814        let (name, parameters): (Tpm2bName, _) = parse_tpm2b_buffer(parameters)?;
815        let (_qualified_name, rest): (Tpm2bName, _) = parse_tpm2b_buffer(parameters)?;
816        ensure_empty(rest)?;
817
818        Ok((public, name))
819    }
820
821    /// Finds a persistent handle by its `Tpm2bName`.
822    ///
823    /// # Errors
824    ///
825    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
826    /// when receiving unepected TPM response.
827    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
828    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
829    pub fn find_persistent(
830        &mut self,
831        target_name: &Tpm2bName,
832    ) -> Result<Option<TpmHandle>, TpmDeviceError> {
833        for handle in self.fetch_handles(TpmHt::Persistent)? {
834            match self.read_public(handle) {
835                Ok((_, name)) => {
836                    if name == *target_name {
837                        return Ok(Some(handle));
838                    }
839                }
840                Err(TpmDeviceError::TpmRc(rc)) => {
841                    let base = rc.base();
842                    if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
843                        continue;
844                    }
845                    return Err(TpmDeviceError::TpmRc(rc));
846                }
847                Err(e) => return Err(e),
848            }
849        }
850        Ok(None)
851    }
852
853    /// Saves the context of a transient object or session.
854    ///
855    /// # Errors
856    ///
857    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
858    /// when receiving unepected TPM response.
859    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
860    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
861    pub fn save_context(&mut self, save_handle: TpmHandle) -> Result<TpmsContext, TpmDeviceError> {
862        let cmd = TpmContextSaveCommand {
863            handles: [save_handle],
864        };
865        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
866        let (_, parameters) = response_parts(response, 0)?;
867        let (context, rest) = parse_tpms_context(parameters)?;
868        ensure_empty(rest)?;
869
870        Ok(context)
871    }
872
873    /// Loads a TPM context and returns the handle.
874    ///
875    /// # Errors
876    ///
877    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
878    /// when receiving unepected TPM response.
879    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
880    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
881    pub fn load_context(&mut self, context: TpmsContext) -> Result<TpmHandle, TpmDeviceError> {
882        let cmd = TpmContextLoadCommand {
883            context,
884            handles: [],
885        };
886        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
887        let (handles, parameters) = response_parts(response, 1)?;
888        let (handle, rest) = parse_wire_copy::<TpmHandle>(handles)?;
889        ensure_empty(rest)?;
890        ensure_empty(parameters)?;
891
892        Ok(handle)
893    }
894
895    /// Flushes a transient object or session from the TPM and removes it from
896    /// the cache.
897    ///
898    /// # Errors
899    ///
900    /// Returns [`TpmDeviceError`](crate::TpmDeviceError) variants when
901    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
902    pub fn flush_context(&mut self, handle: TpmHandle) -> Result<(), TpmDeviceError> {
903        let cmd = TpmFlushContextCommand {
904            flush_handle: handle,
905            handles: [],
906        };
907        self.transmit(&cmd, Self::NO_SESSIONS)?;
908        Ok(())
909    }
910
911    /// Loads a session context and then flushes the resulting handle.
912    ///
913    /// # Errors
914    ///
915    /// Returns [`TpmDeviceError`](crate::TpmDeviceError) variants when
916    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
917    pub fn flush_session(&mut self, context: TpmsContext) -> Result<(), TpmDeviceError> {
918        match self.load_context(context) {
919            Ok(handle) => self.flush_context(handle),
920            Err(TpmDeviceError::TpmRc(rc)) => {
921                let base = rc.base();
922                if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
923                    Ok(())
924                } else {
925                    Err(TpmDeviceError::TpmRc(rc))
926                }
927            }
928            Err(e) => Err(e),
929        }
930    }
931}
932
933/// The responder end of a [`TpmTransport`], for serving TPM commands.
934///
935/// Where a [`TpmDevice`] sends commands and receives responses, a `TpmResponder`
936/// receives commands and sends responses. Both ends share the same transport
937/// and frame codec; only the direction of use differs, which makes this crate
938/// usable for the TPM side of an exchange, such as an emulator.
939///
940/// Command decoding and response marshaling are left to the caller (for example
941/// via `tpm2_protocol`), keeping this type focused on transport and framing.
942pub struct TpmResponder {
943    transport: Box<dyn TpmTransport>,
944    command: Vec<u8>,
945}
946
947impl std::fmt::Debug for TpmResponder {
948    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
949        f.debug_struct("TpmResponder").finish_non_exhaustive()
950    }
951}
952
953impl TpmResponder {
954    /// Creates a `TpmResponder` serving over the given transport.
955    #[must_use]
956    pub fn new(transport: Box<dyn TpmTransport>) -> Self {
957        Self {
958            transport,
959            command: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
960        }
961    }
962
963    /// Receives the next command frame, returning its raw bytes.
964    ///
965    /// # Errors
966    ///
967    /// Returns [`UnexpectedEof`](crate::TpmDeviceError::UnexpectedEof) when the
968    /// peer disconnects, or other [`TpmDeviceError`](crate::TpmDeviceError)
969    /// variants when the transport fails.
970    pub fn recv_command(&mut self) -> Result<&[u8], TpmDeviceError> {
971        self.transport.recv(&mut self.command)?;
972        Ok(&self.command)
973    }
974
975    /// Sends a marshaled response frame.
976    ///
977    /// # Errors
978    ///
979    /// Returns a [`TpmDeviceError`](crate::TpmDeviceError) when the transport
980    /// fails to send the frame.
981    pub fn send_response(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
982        self.transport.send(frame)
983    }
984
985    /// Serves commands until the peer disconnects.
986    ///
987    /// Each received command frame is passed to `handler`, whose returned bytes
988    /// are written back as the response frame. Returns `Ok(())` once the peer
989    /// closes the transport.
990    ///
991    /// # Errors
992    ///
993    /// Returns a [`TpmDeviceError`](crate::TpmDeviceError) when receiving a
994    /// command or sending a response fails for any reason other than a clean
995    /// disconnect.
996    pub fn serve<H>(&mut self, mut handler: H) -> Result<(), TpmDeviceError>
997    where
998        H: FnMut(&[u8]) -> Vec<u8>,
999    {
1000        loop {
1001            match self.transport.recv(&mut self.command) {
1002                Ok(()) => {}
1003                Err(TpmDeviceError::UnexpectedEof) => return Ok(()),
1004                Err(e) => return Err(e),
1005            }
1006            let response = handler(&self.command);
1007            self.transport.send(&response)?;
1008        }
1009    }
1010}
1011
1012fn ensure_empty(buf: &[u8]) -> Result<(), TpmDeviceError> {
1013    if buf.is_empty() {
1014        Ok(())
1015    } else {
1016        Err(TpmDeviceError::TrailingData)
1017    }
1018}
1019
1020fn response_parts(
1021    response: &TpmResponse,
1022    response_handles: usize,
1023) -> Result<(&[u8], &[u8]), TpmDeviceError> {
1024    let handle_len = response_handles
1025        .checked_mul(core::mem::size_of::<TpmHandle>())
1026        .ok_or(TpmDeviceError::InvalidResponse)?;
1027    let body = response.body();
1028    if body.len() < handle_len {
1029        return Err(TpmDeviceError::InvalidResponse);
1030    }
1031
1032    let (handles, after_handles) = body.split_at(handle_len);
1033    if response.tag().map_err(TpmDeviceError::Unmarshal)? != TpmSt::Sessions {
1034        return Ok((handles, after_handles));
1035    }
1036
1037    let (parameter_size, after_size) = parse_wire_copy::<TpmUint32>(after_handles)?;
1038    let parameter_size =
1039        usize::try_from(parameter_size.value()).map_err(|_| TpmDeviceError::InvalidResponse)?;
1040    if after_size.len() < parameter_size {
1041        return Err(TpmDeviceError::InvalidResponse);
1042    }
1043
1044    let (parameters, _auth_area) = after_size.split_at(parameter_size);
1045    Ok((handles, parameters))
1046}
1047
1048fn parse_wire_copy<'a, T>(buf: &'a [u8]) -> Result<(T, &'a [u8]), TpmDeviceError>
1049where
1050    T: TpmCast + Copy + 'a,
1051{
1052    let (value, rest) = T::cast_prefix(buf).map_err(TpmDeviceError::Unmarshal)?;
1053    Ok((*value, rest))
1054}
1055
1056fn parse_field_value<'a, T>(buf: &'a [u8]) -> Result<(T, &'a [u8]), TpmDeviceError>
1057where
1058    T: TpmField<'a, View = T>,
1059{
1060    <T as TpmField<'a>>::cast_prefix_field(buf).map_err(TpmDeviceError::Unmarshal)
1061}
1062
1063fn parse_tpm2b_buffer<const CAPACITY: usize>(
1064    buf: &[u8],
1065) -> Result<(TpmBuffer<CAPACITY>, &[u8]), TpmDeviceError> {
1066    let (value, rest) =
1067        Tpm2bWire::<CAPACITY>::cast_prefix(buf).map_err(TpmDeviceError::Unmarshal)?;
1068    let value = TpmBuffer::<CAPACITY>::try_from(value.data()).map_err(TpmDeviceError::Unmarshal)?;
1069
1070    Ok((value, rest))
1071}
1072
1073fn parse_tpms_scheme_hash(buf: &[u8]) -> Result<(TpmsSchemeHash, &[u8]), TpmDeviceError> {
1074    let (hash_alg, rest) = parse_field_value::<TpmAlgId>(buf)?;
1075
1076    Ok((TpmsSchemeHash { hash_alg }, rest))
1077}
1078
1079fn parse_tpmt_kdf_scheme(buf: &[u8]) -> Result<(TpmtKdfScheme, &[u8]), TpmDeviceError> {
1080    let (scheme, buf) = parse_field_value::<TpmAlgId>(buf)?;
1081    let (details, rest) = parse_tpmu_kdf_scheme(scheme, buf)?;
1082
1083    Ok((TpmtKdfScheme { scheme, details }, rest))
1084}
1085
1086fn parse_tpmu_kdf_scheme(
1087    scheme: TpmAlgId,
1088    buf: &[u8],
1089) -> Result<(TpmuKdfScheme, &[u8]), TpmDeviceError> {
1090    match scheme {
1091        TpmAlgId::Mgf1 => {
1092            let (details, rest) = parse_tpms_scheme_hash(buf)?;
1093            Ok((TpmuKdfScheme::Mgf1(details), rest))
1094        }
1095        TpmAlgId::Kdf1Sp800_56A => {
1096            let (details, rest) = parse_tpms_scheme_hash(buf)?;
1097            Ok((TpmuKdfScheme::Kdf1Sp800_56a(details), rest))
1098        }
1099        TpmAlgId::Kdf2 => {
1100            let (details, rest) = parse_tpms_scheme_hash(buf)?;
1101            Ok((TpmuKdfScheme::Kdf2(details), rest))
1102        }
1103        TpmAlgId::Kdf1Sp800_108 => {
1104            let (details, rest) = parse_tpms_scheme_hash(buf)?;
1105            Ok((TpmuKdfScheme::Kdf1Sp800_108(details), rest))
1106        }
1107        TpmAlgId::Null => Ok((TpmuKdfScheme::Null, buf)),
1108        _ => Err(TpmDeviceError::InvalidResponse),
1109    }
1110}
1111
1112fn parse_tpms_scheme_xor(buf: &[u8]) -> Result<(TpmsSchemeXor, &[u8]), TpmDeviceError> {
1113    let (hash_alg, buf) = parse_field_value::<TpmAlgId>(buf)?;
1114    let (kdf, rest) = parse_tpmt_kdf_scheme(buf)?;
1115
1116    Ok((TpmsSchemeXor { hash_alg, kdf }, rest))
1117}
1118
1119fn parse_tpmu_keyedhash_scheme(
1120    scheme: TpmAlgId,
1121    buf: &[u8],
1122) -> Result<(TpmuKeyedhashScheme, &[u8]), TpmDeviceError> {
1123    match scheme {
1124        TpmAlgId::Hmac => {
1125            let (details, rest) = parse_tpms_scheme_hash(buf)?;
1126            Ok((TpmuKeyedhashScheme::Hmac(details), rest))
1127        }
1128        TpmAlgId::Xor => {
1129            let (details, rest) = parse_tpms_scheme_xor(buf)?;
1130            Ok((TpmuKeyedhashScheme::Xor(details), rest))
1131        }
1132        TpmAlgId::Null => Ok((TpmuKeyedhashScheme::Null, buf)),
1133        _ => Err(TpmDeviceError::InvalidResponse),
1134    }
1135}
1136
1137fn parse_tpmt_keyedhash_scheme(buf: &[u8]) -> Result<(TpmtKeyedhashScheme, &[u8]), TpmDeviceError> {
1138    let (scheme, buf) = parse_field_value::<TpmAlgId>(buf)?;
1139    let (details, rest) = parse_tpmu_keyedhash_scheme(scheme, buf)?;
1140
1141    Ok((TpmtKeyedhashScheme { scheme, details }, rest))
1142}
1143
1144fn parse_tpmu_asym_scheme(
1145    scheme: TpmAlgId,
1146    buf: &[u8],
1147) -> Result<(TpmuAsymScheme, &[u8]), TpmDeviceError> {
1148    match scheme {
1149        TpmAlgId::Rsassa
1150        | TpmAlgId::Rsapss
1151        | TpmAlgId::Ecdsa
1152        | TpmAlgId::Ecdaa
1153        | TpmAlgId::Sm2
1154        | TpmAlgId::Ecschnorr
1155        | TpmAlgId::Oaep
1156        | TpmAlgId::Ecdh
1157        | TpmAlgId::Ecmqv => {
1158            let (details, rest) = parse_tpms_scheme_hash(buf)?;
1159            Ok((TpmuAsymScheme::Hash(details), rest))
1160        }
1161        TpmAlgId::Rsaes | TpmAlgId::Null => Ok((TpmuAsymScheme::Null, buf)),
1162        _ => Err(TpmDeviceError::InvalidResponse),
1163    }
1164}
1165
1166fn parse_tpmt_rsa_scheme(buf: &[u8]) -> Result<(TpmtRsaScheme, &[u8]), TpmDeviceError> {
1167    let (scheme, buf) = parse_field_value::<TpmAlgId>(buf)?;
1168    let (details, rest) = parse_tpmu_asym_scheme(scheme, buf)?;
1169
1170    Ok((TpmtRsaScheme { scheme, details }, rest))
1171}
1172
1173fn parse_tpmt_ecc_scheme(buf: &[u8]) -> Result<(TpmtEccScheme, &[u8]), TpmDeviceError> {
1174    let (scheme, buf) = parse_field_value::<TpmAlgId>(buf)?;
1175    let (details, rest) = parse_tpmu_asym_scheme(scheme, buf)?;
1176
1177    Ok((TpmtEccScheme { scheme, details }, rest))
1178}
1179
1180fn parse_tpmu_sym_key_bits(
1181    algorithm: TpmAlgId,
1182    buf: &[u8],
1183) -> Result<(TpmuSymKeyBits, &[u8]), TpmDeviceError> {
1184    match algorithm {
1185        TpmAlgId::Aes => {
1186            let (value, rest) = parse_wire_copy::<TpmUint16>(buf)?;
1187            Ok((TpmuSymKeyBits::Aes(value), rest))
1188        }
1189        TpmAlgId::Sm4 => {
1190            let (value, rest) = parse_wire_copy::<TpmUint16>(buf)?;
1191            Ok((TpmuSymKeyBits::Sm4(value), rest))
1192        }
1193        TpmAlgId::Camellia => {
1194            let (value, rest) = parse_wire_copy::<TpmUint16>(buf)?;
1195            Ok((TpmuSymKeyBits::Camellia(value), rest))
1196        }
1197        TpmAlgId::Xor => {
1198            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
1199            Ok((TpmuSymKeyBits::Xor(value), rest))
1200        }
1201        TpmAlgId::Null => Ok((TpmuSymKeyBits::Null, buf)),
1202        _ => Err(TpmDeviceError::InvalidResponse),
1203    }
1204}
1205
1206fn parse_tpmu_sym_mode(
1207    algorithm: TpmAlgId,
1208    buf: &[u8],
1209) -> Result<(TpmuSymMode, &[u8]), TpmDeviceError> {
1210    match algorithm {
1211        TpmAlgId::Aes => {
1212            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
1213            Ok((TpmuSymMode::Aes(value), rest))
1214        }
1215        TpmAlgId::Sm4 => {
1216            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
1217            Ok((TpmuSymMode::Sm4(value), rest))
1218        }
1219        TpmAlgId::Camellia => {
1220            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
1221            Ok((TpmuSymMode::Camellia(value), rest))
1222        }
1223        TpmAlgId::Xor => {
1224            let (value, rest) = parse_field_value::<TpmAlgId>(buf)?;
1225            Ok((TpmuSymMode::Xor(value), rest))
1226        }
1227        TpmAlgId::Null => Ok((TpmuSymMode::Null, buf)),
1228        _ => Err(TpmDeviceError::InvalidResponse),
1229    }
1230}
1231
1232fn parse_tpmt_sym_def(buf: &[u8]) -> Result<(TpmtSymDefObject, &[u8]), TpmDeviceError> {
1233    let (algorithm, buf) = parse_field_value::<TpmAlgId>(buf)?;
1234    if algorithm == TpmAlgId::Null {
1235        return Ok((TpmtSymDefObject::default(), buf));
1236    }
1237
1238    let (key_bits, buf) = parse_tpmu_sym_key_bits(algorithm, buf)?;
1239    let (mode, rest) = parse_tpmu_sym_mode(algorithm, buf)?;
1240
1241    Ok((
1242        TpmtSymDefObject {
1243            algorithm,
1244            key_bits,
1245            mode,
1246        },
1247        rest,
1248    ))
1249}
1250
1251fn parse_tpmu_public_parms(
1252    object_type: TpmAlgId,
1253    buf: &[u8],
1254) -> Result<(TpmuPublicParms, &[u8]), TpmDeviceError> {
1255    match object_type {
1256        TpmAlgId::KeyedHash => {
1257            let (scheme, rest) = parse_tpmt_keyedhash_scheme(buf)?;
1258            Ok((
1259                TpmuPublicParms::KeyedHash(TpmsKeyedhashParms { scheme }),
1260                rest,
1261            ))
1262        }
1263        TpmAlgId::SymCipher => {
1264            let (sym, rest) = parse_tpmt_sym_def(buf)?;
1265            Ok((TpmuPublicParms::SymCipher(TpmsSymcipherParms { sym }), rest))
1266        }
1267        TpmAlgId::Rsa => {
1268            let (symmetric, buf) = parse_tpmt_sym_def(buf)?;
1269            let (scheme, buf) = parse_tpmt_rsa_scheme(buf)?;
1270            let (key_bits, buf) = parse_wire_copy::<TpmUint16>(buf)?;
1271            let (exponent, rest) = parse_wire_copy::<TpmUint32>(buf)?;
1272            Ok((
1273                TpmuPublicParms::Rsa(TpmsRsaParms {
1274                    symmetric,
1275                    scheme,
1276                    key_bits,
1277                    exponent,
1278                }),
1279                rest,
1280            ))
1281        }
1282        TpmAlgId::Ecc => {
1283            let (symmetric, buf) = parse_tpmt_sym_def(buf)?;
1284            let (scheme, buf) = parse_tpmt_ecc_scheme(buf)?;
1285            let (curve_id, buf) = parse_field_value::<TpmEccCurve>(buf)?;
1286            let (kdf, rest) = parse_tpmt_kdf_scheme(buf)?;
1287            Ok((
1288                TpmuPublicParms::Ecc(TpmsEccParms {
1289                    symmetric,
1290                    scheme,
1291                    curve_id,
1292                    kdf,
1293                }),
1294                rest,
1295            ))
1296        }
1297        TpmAlgId::Null => Ok((TpmuPublicParms::Null, buf)),
1298        _ => Err(TpmDeviceError::InvalidResponse),
1299    }
1300}
1301
1302fn parse_tpms_ecc_point(buf: &[u8]) -> Result<(TpmsEccPoint, &[u8]), TpmDeviceError> {
1303    let (x, buf): (Tpm2bEccParameter, _) = parse_tpm2b_buffer(buf)?;
1304    let (y, rest): (Tpm2bEccParameter, _) = parse_tpm2b_buffer(buf)?;
1305
1306    Ok((TpmsEccPoint { x, y }, rest))
1307}
1308
1309fn parse_tpmu_public_id(
1310    object_type: TpmAlgId,
1311    buf: &[u8],
1312) -> Result<(TpmuPublicId, &[u8]), TpmDeviceError> {
1313    match object_type {
1314        TpmAlgId::KeyedHash => {
1315            let (value, rest): (Tpm2bDigest, _) = parse_tpm2b_buffer(buf)?;
1316            Ok((TpmuPublicId::KeyedHash(value), rest))
1317        }
1318        TpmAlgId::SymCipher => {
1319            let (value, rest): (Tpm2bSymKey, _) = parse_tpm2b_buffer(buf)?;
1320            Ok((TpmuPublicId::SymCipher(value), rest))
1321        }
1322        TpmAlgId::Rsa => {
1323            let (value, rest): (Tpm2bPublicKeyRsa, _) = parse_tpm2b_buffer(buf)?;
1324            Ok((TpmuPublicId::Rsa(value), rest))
1325        }
1326        TpmAlgId::Ecc => {
1327            let (value, rest) = parse_tpms_ecc_point(buf)?;
1328            Ok((TpmuPublicId::Ecc(value), rest))
1329        }
1330        TpmAlgId::Null => Ok((TpmuPublicId::Null, buf)),
1331        _ => Err(TpmDeviceError::InvalidResponse),
1332    }
1333}
1334
1335fn parse_tpmt_public(buf: &[u8]) -> Result<(TpmtPublic, &[u8]), TpmDeviceError> {
1336    let (object_type, buf) = parse_field_value::<TpmAlgId>(buf)?;
1337    let (name_alg, buf) = parse_field_value::<TpmAlgId>(buf)?;
1338    let (object_attributes, buf) = parse_field_value::<TpmaObject>(buf)?;
1339    let (auth_policy, buf): (Tpm2bDigest, _) = parse_tpm2b_buffer(buf)?;
1340    let (parameters, buf) = parse_tpmu_public_parms(object_type, buf)?;
1341    let (unique, rest) = parse_tpmu_public_id(object_type, buf)?;
1342
1343    Ok((
1344        TpmtPublic {
1345            object_type,
1346            name_alg,
1347            object_attributes,
1348            auth_policy,
1349            parameters,
1350            unique,
1351        },
1352        rest,
1353    ))
1354}
1355
1356fn parse_tpm2b_public(buf: &[u8]) -> Result<(TpmtPublic, &[u8]), TpmDeviceError> {
1357    let (size, buf) = parse_wire_copy::<TpmUint16>(buf)?;
1358    let size = usize::from(size.value());
1359    if buf.len() < size {
1360        return Err(TpmDeviceError::InvalidResponse);
1361    }
1362
1363    let (public, rest) = buf.split_at(size);
1364    let (public, public_rest) = parse_tpmt_public(public)?;
1365    ensure_empty(public_rest)?;
1366
1367    Ok((public, rest))
1368}
1369
1370fn parse_tpms_context(buf: &[u8]) -> Result<(TpmsContext, &[u8]), TpmDeviceError> {
1371    let (sequence, buf) = parse_wire_copy::<TpmUint64>(buf)?;
1372    let (saved_handle, buf) = parse_wire_copy::<TpmHandle>(buf)?;
1373    let (hierarchy, buf) = parse_field_value::<TpmRh>(buf)?;
1374    let (context_blob, rest): (TpmBuffer<TPM_MAX_COMMAND_SIZE>, _) = parse_tpm2b_buffer(buf)?;
1375
1376    Ok((
1377        TpmsContext {
1378            sequence,
1379            saved_handle,
1380            hierarchy,
1381            context_blob,
1382        },
1383        rest,
1384    ))
1385}
1386
1387fn parse_list<'a, T, const CAPACITY: usize>(
1388    buf: &'a [u8],
1389    mut parse_item: impl FnMut(&'a [u8]) -> Result<(T, &'a [u8]), TpmDeviceError>,
1390) -> Result<(TpmList<T, CAPACITY>, &'a [u8]), TpmDeviceError>
1391where
1392    T: Copy,
1393{
1394    let (count, mut cursor) = parse_wire_copy::<TpmUint32>(buf)?;
1395    let mut list = TpmList::<T, CAPACITY>::new();
1396
1397    for _ in 0..count.value() {
1398        let (item, rest) = parse_item(cursor)?;
1399        list.try_push(item).map_err(TpmDeviceError::Unmarshal)?;
1400        cursor = rest;
1401    }
1402
1403    Ok((list, cursor))
1404}
1405
1406fn parse_tpms_alg_property(buf: &[u8]) -> Result<(TpmsAlgProperty, &[u8]), TpmDeviceError> {
1407    let (alg, buf) = parse_field_value::<TpmAlgId>(buf)?;
1408    let (alg_properties, rest) = parse_field_value::<TpmaAlgorithm>(buf)?;
1409
1410    Ok((
1411        TpmsAlgProperty {
1412            alg,
1413            alg_properties,
1414        },
1415        rest,
1416    ))
1417}
1418
1419fn parse_tpms_tagged_property(buf: &[u8]) -> Result<(TpmsTaggedProperty, &[u8]), TpmDeviceError> {
1420    let (property, buf) = parse_field_value::<TpmPt>(buf)?;
1421    let (value, rest) = parse_wire_copy::<TpmUint32>(buf)?;
1422
1423    Ok((TpmsTaggedProperty { property, value }, rest))
1424}
1425
1426fn parse_tpms_pcr_selection(buf: &[u8]) -> Result<(TpmsPcrSelection, &[u8]), TpmDeviceError> {
1427    let (hash, buf) = parse_field_value::<TpmAlgId>(buf)?;
1428    let (pcr_select, rest) =
1429        <TpmsPcrSelect as TpmField>::cast_prefix_field(buf).map_err(TpmDeviceError::Unmarshal)?;
1430    let pcr_select = TpmsPcrSelect::try_from(pcr_select).map_err(TpmDeviceError::Unmarshal)?;
1431
1432    Ok((TpmsPcrSelection { hash, pcr_select }, rest))
1433}
1434
1435fn parse_capability_data(buf: &[u8]) -> Result<(TpmsCapabilityData, &[u8]), TpmDeviceError> {
1436    let (capability, buf) = parse_field_value::<TpmCap>(buf)?;
1437    let (data, rest) = match capability {
1438        TpmCap::Algs => {
1439            let (list, rest) = parse_list::<TpmsAlgProperty, 64>(buf, parse_tpms_alg_property)?;
1440            (TpmuCapabilities::Algs(list), rest)
1441        }
1442        TpmCap::Handles => {
1443            let (list, rest) = parse_list::<TpmHandle, 128>(buf, parse_wire_copy::<TpmHandle>)?;
1444            (TpmuCapabilities::Handles(list), rest)
1445        }
1446        TpmCap::Pcrs => {
1447            let (list, rest) = parse_list::<TpmsPcrSelection, 8>(buf, parse_tpms_pcr_selection)?;
1448            (TpmuCapabilities::Pcrs(list), rest)
1449        }
1450        TpmCap::Commands => {
1451            let (list, rest) = parse_list::<TpmaCc, 256>(buf, parse_field_value::<TpmaCc>)?;
1452            (TpmuCapabilities::Commands(list), rest)
1453        }
1454        TpmCap::TpmProperties => {
1455            let (list, rest) =
1456                parse_list::<TpmsTaggedProperty, 64>(buf, parse_tpms_tagged_property)?;
1457            (TpmuCapabilities::TpmProperties(list), rest)
1458        }
1459        TpmCap::EccCurves => {
1460            let (list, rest) =
1461                parse_list::<TpmEccCurve, 64>(buf, parse_field_value::<TpmEccCurve>)?;
1462            (TpmuCapabilities::EccCurves(list), rest)
1463        }
1464        TpmCap::PpCommands | TpmCap::AuditCommands | TpmCap::AuthPolicies | TpmCap::Act => {
1465            return Err(TpmDeviceError::InvalidResponse);
1466        }
1467        _ => return Err(TpmDeviceError::InvalidResponse),
1468    };
1469
1470    Ok((TpmsCapabilityData { capability, data }, rest))
1471}
1472
1473/// A builder for creating a TPM policy session.
1474pub struct TpmPolicySessionBuilder {
1475    bind: TpmHandle,
1476    tpm_key: TpmHandle,
1477    nonce_caller: Option<Tpm2bNonce>,
1478    encrypted_salt: Option<Tpm2bEncryptedSecret>,
1479    session_type: TpmSe,
1480    symmetric: TpmtSymDefObject,
1481    auth_hash: TpmAlgId,
1482}
1483
1484impl Default for TpmPolicySessionBuilder {
1485    fn default() -> Self {
1486        Self {
1487            bind: (TpmRh::Null as u32).into(),
1488            tpm_key: (TpmRh::Null as u32).into(),
1489            nonce_caller: None,
1490            encrypted_salt: None,
1491            session_type: TpmSe::Policy,
1492            symmetric: TpmtSymDefObject::default(),
1493            auth_hash: TpmAlgId::Sha256,
1494        }
1495    }
1496}
1497
1498impl TpmPolicySessionBuilder {
1499    #[must_use]
1500    pub fn new() -> Self {
1501        Self::default()
1502    }
1503
1504    #[must_use]
1505    pub fn with_bind(mut self, bind: TpmHandle) -> Self {
1506        self.bind = bind;
1507        self
1508    }
1509
1510    #[must_use]
1511    pub fn with_tpm_key(mut self, tpm_key: TpmHandle) -> Self {
1512        self.tpm_key = tpm_key;
1513        self
1514    }
1515
1516    #[must_use]
1517    pub fn with_nonce_caller(mut self, nonce: Tpm2bNonce) -> Self {
1518        self.nonce_caller = Some(nonce);
1519        self
1520    }
1521
1522    #[must_use]
1523    pub fn with_encrypted_salt(mut self, salt: Tpm2bEncryptedSecret) -> Self {
1524        self.encrypted_salt = Some(salt);
1525        self
1526    }
1527
1528    #[must_use]
1529    pub fn with_session_type(mut self, session_type: TpmSe) -> Self {
1530        self.session_type = session_type;
1531        self
1532    }
1533
1534    #[must_use]
1535    pub fn with_symmetric(mut self, symmetric: TpmtSymDefObject) -> Self {
1536        self.symmetric = symmetric;
1537        self
1538    }
1539
1540    #[must_use]
1541    pub fn with_auth_hash(mut self, auth_hash: TpmAlgId) -> Self {
1542        self.auth_hash = auth_hash;
1543        self
1544    }
1545
1546    /// Opens the policy session on the provided device.
1547    ///
1548    /// # Errors
1549    ///
1550    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch) if
1551    /// the TPM response is unexpected.
1552    /// Returns [`Unmarshal`](crate::TpmDeviceError::Unmarshal) when unmarshal
1553    /// operation on TPM protocol compliant data fails.
1554    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants depending
1555    /// on function.
1556    pub fn open(self, device: &mut TpmDevice) -> Result<TpmPolicySession, TpmDeviceError> {
1557        let nonce_caller = if let Some(nonce) = self.nonce_caller {
1558            nonce
1559        } else {
1560            let digest_len = TpmHash::try_from(self.auth_hash)
1561                .map_err(|_| TpmDeviceError::UnsupportedAlgorithm(self.auth_hash))?
1562                .size();
1563            let mut nonce_bytes = vec![0; digest_len];
1564            thread_rng().fill_bytes(&mut nonce_bytes);
1565            Tpm2bNonce::try_from(nonce_bytes.as_slice()).map_err(TpmDeviceError::Unmarshal)?
1566        };
1567
1568        let cmd = TpmStartAuthSessionCommand {
1569            nonce_caller,
1570            encrypted_salt: self.encrypted_salt.unwrap_or_default(),
1571            session_type: self.session_type,
1572            symmetric: self.symmetric,
1573            auth_hash: self.auth_hash,
1574            handles: [self.tpm_key, self.bind],
1575        };
1576
1577        let response = device.transmit(&cmd, TpmDevice::NO_SESSIONS)?;
1578        let (handles, parameters) = response_parts(response, 1)?;
1579        let (handle, rest) = parse_wire_copy::<TpmHandle>(handles)?;
1580        ensure_empty(rest)?;
1581        let (nonce_tpm, rest): (Tpm2bNonce, _) = parse_tpm2b_buffer(parameters)?;
1582        ensure_empty(rest)?;
1583
1584        Ok(TpmPolicySession {
1585            handle,
1586            attributes: TpmaSession::CONTINUE_SESSION,
1587            hash_alg: self.auth_hash,
1588            nonce_tpm,
1589        })
1590    }
1591}
1592
1593/// Represents an active TPM policy session.
1594#[derive(Debug, Clone)]
1595pub struct TpmPolicySession {
1596    handle: TpmHandle,
1597    attributes: TpmaSession,
1598    hash_alg: TpmAlgId,
1599    nonce_tpm: Tpm2bNonce,
1600}
1601
1602impl TpmPolicySession {
1603    /// Creates a new builder for `TpmPolicySession`.
1604    #[must_use]
1605    pub fn builder() -> TpmPolicySessionBuilder {
1606        TpmPolicySessionBuilder::new()
1607    }
1608
1609    /// Returns the session handle.
1610    #[must_use]
1611    pub fn handle(&self) -> TpmHandle {
1612        self.handle
1613    }
1614
1615    /// Returns the session attributes.
1616    #[must_use]
1617    pub fn attributes(&self) -> TpmaSession {
1618        self.attributes
1619    }
1620
1621    /// Returns the hash algorithm used by the session.
1622    #[must_use]
1623    pub fn hash_alg(&self) -> TpmAlgId {
1624        self.hash_alg
1625    }
1626
1627    /// Returns the nonce generated by the TPM.
1628    #[must_use]
1629    pub fn nonce_tpm(&self) -> &Tpm2bNonce {
1630        &self.nonce_tpm
1631    }
1632
1633    /// Applies a list of policy commands to this session.
1634    ///
1635    /// This method iterates through the provided commands, updates the first handle
1636    /// of each command (or second for `PolicySecret`) to point to this session,
1637    /// and transmits them to the device.
1638    ///
1639    /// # Errors
1640    ///
1641    /// Returns [`InvalidCc`](crate::TpmDeviceError::InvalidCc) when a command is not
1642    /// a supported policy command.
1643    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
1644    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
1645    pub fn run(
1646        &self,
1647        device: &mut TpmDevice,
1648        commands: impl IntoIterator<Item = (TpmCommand, TpmAuthCommands)>,
1649    ) -> Result<(), TpmDeviceError> {
1650        for (mut command_body, auth_sessions) in commands {
1651            match &mut command_body {
1652                TpmCommand::PolicyPcr(cmd) => cmd.handles[0] = self.handle,
1653                TpmCommand::PolicyOr(cmd) => cmd.handles[0] = self.handle,
1654                TpmCommand::PolicyRestart(cmd) => {
1655                    cmd.handles[0] = self.handle;
1656                }
1657                TpmCommand::PolicySecret(cmd) => {
1658                    cmd.handles[1] = self.handle;
1659                }
1660                _ => {
1661                    return Err(TpmDeviceError::InvalidCc(command_body.cc()));
1662                }
1663            }
1664            device.transmit(&command_body, auth_sessions.as_ref())?;
1665        }
1666        Ok(())
1667    }
1668
1669    /// Flushes the session context from the TPM.
1670    ///
1671    /// # Errors
1672    ///
1673    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
1674    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
1675    pub fn flush(&self, device: &mut TpmDevice) -> Result<(), TpmDeviceError> {
1676        device.flush_context(self.handle)
1677    }
1678}
1679
1680#[cfg(test)]
1681mod tests {
1682    use super::*;
1683    use std::cell::RefCell;
1684    use std::io::Cursor;
1685    use std::rc::Rc;
1686
1687    struct Duplex {
1688        input: Cursor<Vec<u8>>,
1689        output: Rc<RefCell<Vec<u8>>>,
1690    }
1691
1692    impl Read for Duplex {
1693        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1694            self.input.read(buf)
1695        }
1696    }
1697
1698    impl Write for Duplex {
1699        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1700            self.output.borrow_mut().extend_from_slice(buf);
1701            Ok(buf.len())
1702        }
1703
1704        fn flush(&mut self) -> std::io::Result<()> {
1705            Ok(())
1706        }
1707    }
1708
1709    fn frame(body: &[u8]) -> Vec<u8> {
1710        let size = u32::try_from(TPM_HEADER_SIZE + body.len()).unwrap();
1711        let mut frame = vec![0x80, 0x01];
1712        frame.extend_from_slice(&size.to_be_bytes());
1713        frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
1714        frame.extend_from_slice(body);
1715        frame
1716    }
1717
1718    #[test]
1719    fn read_frame_reads_exactly_one_frame() {
1720        let first = frame(&[0xAA, 0xBB]);
1721        let mut bytes = first.clone();
1722        bytes.extend_from_slice(&frame(&[0xCC]));
1723        let mut reader = Cursor::new(bytes);
1724
1725        let mut buf = Vec::new();
1726        read_frame(&mut reader, &mut buf).unwrap();
1727
1728        assert_eq!(buf, first);
1729    }
1730
1731    #[test]
1732    fn write_then_read_round_trips() {
1733        let expected = frame(&[1, 2, 3, 4]);
1734        let mut stream = Cursor::new(Vec::new());
1735        write_frame(&mut stream, &expected).unwrap();
1736        stream.set_position(0);
1737
1738        let mut buf = Vec::new();
1739        read_frame(&mut stream, &mut buf).unwrap();
1740
1741        assert_eq!(buf, expected);
1742    }
1743
1744    #[test]
1745    fn stream_transport_sends_and_receives() {
1746        let response = frame(&[0x11, 0x22]);
1747        let command = frame(&[0x33]);
1748        let output = Rc::new(RefCell::new(Vec::new()));
1749        let mut transport = TpmStreamTransport::new(Duplex {
1750            input: Cursor::new(response.clone()),
1751            output: Rc::clone(&output),
1752        });
1753
1754        transport.send(&command).unwrap();
1755        let mut buf = Vec::new();
1756        transport.recv(&mut buf).unwrap();
1757
1758        assert_eq!(buf, response);
1759        assert_eq!(*output.borrow(), command);
1760    }
1761
1762    #[test]
1763    fn responder_serves_commands_until_disconnect() {
1764        let command = frame(&[0x01]);
1765        let response = frame(&[0x02, 0x03]);
1766        let output = Rc::new(RefCell::new(Vec::new()));
1767        let transport = TpmStreamTransport::new(Duplex {
1768            input: Cursor::new(command.clone()),
1769            output: Rc::clone(&output),
1770        });
1771        let mut responder = TpmResponder::new(Box::new(transport));
1772
1773        let reply = response.clone();
1774        let mut served = 0;
1775        responder
1776            .serve(|cmd| {
1777                assert_eq!(cmd, command.as_slice());
1778                served += 1;
1779                reply.clone()
1780            })
1781            .unwrap();
1782
1783        assert_eq!(served, 1);
1784        assert_eq!(*output.borrow(), response);
1785    }
1786
1787    #[test]
1788    fn frame_size_rejects_short_header() {
1789        assert!(frame_size(&[0x80, 0x01, 0x00]).is_err());
1790    }
1791
1792    #[test]
1793    fn read_frame_reports_unexpected_eof_on_truncation() {
1794        let mut reader = Cursor::new(vec![0x80, 0x01]);
1795        let mut buf = Vec::new();
1796        assert_eq!(
1797            read_frame(&mut reader, &mut buf),
1798            Err(TpmDeviceError::UnexpectedEof)
1799        );
1800    }
1801}