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    PARSE_COMMAND_MAP, PARSE_RESPONSE_MAP, TPM_HEADER_SIZE,
8};
9use crate::{
10    data::{TpmCc, TpmRc, TpmSt, TpmsAuthCommand, TpmsAuthResponse},
11    TpmErrorKind, TpmNotDiscriminant, TpmParse, TpmResult,
12};
13use core::{convert::TryFrom, mem::size_of};
14
15/// The result of parsing a TPM response, containing either the successfully parsed
16/// body and auth areas (with a success or warning code) or a fatal error code.
17pub type TpmParseResult<'a> = Result<(TpmRc, TpmResponseBody, TpmAuthResponses), (TpmRc, &'a [u8])>;
18
19/// Parses a command from a TPM command buffer.
20///
21/// # Errors
22///
23/// * `TpmErrorKind::ParseUnderflow` if the buffer is too small
24/// * `TpmErrorKind::NotDiscriminant` if the buffer contains an unsupported command code or unexpected byte
25/// * `TpmErrorKind::TrailingData` if the command has after spurious data left
26pub fn tpm_parse_command(buf: &[u8]) -> TpmResult<(TpmHandles, TpmCommandBody, TpmAuthCommands)> {
27    if buf.len() < TPM_HEADER_SIZE {
28        return Err(TpmErrorKind::ParseUnderflow);
29    }
30    let command_len = buf.len();
31
32    let (tag_raw, buf) = u16::parse(buf)?;
33    let tag = TpmSt::try_from(tag_raw).map_err(|()| {
34        TpmErrorKind::NotDiscriminant("TpmSt", TpmNotDiscriminant::Unsigned(u64::from(tag_raw)))
35    })?;
36    let (size, buf) = u32::parse(buf)?;
37    let (cc_raw, body_buf) = u32::parse(buf)?;
38
39    if command_len != size as usize {
40        return Err(TpmErrorKind::ParseUnderflow);
41    }
42
43    let cc = TpmCc::try_from(cc_raw).map_err(|()| {
44        TpmErrorKind::NotDiscriminant("TpmCc", TpmNotDiscriminant::Unsigned(u64::from(cc_raw)))
45    })?;
46    let dispatch = PARSE_COMMAND_MAP
47        .binary_search_by_key(&cc, |d| d.0)
48        .map(|index| &PARSE_COMMAND_MAP[index])
49        .map_err(|_| {
50            TpmErrorKind::NotDiscriminant("TpmCc", TpmNotDiscriminant::Unsigned(u64::from(cc_raw)))
51        })?;
52
53    if tag == TpmSt::Sessions && !dispatch.2 {
54        return Err(TpmErrorKind::InvalidTag {
55            type_name: "TpmSt",
56            expected: TpmSt::NoSessions as u16,
57            got: tag_raw,
58        });
59    }
60    if tag == TpmSt::NoSessions && !dispatch.1 {
61        return Err(TpmErrorKind::InvalidTag {
62            type_name: "TpmSt",
63            expected: TpmSt::Sessions as u16,
64            got: tag_raw,
65        });
66    }
67
68    let handle_area_size = dispatch.3 * size_of::<u32>();
69    if body_buf.len() < handle_area_size {
70        return Err(TpmErrorKind::ParseUnderflow);
71    }
72    let (handle_area, after_handles) = body_buf.split_at(handle_area_size);
73
74    let mut sessions = TpmAuthCommands::new();
75    let param_area = if tag == TpmSt::Sessions {
76        let (auth_area_size, buf_after_auth_size) = u32::parse(after_handles)?;
77        let auth_area_size = auth_area_size as usize;
78        if buf_after_auth_size.len() < auth_area_size {
79            return Err(TpmErrorKind::ParseUnderflow);
80        }
81        let (mut auth_area, param_area) = buf_after_auth_size.split_at(auth_area_size);
82        while !auth_area.is_empty() {
83            let (session, rest) = TpmsAuthCommand::parse(auth_area)?;
84            sessions
85                .try_push(session)
86                .map_err(|_| TpmErrorKind::ParseCapacity)?;
87            auth_area = rest;
88        }
89        if !auth_area.is_empty() {
90            return Err(TpmErrorKind::TrailingData);
91        }
92        param_area
93    } else {
94        after_handles
95    };
96
97    let (command_data, param_remainder) = (dispatch.4)(handle_area, param_area)?;
98
99    if !param_remainder.is_empty() {
100        return Err(TpmErrorKind::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)?;
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/// * `TpmErrorKind::ParseUnderflow` if the buffer is too small
119/// * `TpmErrorKind::InvalidTag` if the tag in the buffer does not match expected
120/// * `TpmErrorKind::NotDiscriminant` if the buffer contains an unsupported command code
121/// * `TpmErrorKind::TrailingData` if the response has after spurious data left
122pub fn tpm_parse_response(cc: TpmCc, buf: &[u8]) -> TpmResult<TpmParseResult<'_>> {
123    if buf.len() < TPM_HEADER_SIZE {
124        return Err(TpmErrorKind::ParseUnderflow);
125    }
126
127    let (tag_raw, remainder) = u16::parse(buf)?;
128    let (size, remainder) = u32::parse(remainder)?;
129    let (code, body_buf) = u32::parse(remainder)?;
130
131    if buf.len() != size as usize {
132        return Err(TpmErrorKind::ParseUnderflow);
133    }
134
135    let rc = TpmRc::try_from(code)?;
136    if rc.is_error() || rc.is_warning() {
137        return Ok(Err((rc, body_buf)));
138    }
139
140    let tag = TpmSt::try_from(tag_raw).map_err(|()| {
141        TpmErrorKind::NotDiscriminant("TpmSt", TpmNotDiscriminant::Unsigned(u64::from(tag_raw)))
142    })?;
143
144    let dispatch = PARSE_RESPONSE_MAP
145        .binary_search_by_key(&cc, |d| d.0)
146        .map(|index| &PARSE_RESPONSE_MAP[index])
147        .map_err(|_| {
148            TpmErrorKind::NotDiscriminant(
149                "TpmCc",
150                TpmNotDiscriminant::Unsigned(u64::from(cc as u32)),
151            )
152        })?;
153
154    let (body, mut session_area) = (dispatch.2)(body_buf)?;
155
156    let mut auth_responses = TpmAuthResponses::new();
157    if tag == TpmSt::Sessions {
158        while !session_area.is_empty() {
159            let (session, rest) = TpmsAuthResponse::parse(session_area)?;
160            auth_responses
161                .try_push(session)
162                .map_err(|_| TpmErrorKind::ParseCapacity)?;
163            session_area = rest;
164        }
165    }
166
167    if !session_area.is_empty() {
168        return Err(TpmErrorKind::TrailingData);
169    }
170
171    Ok(Ok((rc, body, auth_responses)))
172}