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    TpmError, TpmWriter,
27    basic::{TpmHandle, TpmUint32},
28    constant::{MAX_HANDLES, TPM_MAX_COMMAND_SIZE},
29    data::{
30        Tpm2bEncryptedSecret, Tpm2bName, Tpm2bNonce, TpmAlgId, TpmCap, TpmCc, TpmEccCurve, TpmHt,
31        TpmPt, TpmRc, TpmRcBase, TpmRh, TpmSe, TpmSt, TpmaSession, TpmsAlgProperty,
32        TpmsAuthCommand, TpmsCapabilityData, TpmsContext, TpmsPcrSelect, TpmsPcrSelection,
33        TpmtPublic, TpmtSymDefObject, TpmuCapabilities,
34    },
35    frame::{
36        TpmAuthCommands, TpmCommandValue as TpmCommand, TpmContextLoadCommand,
37        TpmContextLoadResponse, TpmContextSaveCommand, TpmContextSaveResponse,
38        TpmFlushContextCommand, TpmFrame, TpmGetCapabilityCommand, TpmGetCapabilityResponse,
39        TpmReadPublicCommand, TpmReadPublicResponse, TpmResponse, TpmResponseOutcome,
40        TpmResponseView, TpmStartAuthSessionCommand, TpmStartAuthSessionResponse,
41        tpm_marshal_command,
42    },
43};
44use tracing::{debug, trace};
45
46/// Errors that can occur when talking to a TPM device.
47///
48/// `Display` renders only the variant name as lowercase space-separated words
49/// (e.g. `UnexpectedEof` becomes `unexpected eof`).
50#[derive(Debug, strum::AsRefStr)]
51#[strum(serialize_all = "title_case")]
52#[non_exhaustive]
53pub enum TpmDeviceError {
54    /// The TPM device is already mutably borrowed.
55    AlreadyBorrowed,
56
57    /// The requested capability is not available from the TPM.
58    CapabilityMissing(TpmCap),
59
60    /// The operation was interrupted by the caller.
61    Interrupted,
62
63    /// An invalid command code was used.
64    InvalidCc(tpm2_protocol::data::TpmCc),
65
66    /// The TPM returned an invalid or malformed response.
67    InvalidResponse,
68
69    /// An I/O error occurred when accessing the TPM device.
70    Io(std::io::Error),
71
72    /// Marshaling a TPM protocol encoded object failed.
73    Marshal(TpmError),
74
75    /// No TPM device is available.
76    NotAvailable,
77
78    /// No PCR banks are available on the TPM.
79    PcrBanksNotAvailable,
80
81    /// The PCR selection masks differ between active banks.
82    PcrBankSelectionMismatch,
83
84    /// The TPM response did not match the expected command code.
85    ResponseMismatch(TpmCc),
86
87    /// The TPM command timed out.
88    Timeout,
89
90    /// The TPM returned an error code.
91    TpmRc(TpmRc),
92
93    /// Trailing data after the response.
94    TrailingData,
95
96    /// Unmarshaling a TPM protocol encoded object failed.
97    Unmarshal(TpmError),
98
99    /// An unexpected end-of-file was encountered.
100    UnexpectedEof,
101
102    /// The requested algorithm is not supported.
103    UnsupportedAlgorithm(TpmAlgId),
104}
105
106impl fmt::Display for TpmDeviceError {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(f, "{}", self.as_ref().to_lowercase())
109    }
110}
111
112impl std::error::Error for TpmDeviceError {}
113
114impl PartialEq for TpmDeviceError {
115    fn eq(&self, other: &Self) -> bool {
116        match (self, other) {
117            (Self::CapabilityMissing(a), Self::CapabilityMissing(b)) => a == b,
118            (Self::InvalidCc(a), Self::InvalidCc(b))
119            | (Self::ResponseMismatch(a), Self::ResponseMismatch(b)) => a == b,
120            (Self::Io(a), Self::Io(b)) => a.kind() == b.kind(),
121            (Self::Marshal(a), Self::Marshal(b)) | (Self::Unmarshal(a), Self::Unmarshal(b)) => {
122                a == b
123            }
124            (Self::TpmRc(a), Self::TpmRc(b)) => a == b,
125            (Self::UnsupportedAlgorithm(a), Self::UnsupportedAlgorithm(b)) => a == b,
126            (Self::AlreadyBorrowed, Self::AlreadyBorrowed)
127            | (Self::Interrupted, Self::Interrupted)
128            | (Self::InvalidResponse, Self::InvalidResponse)
129            | (Self::NotAvailable, Self::NotAvailable)
130            | (Self::PcrBanksNotAvailable, Self::PcrBanksNotAvailable)
131            | (Self::PcrBankSelectionMismatch, Self::PcrBankSelectionMismatch)
132            | (Self::Timeout, Self::Timeout)
133            | (Self::TrailingData, Self::TrailingData)
134            | (Self::UnexpectedEof, Self::UnexpectedEof) => true,
135            _ => false,
136        }
137    }
138}
139
140impl Eq for TpmDeviceError {}
141
142impl From<TpmRc> for TpmDeviceError {
143    fn from(rc: TpmRc) -> Self {
144        Self::TpmRc(rc)
145    }
146}
147
148impl From<std::io::Error> for TpmDeviceError {
149    fn from(err: std::io::Error) -> Self {
150        Self::Io(err)
151    }
152}
153
154impl From<nix::Error> for TpmDeviceError {
155    fn from(err: nix::Error) -> Self {
156        Self::Io(std::io::Error::from_raw_os_error(err as i32))
157    }
158}
159
160/// Executes a closure with a mutable reference to a `TpmDevice`.
161///
162/// This helper function centralizes the boilerplate for safely acquiring a
163/// mutable borrow of a `TpmDevice` from the shared `Rc<RefCell<...>>`.
164///
165/// # Errors
166///
167/// Returns [`NotAvailable`](crate::TpmDeviceError::NotAvailable) when no device
168/// is present.
169/// Returns [`AlreadyBorrowed`](crate::TpmDeviceError::AlreadyBorrowed) when the
170/// device is already mutably borrowed.
171/// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants depending
172/// on function.
173pub fn with_device<F, T, E>(device: Option<&Rc<RefCell<TpmDevice>>>, function: F) -> Result<T, E>
174where
175    F: FnOnce(&mut TpmDevice) -> Result<T, E>,
176    E: From<TpmDeviceError>,
177{
178    let device_rc = device.ok_or(TpmDeviceError::NotAvailable)?;
179    let mut device_guard = device_rc
180        .try_borrow_mut()
181        .map_err(|_| TpmDeviceError::AlreadyBorrowed)?;
182    function(&mut device_guard)
183}
184
185/// A bidirectional, frame-oriented transport for marshaled TPM frames.
186///
187/// The two endpoints of a TPM exchange are symmetric on the wire: a host sends
188/// command frames and receives response frames, while a responder such as an
189/// emulator does the reverse. A `TpmTransport` therefore moves whole frames in
190/// either direction, decoupling both ends from any concrete byte stream.
191pub trait TpmTransport {
192    /// Sends one complete marshaled frame.
193    ///
194    /// # Errors
195    ///
196    /// Returns [`Io`](crate::TpmDeviceError::Io) when writing the frame fails.
197    fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError>;
198
199    /// Receives one complete frame into `buf`, replacing its contents.
200    ///
201    /// # Errors
202    ///
203    /// Returns [`Io`](crate::TpmDeviceError::Io) when reading fails,
204    /// [`Timeout`](crate::TpmDeviceError::Timeout) when no complete frame
205    /// arrives in time, [`Interrupted`](crate::TpmDeviceError::Interrupted)
206    /// when cancellation is requested, or
207    /// [`InvalidResponse`](crate::TpmDeviceError::InvalidResponse) /
208    /// [`TrailingData`](crate::TpmDeviceError::TrailingData) when the frame
209    /// envelope is malformed.
210    fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError>;
211}
212
213const TPM_HEADER_SIZE: usize = 10;
214
215/// Returns the total frame length declared by a TPM frame header.
216///
217/// # Errors
218///
219/// Returns [`InvalidResponse`](crate::TpmDeviceError::InvalidResponse) when the
220/// header is shorter than its size field or declares a size outside the range
221/// `[TPM_HEADER_SIZE, TPM_MAX_COMMAND_SIZE]`.
222fn frame_size(header: &[u8]) -> Result<usize, TpmDeviceError> {
223    let Some(size_bytes) = header.get(2..6) else {
224        return Err(TpmDeviceError::InvalidResponse);
225    };
226    let Ok(size_bytes): Result<[u8; 4], _> = size_bytes.try_into() else {
227        return Err(TpmDeviceError::InvalidResponse);
228    };
229    let size = u32::from_be_bytes(size_bytes) as usize;
230    if !(TPM_HEADER_SIZE..=TPM_MAX_COMMAND_SIZE).contains(&size) {
231        return Err(TpmDeviceError::InvalidResponse);
232    }
233    Ok(size)
234}
235
236fn fill_exact<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<(), TpmDeviceError> {
237    match reader.read_exact(buf) {
238        Ok(()) => Ok(()),
239        Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
240            Err(TpmDeviceError::UnexpectedEof)
241        }
242        Err(e) => Err(TpmDeviceError::Io(e)),
243    }
244}
245
246/// Reads one complete TPM frame from a blocking stream into `buf`.
247///
248/// The header is read first to learn the frame's declared size, then exactly
249/// that many bytes (header included) are read; `buf` is cleared beforehand. The
250/// framing is identical for command and response frames, so this serves a host
251/// reading responses and a responder reading commands alike.
252///
253/// # Errors
254///
255/// Returns [`UnexpectedEof`](crate::TpmDeviceError::UnexpectedEof) when the
256/// stream ends before a complete frame, [`Io`](crate::TpmDeviceError::Io) on any
257/// other read failure, or
258/// [`InvalidResponse`](crate::TpmDeviceError::InvalidResponse) when the header
259/// declares an out-of-range size.
260pub fn read_frame<R: Read>(reader: &mut R, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
261    buf.clear();
262    buf.resize(TPM_HEADER_SIZE, 0);
263    fill_exact(reader, buf)?;
264
265    let size = frame_size(buf)?;
266    buf.resize(size, 0);
267    fill_exact(reader, &mut buf[TPM_HEADER_SIZE..])?;
268
269    Ok(())
270}
271
272/// Writes one complete marshaled TPM frame to a blocking stream and flushes it.
273///
274/// # Errors
275///
276/// Returns [`Io`](crate::TpmDeviceError::Io) when writing or flushing fails.
277pub fn write_frame<W: Write>(writer: &mut W, frame: &[u8]) -> Result<(), TpmDeviceError> {
278    writer.write_all(frame).map_err(TpmDeviceError::Io)?;
279    writer.flush().map_err(TpmDeviceError::Io)?;
280    Ok(())
281}
282
283/// A [`TpmTransport`] over any blocking byte stream.
284///
285/// Suitable for TPM endpoints reached over TCP (such as a software TPM),
286/// Unix-domain sockets, or in-memory pipes.
287pub struct TpmStreamTransport<S: Read + Write> {
288    stream: S,
289}
290
291impl<S: Read + Write> TpmStreamTransport<S> {
292    /// Wraps a stream as a transport.
293    #[must_use]
294    pub fn new(stream: S) -> Self {
295        Self { stream }
296    }
297
298    /// Consumes the transport and returns the underlying stream.
299    #[must_use]
300    pub fn into_inner(self) -> S {
301        self.stream
302    }
303}
304
305impl<S: Read + Write> TpmTransport for TpmStreamTransport<S> {
306    fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
307        write_frame(&mut self.stream, frame)
308    }
309
310    fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
311        read_frame(&mut self.stream, buf)
312    }
313}
314
315/// A [`TpmTransport`] backed by a Linux TPM character device.
316pub struct TpmPosixDevice {
317    file: File,
318    interrupted: Box<dyn Fn() -> bool>,
319    timeout: Duration,
320}
321
322impl TpmPosixDevice {
323    /// Creates a new builder for a `TpmPosixDevice`.
324    #[must_use]
325    pub fn builder() -> TpmPosixDeviceBuilder {
326        TpmPosixDeviceBuilder::default()
327    }
328
329    fn receive(&mut self, buf: &mut [u8]) -> Result<usize, TpmDeviceError> {
330        let fd = self.file.as_fd();
331        let mut fds = [PollFd::new(fd, PollFlags::POLLIN)];
332
333        let num_events = match poll(&mut fds, 100u16) {
334            Ok(num) => num,
335            Err(nix::Error::EINTR) => return Ok(0),
336            Err(e) => return Err(e.into()),
337        };
338
339        if num_events == 0 {
340            return Ok(0);
341        }
342
343        let revents = fds[0].revents().unwrap_or(PollFlags::empty());
344
345        if revents.intersects(PollFlags::POLLERR | PollFlags::POLLNVAL) {
346            return Err(TpmDeviceError::UnexpectedEof);
347        }
348
349        if revents.contains(PollFlags::POLLIN) {
350            match self.file.read(buf) {
351                Ok(0) => Err(TpmDeviceError::UnexpectedEof),
352                Ok(n) => Ok(n),
353                Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
354                Err(e) if e.kind() == std::io::ErrorKind::Interrupted => Ok(0),
355                Err(e) => Err(e.into()),
356            }
357        } else if revents.contains(PollFlags::POLLHUP) {
358            Err(TpmDeviceError::UnexpectedEof)
359        } else {
360            Ok(0)
361        }
362    }
363}
364
365impl TpmTransport for TpmPosixDevice {
366    fn send(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
367        self.file.write_all(frame)?;
368        self.file.flush()?;
369        Ok(())
370    }
371
372    fn recv(&mut self, buf: &mut Vec<u8>) -> Result<(), TpmDeviceError> {
373        buf.clear();
374        let start_time = Instant::now();
375        let mut total_size: Option<usize> = None;
376        let mut temp_buf = [0u8; 1024];
377
378        loop {
379            if (self.interrupted)() {
380                return Err(TpmDeviceError::Interrupted);
381            }
382            if start_time.elapsed() > self.timeout {
383                return Err(TpmDeviceError::Timeout);
384            }
385
386            let n = self.receive(&mut temp_buf)?;
387            if n > 0 {
388                buf.extend_from_slice(&temp_buf[..n]);
389            }
390
391            if total_size.is_none() && buf.len() >= TPM_HEADER_SIZE {
392                total_size = Some(frame_size(buf)?);
393            }
394
395            if let Some(size) = total_size {
396                if buf.len() == size {
397                    break;
398                }
399                if buf.len() > size {
400                    return Err(TpmDeviceError::TrailingData);
401                }
402            }
403        }
404
405        Ok(())
406    }
407}
408
409/// A builder for constructing a [`TpmPosixDevice`].
410pub struct TpmPosixDeviceBuilder {
411    path: PathBuf,
412    timeout: Duration,
413    interrupted: Box<dyn Fn() -> bool>,
414}
415
416impl Default for TpmPosixDeviceBuilder {
417    fn default() -> Self {
418        Self {
419            path: PathBuf::from("/dev/tpmrm0"),
420            timeout: Duration::from_mins(2),
421            interrupted: Box::new(|| false),
422        }
423    }
424}
425
426impl TpmPosixDeviceBuilder {
427    /// Sets the device file path.
428    #[must_use]
429    pub fn with_path<P: AsRef<Path>>(mut self, path: P) -> Self {
430        self.path = path.as_ref().to_path_buf();
431        self
432    }
433
434    /// Sets the operation timeout.
435    #[must_use]
436    pub fn with_timeout(mut self, timeout: Duration) -> Self {
437        self.timeout = timeout;
438        self
439    }
440
441    /// Sets the interruption check callback.
442    #[must_use]
443    pub fn with_interrupted<F>(mut self, handler: F) -> Self
444    where
445        F: Fn() -> bool + 'static,
446    {
447        self.interrupted = Box::new(handler);
448        self
449    }
450
451    /// Opens the TPM character device and constructs the [`TpmPosixDevice`].
452    ///
453    /// # Errors
454    ///
455    /// Returns [`Io`](crate::TpmDeviceError::Io) when the device file cannot be
456    /// opened or when configuring the file descriptor flags fails.
457    pub fn build(self) -> Result<TpmPosixDevice, TpmDeviceError> {
458        let file = OpenOptions::new()
459            .read(true)
460            .write(true)
461            .open(&self.path)
462            .map_err(TpmDeviceError::Io)?;
463
464        let fd = file.as_raw_fd();
465        let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL)?;
466        let mut oflags = fcntl::OFlag::from_bits_truncate(flags);
467        oflags.insert(fcntl::OFlag::O_NONBLOCK);
468        fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(oflags))?;
469
470        Ok(TpmPosixDevice {
471            file,
472            interrupted: self.interrupted,
473            timeout: self.timeout,
474        })
475    }
476}
477
478pub struct TpmDevice {
479    transport: Box<dyn TpmTransport>,
480    command: Vec<u8>,
481    response: Vec<u8>,
482}
483
484impl std::fmt::Debug for TpmDevice {
485    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
486        f.debug_struct("TpmDevice").finish_non_exhaustive()
487    }
488}
489
490impl TpmDevice {
491    const NO_SESSIONS: &'static [TpmsAuthCommand] = &[];
492
493    /// Number of items requested per paginated `GetCapability` query.
494    #[allow(clippy::cast_possible_truncation)]
495    const CAPABILITY_PAGE_SIZE: u32 = MAX_HANDLES as u32;
496
497    /// Creates a `TpmDevice` driving the given transport.
498    #[must_use]
499    pub fn new(transport: Box<dyn TpmTransport>) -> Self {
500        Self {
501            transport,
502            command: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
503            response: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
504        }
505    }
506
507    /// Performs the whole TPM command transmission process.
508    ///
509    /// # Errors
510    ///
511    /// Returns [`Interrupted`](crate::TpmDeviceError::Interrupted) when the
512    /// interrupt callback requests cancellation.
513    /// Returns [`Io`](crate::TpmDeviceError::Io) when a write, flush, or read
514    /// operation on the device file fails, or when polling the device file
515    /// descriptor fails.
516    /// Returns [`Marshal`](crate::TpmDeviceError::Marshal) when marshal
517    /// operation on TPM protocol compliant data fails.
518    /// Returns [`Timeout`](crate::TpmDeviceError::Timeout) when the TPM does
519    /// not respond within the configured timeout.
520    /// Returns [`TpmRc`](crate::TpmDeviceError::TpmRc) when the TPM returns an
521    /// error code.
522    /// Returns [`Unmarshal`](crate::TpmDeviceError::Unmarshal) when unmarshal
523    /// operation on TPM protocol compliant data fails.
524    pub fn transmit<C: TpmFrame>(
525        &mut self,
526        command: &C,
527        sessions: &[TpmsAuthCommand],
528    ) -> Result<&TpmResponse, TpmDeviceError> {
529        self.prepare_command(command, sessions)?;
530        let cc = command.cc();
531
532        self.transport.send(&self.command)?;
533        self.transport.recv(&mut self.response)?;
534
535        let response = TpmResponse::cast(&self.response).map_err(TpmDeviceError::Unmarshal)?;
536        let outcome = TpmResponseView::cast(cc, response).map_err(TpmDeviceError::Unmarshal)?;
537        trace!("{} R: {}", cc, hex::encode(&self.response));
538        match outcome {
539            TpmResponseOutcome::Dispatched(_) => Ok(response),
540            TpmResponseOutcome::Rejected(rc) => Err(TpmDeviceError::TpmRc(rc)),
541        }
542    }
543
544    fn prepare_command<C: TpmFrame>(
545        &mut self,
546        command: &C,
547        sessions: &[TpmsAuthCommand],
548    ) -> Result<(), TpmDeviceError> {
549        let cc = command.cc();
550        let tag = if sessions.is_empty() {
551            TpmSt::NoSessions
552        } else {
553            TpmSt::Sessions
554        };
555
556        self.command.resize(TPM_MAX_COMMAND_SIZE, 0);
557
558        let len = {
559            let mut writer = TpmWriter::new(&mut self.command);
560            tpm_marshal_command(command, tag, sessions, &mut writer)
561                .map_err(TpmDeviceError::Marshal)?;
562            writer.len()
563        };
564        self.command.truncate(len);
565
566        trace!("{} C: {}", cc, hex::encode(&self.command));
567        Ok(())
568    }
569
570    /// Fetches a complete list of capabilities from the TPM, handling
571    /// pagination.
572    ///
573    /// # Errors
574    ///
575    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
576    /// when receiving unepected TPM response.
577    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
578    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
579    fn get_capability<T, F, N>(
580        &mut self,
581        cap: TpmCap,
582        property_start: u32,
583        count: u32,
584        mut extract: F,
585        next_prop: N,
586    ) -> Result<Vec<T>, TpmDeviceError>
587    where
588        T: Copy,
589        F: for<'a> FnMut(&'a TpmuCapabilities) -> Result<&'a [T], TpmDeviceError>,
590        N: Fn(&T) -> u32,
591    {
592        let mut results = Vec::new();
593        let mut prop = property_start;
594        loop {
595            let (more_data, cap_data) =
596                self.get_capability_page(cap, TpmUint32::from(prop), TpmUint32::from(count))?;
597            let items: &[T] = extract(&cap_data.data)?;
598            results.extend_from_slice(items);
599
600            if !more_data {
601                break;
602            }
603
604            let Some(last) = items.last() else {
605                break;
606            };
607
608            let next = next_prop(last);
609            if next <= prop {
610                break;
611            }
612            prop = next;
613        }
614        Ok(results)
615    }
616
617    /// Retrieves all algorithm properties supported by the TPM.
618    ///
619    /// # Errors
620    ///
621    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
622    /// when receiving unepected TPM response.
623    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
624    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
625    pub fn fetch_algorithm_properties(&mut self) -> Result<Vec<TpmsAlgProperty>, TpmDeviceError> {
626        self.get_capability(
627            TpmCap::Algs,
628            0,
629            Self::CAPABILITY_PAGE_SIZE,
630            |caps| match caps {
631                TpmuCapabilities::Algs(algs) => Ok(algs),
632                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Algs)),
633            },
634            |last| u32::from(last.alg.value()) + 1,
635        )
636    }
637
638    /// Retrieves all handles of a specific type from the TPM.
639    ///
640    /// # Errors
641    ///
642    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
643    /// when receiving unepected TPM response.
644    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
645    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
646    pub fn fetch_handles(&mut self, class: TpmHt) -> Result<Vec<TpmHandle>, TpmDeviceError> {
647        let class_prefix = (class as u32) << 24;
648
649        self.get_capability(
650            TpmCap::Handles,
651            class_prefix,
652            Self::CAPABILITY_PAGE_SIZE,
653            |caps| match caps {
654                TpmuCapabilities::Handles(handles) => Ok(handles),
655                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Handles)),
656            },
657            |last| last.value().saturating_add(1),
658        )
659        .map(|handles| {
660            handles
661                .into_iter()
662                .filter(|handle| handle.value() >> 24 == class as u32)
663                .collect()
664        })
665    }
666
667    /// Retrieves all available ECC curves supported by the TPM.
668    ///
669    /// # Errors
670    ///
671    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
672    /// when receiving unepected TPM response.
673    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
674    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
675    pub fn fetch_ecc_curves(&mut self) -> Result<Vec<TpmEccCurve>, TpmDeviceError> {
676        self.get_capability(
677            TpmCap::EccCurves,
678            0,
679            Self::CAPABILITY_PAGE_SIZE,
680            |caps| match caps {
681                TpmuCapabilities::EccCurves(curves) => Ok(curves),
682                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::EccCurves)),
683            },
684            |last| u32::from(last.value()) + 1,
685        )
686    }
687
688    /// Retrieves the list of active PCR banks and the bank selection mask.
689    ///
690    /// # Errors
691    ///
692    /// Returns
693    /// [`PcrBanksNotAvailable`](crate::TpmDeviceError::PcrBanksNotAvailable)
694    /// when no PCR banks are available.
695    /// Return
696    /// [`PcrBankSelectionMismatch`](crate::TpmDeviceError::PcrBankSelectionMismatch)
697    /// when the PCR selection masks differ between active banks.
698    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
699    /// when receiving unepected TPM response.
700    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
701    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
702    pub fn fetch_pcr_bank_list(
703        &mut self,
704    ) -> Result<(Vec<TpmAlgId>, TpmsPcrSelect), TpmDeviceError> {
705        let pcrs: Vec<TpmsPcrSelection> = self.get_capability(
706            TpmCap::Pcrs,
707            0,
708            Self::CAPABILITY_PAGE_SIZE,
709            |caps| match caps {
710                TpmuCapabilities::Pcrs(pcrs) => Ok(pcrs),
711                _ => Err(TpmDeviceError::CapabilityMissing(TpmCap::Pcrs)),
712            },
713            |last| last.hash as u32 + 1,
714        )?;
715
716        if pcrs.is_empty() {
717            return Err(TpmDeviceError::PcrBanksNotAvailable);
718        }
719
720        let mut common_select: Option<TpmsPcrSelect> = None;
721        let mut algs = Vec::with_capacity(pcrs.len());
722
723        for bank in pcrs {
724            if bank.pcr_select.iter().all(|&b| b == 0) {
725                debug!(
726                    "skipping unallocated bank {:?} (mask: {})",
727                    bank.hash,
728                    hex::encode(&*bank.pcr_select)
729                );
730                continue;
731            }
732
733            if let Some(ref select) = common_select {
734                if bank.pcr_select != *select {
735                    return Err(TpmDeviceError::PcrBankSelectionMismatch);
736                }
737            } else {
738                common_select = Some(bank.pcr_select);
739            }
740            algs.push(bank.hash);
741        }
742
743        let select = common_select.ok_or(TpmDeviceError::PcrBanksNotAvailable)?;
744
745        algs.sort();
746        Ok((algs, select))
747    }
748
749    /// Fetches and returns one page of capabilities of a certain type from the
750    /// TPM.
751    ///
752    /// # Errors
753    ///
754    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
755    /// when receiving unepected TPM response.
756    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
757    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
758    fn get_capability_page(
759        &mut self,
760        cap: TpmCap,
761        property: TpmUint32,
762        property_count: TpmUint32,
763    ) -> Result<(bool, TpmsCapabilityData), TpmDeviceError> {
764        let cmd = TpmGetCapabilityCommand {
765            cap,
766            property,
767            property_count,
768            handles: [],
769        };
770
771        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
772        let body = response
773            .unmarshal::<TpmGetCapabilityResponse>()
774            .map_err(TpmDeviceError::Unmarshal)?;
775
776        Ok((body.more_data.into(), body.capability_data))
777    }
778
779    /// Reads a specific TPM property.
780    ///
781    /// # Errors
782    ///
783    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
784    /// when receiving unepected TPM response.
785    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
786    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
787    pub fn fetch_tpm_property(&mut self, property: TpmPt) -> Result<u32, TpmDeviceError> {
788        let (_, cap_data) = self.get_capability_page(
789            TpmCap::TpmProperties,
790            TpmUint32::from(property as u32),
791            TpmUint32::from(1),
792        )?;
793
794        let TpmuCapabilities::TpmProperties(props) = &cap_data.data else {
795            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
796        };
797
798        let Some(prop) = props.iter().find(|prop| prop.property == property) else {
799            return Err(TpmDeviceError::CapabilityMissing(TpmCap::TpmProperties));
800        };
801
802        Ok(prop.value.value())
803    }
804
805    /// Reads the public area of a TPM object.
806    ///
807    /// # Errors
808    ///
809    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
810    /// when receiving unepected TPM response.
811    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
812    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
813    pub fn read_public(
814        &mut self,
815        handle: TpmHandle,
816    ) -> Result<(TpmtPublic, Tpm2bName), TpmDeviceError> {
817        let cmd = TpmReadPublicCommand { handles: [handle] };
818        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
819        let body = response
820            .unmarshal::<TpmReadPublicResponse>()
821            .map_err(TpmDeviceError::Unmarshal)?;
822
823        Ok((body.out_public.inner, body.name))
824    }
825
826    /// Finds a persistent handle by its `Tpm2bName`.
827    ///
828    /// # Errors
829    ///
830    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
831    /// when receiving unepected TPM response.
832    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
833    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
834    pub fn find_persistent(
835        &mut self,
836        target_name: &Tpm2bName,
837    ) -> Result<Option<TpmHandle>, TpmDeviceError> {
838        for handle in self.fetch_handles(TpmHt::Persistent)? {
839            match self.read_public(handle) {
840                Ok((_, name)) => {
841                    if name == *target_name {
842                        return Ok(Some(handle));
843                    }
844                }
845                Err(TpmDeviceError::TpmRc(rc)) => {
846                    let base = rc.base();
847                    if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
848                        continue;
849                    }
850                    return Err(TpmDeviceError::TpmRc(rc));
851                }
852                Err(e) => return Err(e),
853            }
854        }
855        Ok(None)
856    }
857
858    /// Saves the context of a transient object or session.
859    ///
860    /// # Errors
861    ///
862    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
863    /// when receiving unepected TPM response.
864    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
865    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
866    pub fn save_context(&mut self, save_handle: TpmHandle) -> Result<TpmsContext, TpmDeviceError> {
867        let cmd = TpmContextSaveCommand {
868            handles: [save_handle],
869        };
870        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
871        let body = response
872            .unmarshal::<TpmContextSaveResponse>()
873            .map_err(TpmDeviceError::Unmarshal)?;
874
875        Ok(body.context)
876    }
877
878    /// Loads a TPM context and returns the handle.
879    ///
880    /// # Errors
881    ///
882    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch)
883    /// when receiving unepected TPM response.
884    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
885    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
886    pub fn load_context(&mut self, context: TpmsContext) -> Result<TpmHandle, TpmDeviceError> {
887        let cmd = TpmContextLoadCommand {
888            context,
889            handles: [],
890        };
891        let response = self.transmit(&cmd, Self::NO_SESSIONS)?;
892        let body = response
893            .unmarshal::<TpmContextLoadResponse>()
894            .map_err(TpmDeviceError::Unmarshal)?;
895        let [handle] = body.handles;
896
897        Ok(handle)
898    }
899
900    /// Flushes a transient object or session from the TPM and removes it from
901    /// the cache.
902    ///
903    /// # Errors
904    ///
905    /// Returns [`TpmDeviceError`](crate::TpmDeviceError) variants when
906    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
907    pub fn flush_context(&mut self, handle: TpmHandle) -> Result<(), TpmDeviceError> {
908        let cmd = TpmFlushContextCommand {
909            flush_handle: handle,
910            handles: [],
911        };
912        self.transmit(&cmd, Self::NO_SESSIONS)?;
913        Ok(())
914    }
915
916    /// Loads a session context and then flushes the resulting handle.
917    ///
918    /// # Errors
919    ///
920    /// Returns [`TpmDeviceError`](crate::TpmDeviceError) variants when
921    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
922    pub fn flush_session(&mut self, context: TpmsContext) -> Result<(), TpmDeviceError> {
923        match self.load_context(context) {
924            Ok(handle) => self.flush_context(handle),
925            Err(TpmDeviceError::TpmRc(rc)) => {
926                let base = rc.base();
927                if base == TpmRcBase::ReferenceH0 || base == TpmRcBase::Handle {
928                    Ok(())
929                } else {
930                    Err(TpmDeviceError::TpmRc(rc))
931                }
932            }
933            Err(e) => Err(e),
934        }
935    }
936}
937
938/// The responder end of a [`TpmTransport`], for serving TPM commands.
939///
940/// Where a [`TpmDevice`] sends commands and receives responses, a `TpmResponder`
941/// receives commands and sends responses. Both ends share the same transport
942/// and frame codec; only the direction of use differs, which makes this crate
943/// usable for the TPM side of an exchange, such as an emulator.
944///
945/// Command decoding and response marshaling are left to the caller (for example
946/// via `tpm2_protocol`), keeping this type focused on transport and framing.
947pub struct TpmResponder {
948    transport: Box<dyn TpmTransport>,
949    command: Vec<u8>,
950}
951
952impl std::fmt::Debug for TpmResponder {
953    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
954        f.debug_struct("TpmResponder").finish_non_exhaustive()
955    }
956}
957
958impl TpmResponder {
959    /// Creates a `TpmResponder` serving over the given transport.
960    #[must_use]
961    pub fn new(transport: Box<dyn TpmTransport>) -> Self {
962        Self {
963            transport,
964            command: Vec::with_capacity(TPM_MAX_COMMAND_SIZE),
965        }
966    }
967
968    /// Receives the next command frame, returning its raw bytes.
969    ///
970    /// # Errors
971    ///
972    /// Returns [`UnexpectedEof`](crate::TpmDeviceError::UnexpectedEof) when the
973    /// peer disconnects, or other [`TpmDeviceError`](crate::TpmDeviceError)
974    /// variants when the transport fails.
975    pub fn recv_command(&mut self) -> Result<&[u8], TpmDeviceError> {
976        self.transport.recv(&mut self.command)?;
977        Ok(&self.command)
978    }
979
980    /// Sends a marshaled response frame.
981    ///
982    /// # Errors
983    ///
984    /// Returns a [`TpmDeviceError`](crate::TpmDeviceError) when the transport
985    /// fails to send the frame.
986    pub fn send_response(&mut self, frame: &[u8]) -> Result<(), TpmDeviceError> {
987        self.transport.send(frame)
988    }
989
990    /// Serves commands until the peer disconnects.
991    ///
992    /// Each received command frame is passed to `handler`, whose returned bytes
993    /// are written back as the response frame. Returns `Ok(())` once the peer
994    /// closes the transport.
995    ///
996    /// # Errors
997    ///
998    /// Returns a [`TpmDeviceError`](crate::TpmDeviceError) when receiving a
999    /// command or sending a response fails for any reason other than a clean
1000    /// disconnect.
1001    pub fn serve<H>(&mut self, mut handler: H) -> Result<(), TpmDeviceError>
1002    where
1003        H: FnMut(&[u8]) -> Vec<u8>,
1004    {
1005        loop {
1006            match self.transport.recv(&mut self.command) {
1007                Ok(()) => {}
1008                Err(TpmDeviceError::UnexpectedEof) => return Ok(()),
1009                Err(e) => return Err(e),
1010            }
1011            let response = handler(&self.command);
1012            self.transport.send(&response)?;
1013        }
1014    }
1015}
1016
1017/// A builder for creating a TPM policy session.
1018pub struct TpmPolicySessionBuilder {
1019    bind: TpmHandle,
1020    tpm_key: TpmHandle,
1021    nonce_caller: Option<Tpm2bNonce>,
1022    encrypted_salt: Option<Tpm2bEncryptedSecret>,
1023    session_type: TpmSe,
1024    symmetric: TpmtSymDefObject,
1025    auth_hash: TpmAlgId,
1026}
1027
1028impl Default for TpmPolicySessionBuilder {
1029    fn default() -> Self {
1030        Self {
1031            bind: (TpmRh::Null as u32).into(),
1032            tpm_key: (TpmRh::Null as u32).into(),
1033            nonce_caller: None,
1034            encrypted_salt: None,
1035            session_type: TpmSe::Policy,
1036            symmetric: TpmtSymDefObject::default(),
1037            auth_hash: TpmAlgId::Sha256,
1038        }
1039    }
1040}
1041
1042impl TpmPolicySessionBuilder {
1043    #[must_use]
1044    pub fn new() -> Self {
1045        Self::default()
1046    }
1047
1048    #[must_use]
1049    pub fn with_bind(mut self, bind: TpmHandle) -> Self {
1050        self.bind = bind;
1051        self
1052    }
1053
1054    #[must_use]
1055    pub fn with_tpm_key(mut self, tpm_key: TpmHandle) -> Self {
1056        self.tpm_key = tpm_key;
1057        self
1058    }
1059
1060    #[must_use]
1061    pub fn with_nonce_caller(mut self, nonce: Tpm2bNonce) -> Self {
1062        self.nonce_caller = Some(nonce);
1063        self
1064    }
1065
1066    #[must_use]
1067    pub fn with_encrypted_salt(mut self, salt: Tpm2bEncryptedSecret) -> Self {
1068        self.encrypted_salt = Some(salt);
1069        self
1070    }
1071
1072    #[must_use]
1073    pub fn with_session_type(mut self, session_type: TpmSe) -> Self {
1074        self.session_type = session_type;
1075        self
1076    }
1077
1078    #[must_use]
1079    pub fn with_symmetric(mut self, symmetric: TpmtSymDefObject) -> Self {
1080        self.symmetric = symmetric;
1081        self
1082    }
1083
1084    #[must_use]
1085    pub fn with_auth_hash(mut self, auth_hash: TpmAlgId) -> Self {
1086        self.auth_hash = auth_hash;
1087        self
1088    }
1089
1090    /// Opens the policy session on the provided device.
1091    ///
1092    /// # Errors
1093    ///
1094    /// Returns [`ResponseMismatch`](crate::TpmDeviceError::ResponseMismatch) if
1095    /// the TPM response is unexpected.
1096    /// Returns [`Unmarshal`](crate::TpmDeviceError::Unmarshal) when unmarshal
1097    /// operation on TPM protocol compliant data fails.
1098    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants depending
1099    /// on function.
1100    pub fn open(self, device: &mut TpmDevice) -> Result<TpmPolicySession, TpmDeviceError> {
1101        let nonce_caller = if let Some(nonce) = self.nonce_caller {
1102            nonce
1103        } else {
1104            let digest_len = TpmHash::try_from(self.auth_hash)
1105                .map_err(|_| TpmDeviceError::UnsupportedAlgorithm(self.auth_hash))?
1106                .size();
1107            let mut nonce_bytes = vec![0; digest_len];
1108            thread_rng().fill_bytes(&mut nonce_bytes);
1109            Tpm2bNonce::try_from(nonce_bytes.as_slice()).map_err(TpmDeviceError::Unmarshal)?
1110        };
1111
1112        let cmd = TpmStartAuthSessionCommand {
1113            nonce_caller,
1114            encrypted_salt: self.encrypted_salt.unwrap_or_default(),
1115            session_type: self.session_type,
1116            symmetric: self.symmetric,
1117            auth_hash: self.auth_hash,
1118            handles: [self.tpm_key, self.bind],
1119        };
1120
1121        let response = device.transmit(&cmd, TpmDevice::NO_SESSIONS)?;
1122        let body = response
1123            .unmarshal::<TpmStartAuthSessionResponse>()
1124            .map_err(TpmDeviceError::Unmarshal)?;
1125        let [handle] = body.handles;
1126        let nonce_tpm = body.nonce_tpm;
1127
1128        Ok(TpmPolicySession {
1129            handle,
1130            attributes: TpmaSession::CONTINUE_SESSION,
1131            hash_alg: self.auth_hash,
1132            nonce_tpm,
1133        })
1134    }
1135}
1136
1137/// Represents an active TPM policy session.
1138#[derive(Debug, Clone)]
1139pub struct TpmPolicySession {
1140    handle: TpmHandle,
1141    attributes: TpmaSession,
1142    hash_alg: TpmAlgId,
1143    nonce_tpm: Tpm2bNonce,
1144}
1145
1146impl TpmPolicySession {
1147    /// Creates a new builder for `TpmPolicySession`.
1148    #[must_use]
1149    pub fn builder() -> TpmPolicySessionBuilder {
1150        TpmPolicySessionBuilder::new()
1151    }
1152
1153    /// Returns the session handle.
1154    #[must_use]
1155    pub fn handle(&self) -> TpmHandle {
1156        self.handle
1157    }
1158
1159    /// Returns the session attributes.
1160    #[must_use]
1161    pub fn attributes(&self) -> TpmaSession {
1162        self.attributes
1163    }
1164
1165    /// Returns the hash algorithm used by the session.
1166    #[must_use]
1167    pub fn hash_alg(&self) -> TpmAlgId {
1168        self.hash_alg
1169    }
1170
1171    /// Returns the nonce generated by the TPM.
1172    #[must_use]
1173    pub fn nonce_tpm(&self) -> &Tpm2bNonce {
1174        &self.nonce_tpm
1175    }
1176
1177    /// Applies a list of policy commands to this session.
1178    ///
1179    /// This method iterates through the provided commands, updates the first handle
1180    /// of each command (or second for `PolicySecret`) to point to this session,
1181    /// and transmits them to the device.
1182    ///
1183    /// # Errors
1184    ///
1185    /// Returns [`InvalidCc`](crate::TpmDeviceError::InvalidCc) when a command is not
1186    /// a supported policy command.
1187    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
1188    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
1189    pub fn run(
1190        &self,
1191        device: &mut TpmDevice,
1192        commands: impl IntoIterator<Item = (TpmCommand, TpmAuthCommands)>,
1193    ) -> Result<(), TpmDeviceError> {
1194        for (mut command_body, auth_sessions) in commands {
1195            // Policy commands take the policy session handle either as the
1196            // first handle (`sessionHandle`), the second handle for commands
1197            // with an entity (`authHandle`, `sessionHandle`), or the third
1198            // handle for commands with two preceding handles.
1199            let session_handle = self.handle;
1200            match &mut command_body {
1201                TpmCommand::PolicyPcr(cmd) => cmd.handles[0] = session_handle,
1202                TpmCommand::PolicyOr(cmd) => cmd.handles[0] = session_handle,
1203                TpmCommand::PolicyRestart(cmd) => cmd.handles[0] = session_handle,
1204                TpmCommand::PolicyAuthorize(cmd) => cmd.handles[0] = session_handle,
1205                TpmCommand::PolicyAuthValue(cmd) => cmd.handles[0] = session_handle,
1206                TpmCommand::PolicyCommandCode(cmd) => cmd.handles[0] = session_handle,
1207                TpmCommand::PolicyCounterTimer(cmd) => cmd.handles[0] = session_handle,
1208                TpmCommand::PolicyCpHash(cmd) => cmd.handles[0] = session_handle,
1209                TpmCommand::PolicyLocality(cmd) => cmd.handles[0] = session_handle,
1210                TpmCommand::PolicyNameHash(cmd) => cmd.handles[0] = session_handle,
1211                TpmCommand::PolicyTicket(cmd) => cmd.handles[0] = session_handle,
1212                TpmCommand::PolicyPhysicalPresence(cmd) => cmd.handles[0] = session_handle,
1213                TpmCommand::PolicyDuplicationSelect(cmd) => cmd.handles[0] = session_handle,
1214                TpmCommand::PolicyGetDigest(cmd) => cmd.handles[0] = session_handle,
1215                TpmCommand::PolicyPassword(cmd) => cmd.handles[0] = session_handle,
1216                TpmCommand::PolicyNvWritten(cmd) => cmd.handles[0] = session_handle,
1217                TpmCommand::PolicyTemplate(cmd) => cmd.handles[0] = session_handle,
1218                TpmCommand::PolicyCapability(cmd) => cmd.handles[0] = session_handle,
1219                TpmCommand::PolicyParameters(cmd) => cmd.handles[0] = session_handle,
1220                TpmCommand::PolicyTransportSpdm(cmd) => cmd.handles[0] = session_handle,
1221                TpmCommand::PolicySecret(cmd) => cmd.handles[1] = session_handle,
1222                TpmCommand::PolicySigned(cmd) => cmd.handles[1] = session_handle,
1223                TpmCommand::PolicyNv(cmd) => cmd.handles[2] = session_handle,
1224                TpmCommand::PolicyAuthorizeNv(cmd) => cmd.handles[2] = session_handle,
1225                TpmCommand::PolicyAcSendSelect(cmd) => cmd.handles[2] = session_handle,
1226                _ => {
1227                    return Err(TpmDeviceError::InvalidCc(command_body.cc()));
1228                }
1229            }
1230            device.transmit(&command_body, auth_sessions.as_ref())?;
1231        }
1232        Ok(())
1233    }
1234
1235    /// Flushes the session context from the TPM.
1236    ///
1237    /// # Errors
1238    ///
1239    /// Returns other [`TpmDeviceError`](crate::TpmDeviceError) variants when
1240    /// [`TpmDevice::transmit`](crate::TpmDevice::transmit) fails.
1241    pub fn flush(&self, device: &mut TpmDevice) -> Result<(), TpmDeviceError> {
1242        device.flush_context(self.handle)
1243    }
1244}
1245
1246#[cfg(test)]
1247mod tests {
1248    use super::*;
1249    use std::cell::RefCell;
1250    use std::io::Cursor;
1251    use std::rc::Rc;
1252
1253    struct Duplex {
1254        input: Cursor<Vec<u8>>,
1255        output: Rc<RefCell<Vec<u8>>>,
1256    }
1257
1258    impl Read for Duplex {
1259        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1260            self.input.read(buf)
1261        }
1262    }
1263
1264    impl Write for Duplex {
1265        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1266            self.output.borrow_mut().extend_from_slice(buf);
1267            Ok(buf.len())
1268        }
1269
1270        fn flush(&mut self) -> std::io::Result<()> {
1271            Ok(())
1272        }
1273    }
1274
1275    fn frame(body: &[u8]) -> Vec<u8> {
1276        let size = u32::try_from(TPM_HEADER_SIZE + body.len()).unwrap();
1277        let mut frame = vec![0x80, 0x01];
1278        frame.extend_from_slice(&size.to_be_bytes());
1279        frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
1280        frame.extend_from_slice(body);
1281        frame
1282    }
1283
1284    #[test]
1285    fn read_frame_reads_exactly_one_frame() {
1286        let first = frame(&[0xAA, 0xBB]);
1287        let mut bytes = first.clone();
1288        bytes.extend_from_slice(&frame(&[0xCC]));
1289        let mut reader = Cursor::new(bytes);
1290
1291        let mut buf = Vec::new();
1292        read_frame(&mut reader, &mut buf).unwrap();
1293
1294        assert_eq!(buf, first);
1295    }
1296
1297    #[test]
1298    fn write_then_read_round_trips() {
1299        let expected = frame(&[1, 2, 3, 4]);
1300        let mut stream = Cursor::new(Vec::new());
1301        write_frame(&mut stream, &expected).unwrap();
1302        stream.set_position(0);
1303
1304        let mut buf = Vec::new();
1305        read_frame(&mut stream, &mut buf).unwrap();
1306
1307        assert_eq!(buf, expected);
1308    }
1309
1310    #[test]
1311    fn stream_transport_sends_and_receives() {
1312        let response = frame(&[0x11, 0x22]);
1313        let command = frame(&[0x33]);
1314        let output = Rc::new(RefCell::new(Vec::new()));
1315        let mut transport = TpmStreamTransport::new(Duplex {
1316            input: Cursor::new(response.clone()),
1317            output: Rc::clone(&output),
1318        });
1319
1320        transport.send(&command).unwrap();
1321        let mut buf = Vec::new();
1322        transport.recv(&mut buf).unwrap();
1323
1324        assert_eq!(buf, response);
1325        assert_eq!(*output.borrow(), command);
1326    }
1327
1328    #[test]
1329    fn responder_serves_commands_until_disconnect() {
1330        let command = frame(&[0x01]);
1331        let response = frame(&[0x02, 0x03]);
1332        let output = Rc::new(RefCell::new(Vec::new()));
1333        let transport = TpmStreamTransport::new(Duplex {
1334            input: Cursor::new(command.clone()),
1335            output: Rc::clone(&output),
1336        });
1337        let mut responder = TpmResponder::new(Box::new(transport));
1338
1339        let reply = response.clone();
1340        let mut served = 0;
1341        responder
1342            .serve(|cmd| {
1343                assert_eq!(cmd, command.as_slice());
1344                served += 1;
1345                reply.clone()
1346            })
1347            .unwrap();
1348
1349        assert_eq!(served, 1);
1350        assert_eq!(*output.borrow(), response);
1351    }
1352
1353    #[test]
1354    fn frame_size_rejects_short_header() {
1355        assert!(frame_size(&[0x80, 0x01, 0x00]).is_err());
1356    }
1357
1358    #[test]
1359    fn read_frame_reports_unexpected_eof_on_truncation() {
1360        let mut reader = Cursor::new(vec![0x80, 0x01]);
1361        let mut buf = Vec::new();
1362        assert_eq!(
1363            read_frame(&mut reader, &mut buf),
1364            Err(TpmDeviceError::UnexpectedEof)
1365        );
1366    }
1367}