tpm2_protocol/frame/
unmarshal.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2025 Jarkko Sakkinen
4
5use super::{
6    TpmAuthCommands, TpmAuthResponses, TpmCommand, TpmHandles, TpmResponse, TPM_DISPATCH_TABLE,
7};
8use crate::{
9    constant::TPM_HEADER_SIZE,
10    data::{TpmCc, TpmRc, TpmRcBase, TpmSt, TpmsAuthCommand, TpmsAuthResponse},
11    TpmDiscriminant, TpmProtocolError, TpmResult, TpmUnmarshal,
12};
13use core::{convert::TryFrom, mem::size_of};
14
15/// A unified struct holding all dispatch info for a given Command Code.
16#[doc(hidden)]
17pub struct TpmDispatch {
18    pub cc: TpmCc,
19    pub handles: usize,
20    #[allow(clippy::type_complexity)]
21    pub command_unmarshaler: for<'a> fn(&'a [u8], &'a [u8]) -> TpmResult<(TpmCommand, &'a [u8])>,
22    #[allow(clippy::type_complexity)]
23    pub response_unmarshaler: for<'a> fn(TpmSt, &'a [u8]) -> TpmResult<(TpmResponse, &'a [u8])>,
24}
25
26/// Represents the dualistic nature of responses.
27pub type TpmResponseResult = Result<(TpmResponse, TpmAuthResponses), TpmRc>;
28
29/// Unmarshals a command from a TPM command buffer.
30///
31/// # Errors
32///
33/// * `TpmProtocolError::UnexpectedEof` if the buffer is too small
34/// * `TpmProtocolError::InvalidDiscriminant` if the buffer contains an unsupported command code or unexpected byte
35/// * `TpmProtocolError::TrailingData` if the command has after spurious data left
36pub fn tpm_unmarshal_command(buf: &[u8]) -> TpmResult<(TpmHandles, TpmCommand, TpmAuthCommands)> {
37    if buf.len() < TPM_HEADER_SIZE as usize {
38        return Err(TpmProtocolError::UnexpectedEof);
39    }
40    let buf_len = buf.len();
41
42    let (tag_raw, buf) = u16::unmarshal(buf)?;
43    let tag = TpmSt::try_from(tag_raw).map_err(|()| {
44        TpmProtocolError::InvalidDiscriminant(
45            "TpmSt",
46            TpmDiscriminant::Unsigned(u64::from(tag_raw)),
47        )
48    })?;
49    let (size, buf) = u32::unmarshal(buf)?;
50    let (cc_raw, body_buf) = u32::unmarshal(buf)?;
51
52    if buf_len < size as usize {
53        return Err(TpmProtocolError::UnexpectedEof);
54    } else if buf_len > size as usize {
55        return Err(TpmProtocolError::TrailingData);
56    }
57
58    let cc = TpmCc::try_from(cc_raw).map_err(|()| {
59        TpmProtocolError::InvalidDiscriminant("TpmCc", TpmDiscriminant::Unsigned(u64::from(cc_raw)))
60    })?;
61    let dispatch = TPM_DISPATCH_TABLE
62        .binary_search_by_key(&cc, |d| d.cc)
63        .map(|index| &TPM_DISPATCH_TABLE[index])
64        .map_err(|_| {
65            TpmProtocolError::InvalidDiscriminant(
66                "TpmCc",
67                TpmDiscriminant::Unsigned(u64::from(cc_raw)),
68            )
69        })?;
70
71    if tag != TpmSt::NoSessions && tag != TpmSt::Sessions {
72        return Err(TpmProtocolError::MalformedValue);
73    }
74
75    let handle_area_size = dispatch.handles * size_of::<u32>();
76    if body_buf.len() < handle_area_size {
77        return Err(TpmProtocolError::UnexpectedEof);
78    }
79    let (handle_area, after_handles) = body_buf.split_at(handle_area_size);
80
81    let mut sessions = TpmAuthCommands::new();
82    let param_area = if tag == TpmSt::Sessions {
83        let (auth_area_size, buf_after_auth_size) = u32::unmarshal(after_handles)?;
84        let auth_area_size = auth_area_size as usize;
85        if buf_after_auth_size.len() < auth_area_size {
86            return Err(TpmProtocolError::UnexpectedEof);
87        }
88        let (mut auth_area, param_area) = buf_after_auth_size.split_at(auth_area_size);
89        while !auth_area.is_empty() {
90            let (session, rest) = TpmsAuthCommand::unmarshal(auth_area)?;
91            sessions.push(session)?;
92            auth_area = rest;
93        }
94        if !auth_area.is_empty() {
95            return Err(TpmProtocolError::TrailingData);
96        }
97        param_area
98    } else {
99        after_handles
100    };
101
102    let (command_data, param_remainder) = (dispatch.command_unmarshaler)(handle_area, param_area)?;
103
104    if !param_remainder.is_empty() {
105        return Err(TpmProtocolError::TrailingData);
106    }
107
108    let mut handles = TpmHandles::new();
109    let mut temp_handle_cursor = handle_area;
110    while !temp_handle_cursor.is_empty() {
111        let (handle, rest) = u32::unmarshal(temp_handle_cursor)?;
112        handles.push(handle.into())?;
113        temp_handle_cursor = rest;
114    }
115
116    Ok((handles, command_data, sessions))
117}
118
119/// Unmarshals a response from a TPM response buffer.
120///
121/// # Errors
122///
123/// * `TpmProtocolError::UnexpectedEof` if the buffer is too small
124/// * `TpmProtocolError::InvalidDiscriminant` if the buffer contains an unsupported command code
125/// * `TpmProtocolError::TrailingData` if the response has after spurious data left
126pub fn tpm_unmarshal_response(cc: TpmCc, buf: &[u8]) -> TpmResult<TpmResponseResult> {
127    if buf.len() < TPM_HEADER_SIZE as usize {
128        return Err(TpmProtocolError::UnexpectedEof);
129    }
130
131    let (tag_raw, remainder) = u16::unmarshal(buf)?;
132    let (size, remainder) = u32::unmarshal(remainder)?;
133    let (code, body_buf) = u32::unmarshal(remainder)?;
134
135    if buf.len() < size as usize {
136        return Err(TpmProtocolError::UnexpectedEof);
137    } else if buf.len() > size as usize {
138        return Err(TpmProtocolError::TrailingData);
139    }
140
141    let rc = TpmRc::try_from(code)?;
142    if !matches!(rc, TpmRc::Fmt0(TpmRcBase::Success)) {
143        return Ok(Err(rc));
144    }
145
146    let tag = TpmSt::try_from(tag_raw).map_err(|()| {
147        TpmProtocolError::InvalidDiscriminant(
148            "TpmSt",
149            TpmDiscriminant::Unsigned(u64::from(tag_raw)),
150        )
151    })?;
152
153    let dispatch = TPM_DISPATCH_TABLE
154        .binary_search_by_key(&cc, |d| d.cc)
155        .map(|index| &TPM_DISPATCH_TABLE[index])
156        .map_err(|_| {
157            TpmProtocolError::InvalidDiscriminant(
158                "TpmCc",
159                TpmDiscriminant::Unsigned(u64::from(cc as u32)),
160            )
161        })?;
162
163    let (body, mut session_area) = (dispatch.response_unmarshaler)(tag, body_buf)?;
164
165    let mut auth_responses = TpmAuthResponses::new();
166    if tag == TpmSt::Sessions {
167        while !session_area.is_empty() {
168            let (session, rest) = TpmsAuthResponse::unmarshal(session_area)?;
169            auth_responses.push(session)?;
170            session_area = rest;
171        }
172    }
173
174    if !session_area.is_empty() {
175        return Err(TpmProtocolError::TrailingData);
176    }
177
178    Ok(Ok((body, auth_responses)))
179}