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    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 TPM command.
30///
31/// # Errors
32///
33/// Returns [`InvalidValue`](crate::TpmProtocolError::InvalidValue) when the
34/// command code is non-existent.
35/// Returns [`TrailingData`](crate::TpmProtocolError::TrailingData) when after
36/// unmarshaling there is some data left.
37/// Returns [`UnexpectedEnd`](crate::TpmProtocolError::UnexpectedEnd) when the
38/// buffer does not hold all the bytes.
39pub fn tpm_unmarshal_command(buf: &[u8]) -> TpmResult<(TpmHandles, TpmCommand, TpmAuthCommands)> {
40    if buf.len() < TPM_HEADER_SIZE as usize {
41        return Err(TpmProtocolError::UnexpectedEnd);
42    }
43    let buf_len = buf.len();
44
45    let (tag_raw, buf) = u16::unmarshal(buf)?;
46    let tag = TpmSt::try_from(tag_raw)?;
47    let (size, buf) = u32::unmarshal(buf)?;
48    let (cc_raw, body_buf) = u32::unmarshal(buf)?;
49
50    if buf_len < size as usize {
51        return Err(TpmProtocolError::UnexpectedEnd);
52    } else if buf_len > size as usize {
53        return Err(TpmProtocolError::TrailingData);
54    }
55
56    let cc = TpmCc::try_from(cc_raw)?;
57    let dispatch = TPM_DISPATCH_TABLE
58        .binary_search_by_key(&cc, |d| d.cc)
59        .map(|index| &TPM_DISPATCH_TABLE[index])
60        .map_err(|_| TpmProtocolError::InvalidCc)?;
61
62    if tag != TpmSt::NoSessions && tag != TpmSt::Sessions {
63        return Err(TpmProtocolError::InvalidTag);
64    }
65
66    let handle_area_size = dispatch.handles * size_of::<u32>();
67    if body_buf.len() < handle_area_size {
68        return Err(TpmProtocolError::UnexpectedEnd);
69    }
70    let (handle_area, after_handles) = body_buf.split_at(handle_area_size);
71
72    let mut sessions = TpmAuthCommands::new();
73    let param_area = if tag == TpmSt::Sessions {
74        let (auth_area_size, buf_after_auth_size) = u32::unmarshal(after_handles)?;
75        let auth_area_size = auth_area_size as usize;
76        if buf_after_auth_size.len() < auth_area_size {
77            return Err(TpmProtocolError::UnexpectedEnd);
78        }
79        let (mut auth_area, param_area) = buf_after_auth_size.split_at(auth_area_size);
80        while !auth_area.is_empty() {
81            let (session, rest) = TpmsAuthCommand::unmarshal(auth_area)?;
82            sessions.push(session)?;
83            auth_area = rest;
84        }
85        if !auth_area.is_empty() {
86            return Err(TpmProtocolError::TrailingData);
87        }
88        param_area
89    } else {
90        after_handles
91    };
92
93    let (command_data, param_remainder) = (dispatch.command_unmarshaler)(handle_area, param_area)?;
94
95    if !param_remainder.is_empty() {
96        return Err(TpmProtocolError::TrailingData);
97    }
98
99    let mut handles = TpmHandles::new();
100    let mut temp_handle_cursor = handle_area;
101    while !temp_handle_cursor.is_empty() {
102        let (handle, rest) = u32::unmarshal(temp_handle_cursor)?;
103        handles.push(handle.into())?;
104        temp_handle_cursor = rest;
105    }
106
107    Ok((handles, command_data, sessions))
108}
109
110/// Unmarshals a response from a TPM response buffer.
111///
112/// # Errors
113///
114/// Returns [`InvalidValue`](crate::TpmProtocolError::InvalidValue) when the
115/// command code is non-existent.
116/// Returns [`TrailingData`](crate::TpmProtocolError::TrailingData) when after
117/// unmarshaling there is some data left.
118/// Returns [`UnexpectedEnd`](crate::TpmProtocolError::UnexpectedEnd) when the
119/// buffer does not hold all the bytes.
120pub fn tpm_unmarshal_response(cc: TpmCc, buf: &[u8]) -> TpmResult<TpmResponseResult> {
121    if buf.len() < TPM_HEADER_SIZE as usize {
122        return Err(TpmProtocolError::UnexpectedEnd);
123    }
124
125    let (tag_raw, remainder) = u16::unmarshal(buf)?;
126    let (size, remainder) = u32::unmarshal(remainder)?;
127    let (code, body_buf) = u32::unmarshal(remainder)?;
128
129    if buf.len() < size as usize {
130        return Err(TpmProtocolError::UnexpectedEnd);
131    } else if buf.len() > size as usize {
132        return Err(TpmProtocolError::TrailingData);
133    }
134
135    let rc = TpmRc::try_from(code)?;
136    if !matches!(rc, TpmRc::Fmt0(TpmRcBase::Success)) {
137        return Ok(Err(rc));
138    }
139
140    let tag = TpmSt::try_from(tag_raw)?;
141
142    let dispatch = TPM_DISPATCH_TABLE
143        .binary_search_by_key(&cc, |d| d.cc)
144        .map(|index| &TPM_DISPATCH_TABLE[index])
145        .map_err(|_| TpmProtocolError::InvalidCc)?;
146
147    let (body, mut session_area) = (dispatch.response_unmarshaler)(tag, body_buf)?;
148
149    let mut auth_responses = TpmAuthResponses::new();
150    if tag == TpmSt::Sessions {
151        while !session_area.is_empty() {
152            let (session, rest) = TpmsAuthResponse::unmarshal(session_area)?;
153            auth_responses.push(session)?;
154            session_area = rest;
155        }
156    }
157
158    if !session_area.is_empty() {
159        return Err(TpmProtocolError::TrailingData);
160    }
161
162    Ok(Ok((body, auth_responses)))
163}