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, TpmParse, TpmResult,
12};
13use core::convert::TryFrom;
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::Boundary` if the buffer is too small
24/// * `TpmErrorKind::InvalidDiscriminant` 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::Boundary);
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(|()| TpmErrorKind::InvalidDiscriminant {
34        type_name: "TpmSt",
35        value: u64::from(tag_raw),
36    })?;
37    let (size, buf) = u32::parse(buf)?;
38    let (cc_raw, mut buf) = u32::parse(buf)?;
39
40    if command_len != size as usize {
41        return Err(TpmErrorKind::Boundary);
42    }
43
44    let cc = TpmCc::try_from(cc_raw).map_err(|()| TpmErrorKind::InvalidDiscriminant {
45        type_name: "TpmCc",
46        value: u64::from(cc_raw),
47    })?;
48    let dispatch = PARSE_COMMAND_MAP
49        .binary_search_by_key(&cc, |d| d.0)
50        .map(|index| &PARSE_COMMAND_MAP[index])
51        .map_err(|_| TpmErrorKind::InvalidDiscriminant {
52            type_name: "TpmCc",
53            value: u64::from(cc_raw),
54        })?;
55
56    if tag == TpmSt::Sessions && !dispatch.2 {
57        return Err(TpmErrorKind::InvalidTag {
58            type_name: "TpmSt",
59            expected: TpmSt::NoSessions as u16,
60            got: tag_raw,
61        });
62    }
63    if tag == TpmSt::NoSessions && !dispatch.1 {
64        return Err(TpmErrorKind::InvalidTag {
65            type_name: "TpmSt",
66            expected: TpmSt::Sessions as u16,
67            got: tag_raw,
68        });
69    }
70
71    let mut handles = TpmHandles::new();
72    for _ in 0..dispatch.3 {
73        let (handle, rest) = u32::parse(buf)?;
74        handles
75            .try_push(handle)
76            .map_err(|_| TpmErrorKind::ValueTooLarge)?;
77        buf = rest;
78    }
79
80    let mut sessions = TpmAuthCommands::new();
81    let param_buf = if tag == TpmSt::Sessions {
82        let (auth_area_size, auth_buf) = u32::parse(buf)?;
83        let auth_area_size = auth_area_size as usize;
84        if auth_buf.len() < auth_area_size {
85            return Err(TpmErrorKind::Boundary);
86        }
87        let (mut auth_area, param_buf) = auth_buf.split_at(auth_area_size);
88        while !auth_area.is_empty() {
89            let (session, rest) = TpmsAuthCommand::parse(auth_area)?;
90            sessions
91                .try_push(session)
92                .map_err(|_| TpmErrorKind::ValueTooLarge)?;
93            auth_area = rest;
94        }
95        if !auth_area.is_empty() {
96            return Err(TpmErrorKind::TrailingData);
97        }
98        param_buf
99    } else {
100        buf
101    };
102
103    let (command_data, remainder) = (dispatch.4)(param_buf)?;
104
105    if !remainder.is_empty() {
106        return Err(TpmErrorKind::TrailingData);
107    }
108
109    Ok((handles, command_data, sessions))
110}
111
112/// Parses a response from a TPM response buffer.
113///
114/// # Errors
115///
116/// * `TpmErrorKind::Boundary` if the buffer is too small
117/// * `TpmErrorKind::InvalidTag` if the tag in the buffer does not match expected
118/// * `TpmErrorKind::InvalidDiscriminant` if the buffer contains an unsupported command code
119/// * `TpmErrorKind::TrailingData` if the response has after spurious data left
120pub fn tpm_parse_response(cc: TpmCc, buf: &[u8]) -> TpmResult<TpmParseResult<'_>> {
121    if buf.len() < TPM_HEADER_SIZE {
122        return Err(TpmErrorKind::Boundary);
123    }
124
125    let (tag_raw, remainder) = u16::parse(buf)?;
126    let (size, remainder) = u32::parse(remainder)?;
127    let (code, body_buf) = u32::parse(remainder)?;
128
129    if buf.len() != size as usize {
130        return Err(TpmErrorKind::Boundary);
131    }
132
133    let rc = TpmRc::try_from(code)?;
134    if rc.is_error() {
135        return Ok(Err((rc, body_buf)));
136    }
137
138    let tag = TpmSt::try_from(tag_raw).map_err(|()| TpmErrorKind::InvalidDiscriminant {
139        type_name: "TpmSt",
140        value: u64::from(tag_raw),
141    })?;
142
143    let dispatch = PARSE_RESPONSE_MAP
144        .binary_search_by_key(&cc, |d| d.0)
145        .map(|index| &PARSE_RESPONSE_MAP[index])
146        .map_err(|_| TpmErrorKind::InvalidDiscriminant {
147            type_name: "TpmCc",
148            value: u64::from(cc as u32),
149        })?;
150
151    let (body, mut session_area) = (dispatch.2)(body_buf)?;
152
153    let mut auth_responses = TpmAuthResponses::new();
154    if tag == TpmSt::Sessions {
155        while !session_area.is_empty() {
156            let (session, rest) = TpmsAuthResponse::parse(session_area)?;
157            auth_responses
158                .try_push(session)
159                .map_err(|_| TpmErrorKind::ValueTooLarge)?;
160            session_area = rest;
161        }
162    }
163
164    if !session_area.is_empty() {
165        return Err(TpmErrorKind::TrailingData);
166    }
167
168    Ok(Ok((rc, body, auth_responses)))
169}