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