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, TPM_MAX_COMMAND_SIZE,
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 mut temp_body_buf = [0u8; TPM_MAX_COMMAND_SIZE];
98    let full_body_len = handle_area.len() + param_area.len();
99    if full_body_len > temp_body_buf.len() {
100        return Err(TpmErrorKind::ParseCapacity);
101    }
102    let full_body_for_parser = &mut temp_body_buf[..full_body_len];
103    full_body_for_parser[..handle_area.len()].copy_from_slice(handle_area);
104    full_body_for_parser[handle_area.len()..].copy_from_slice(param_area);
105
106    let (command_data, remainder) = (dispatch.4)(full_body_for_parser)?;
107
108    if !remainder.is_empty() {
109        return Err(TpmErrorKind::TrailingData);
110    }
111
112    let mut handles = TpmHandles::new();
113    let mut temp_handle_cursor = handle_area;
114    while !temp_handle_cursor.is_empty() {
115        let (handle, rest) = u32::parse(temp_handle_cursor)?;
116        handles.try_push(handle)?;
117        temp_handle_cursor = rest;
118    }
119
120    Ok((handles, command_data, sessions))
121}
122
123/// Parses a response from a TPM response buffer.
124///
125/// # Errors
126///
127/// * `TpmErrorKind::ParseUnderflow` if the buffer is too small
128/// * `TpmErrorKind::InvalidTag` if the tag in the buffer does not match expected
129/// * `TpmErrorKind::NotDiscriminant` if the buffer contains an unsupported command code
130/// * `TpmErrorKind::TrailingData` if the response has after spurious data left
131pub fn tpm_parse_response(cc: TpmCc, buf: &[u8]) -> TpmResult<TpmParseResult<'_>> {
132    if buf.len() < TPM_HEADER_SIZE {
133        return Err(TpmErrorKind::ParseUnderflow);
134    }
135
136    let (tag_raw, remainder) = u16::parse(buf)?;
137    let (size, remainder) = u32::parse(remainder)?;
138    let (code, body_buf) = u32::parse(remainder)?;
139
140    if buf.len() != size as usize {
141        return Err(TpmErrorKind::ParseUnderflow);
142    }
143
144    let rc = TpmRc::try_from(code)?;
145    if rc.is_error() {
146        return Ok(Err((rc, body_buf)));
147    }
148
149    let tag = TpmSt::try_from(tag_raw).map_err(|()| {
150        TpmErrorKind::NotDiscriminant("TpmSt", TpmNotDiscriminant::Unsigned(u64::from(tag_raw)))
151    })?;
152
153    let dispatch = PARSE_RESPONSE_MAP
154        .binary_search_by_key(&cc, |d| d.0)
155        .map(|index| &PARSE_RESPONSE_MAP[index])
156        .map_err(|_| {
157            TpmErrorKind::NotDiscriminant(
158                "TpmCc",
159                TpmNotDiscriminant::Unsigned(u64::from(cc as u32)),
160            )
161        })?;
162
163    let (body, mut session_area) = (dispatch.2)(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::parse(session_area)?;
169            auth_responses
170                .try_push(session)
171                .map_err(|_| TpmErrorKind::ParseCapacity)?;
172            session_area = rest;
173        }
174    }
175
176    if !session_area.is_empty() {
177        return Err(TpmErrorKind::TrailingData);
178    }
179
180    Ok(Ok((rc, body, auth_responses)))
181}