blob: c43af5aa2fd3669fb4ae3f209860452726f3cd59 [file] [log] [blame]
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2025 Opinsys Oy
// Copyright (c) 2024-2025 Jarkko Sakkinen
use super::{
TpmAuthCommands, TpmAuthResponses, TpmCommandBody, TpmHandles, TpmResponseBody,
TPM_DISPATCH_TABLE,
};
use crate::{
constant::TPM_HEADER_SIZE,
data::{TpmCc, TpmRc, TpmRcBase, TpmSt, TpmsAuthCommand, TpmsAuthResponse},
TpmDiscriminant, TpmError, TpmParse, TpmResult,
};
use core::{convert::TryFrom, mem::size_of};
/// A unified struct holding all dispatch info for a given Command Code.
#[doc(hidden)]
pub struct TpmDispatch {
pub cc: TpmCc,
pub handles: usize,
#[allow(clippy::type_complexity)]
pub command_parser: for<'a> fn(&'a [u8], &'a [u8]) -> TpmResult<(TpmCommandBody, &'a [u8])>,
#[allow(clippy::type_complexity)]
pub response_parser: for<'a> fn(TpmSt, &'a [u8]) -> TpmResult<(TpmResponseBody, &'a [u8])>,
}
/// Represents the dualistic nature of responses.
pub type TpmResponseResult = Result<(TpmResponseBody, TpmAuthResponses), TpmRc>;
/// Parses a command from a TPM command buffer.
///
/// # Errors
///
/// * `TpmError::TruncatedData` if the buffer is too small
/// * `TpmError::UnknownDiscriminant` if the buffer contains an unsupported command code or unexpected byte
/// * `TpmError::TrailingData` if the command has after spurious data left
pub fn tpm_parse_command(buf: &[u8]) -> TpmResult<(TpmHandles, TpmCommandBody, TpmAuthCommands)> {
if buf.len() < TPM_HEADER_SIZE {
return Err(TpmError::TruncatedData);
}
let buf_len = buf.len();
let (tag_raw, buf) = u16::parse(buf)?;
let tag = TpmSt::try_from(tag_raw).map_err(|()| {
TpmError::UnknownDiscriminant("TpmSt", TpmDiscriminant::Unsigned(u64::from(tag_raw)))
})?;
let (size, buf) = u32::parse(buf)?;
let (cc_raw, body_buf) = u32::parse(buf)?;
if buf_len < size as usize {
return Err(TpmError::TruncatedData);
} else if buf_len > size as usize {
return Err(TpmError::TrailingData);
}
let cc = TpmCc::try_from(cc_raw).map_err(|()| {
TpmError::UnknownDiscriminant("TpmCc", TpmDiscriminant::Unsigned(u64::from(cc_raw)))
})?;
let dispatch = TPM_DISPATCH_TABLE
.binary_search_by_key(&cc, |d| d.cc)
.map(|index| &TPM_DISPATCH_TABLE[index])
.map_err(|_| {
TpmError::UnknownDiscriminant("TpmCc", TpmDiscriminant::Unsigned(u64::from(cc_raw)))
})?;
if tag != TpmSt::NoSessions && tag != TpmSt::Sessions {
return Err(TpmError::MalformedData);
}
let handle_area_size = dispatch.handles * size_of::<u32>();
if body_buf.len() < handle_area_size {
return Err(TpmError::TruncatedData);
}
let (handle_area, after_handles) = body_buf.split_at(handle_area_size);
let mut sessions = TpmAuthCommands::new();
let param_area = if tag == TpmSt::Sessions {
let (auth_area_size, buf_after_auth_size) = u32::parse(after_handles)?;
let auth_area_size = auth_area_size as usize;
if buf_after_auth_size.len() < auth_area_size {
return Err(TpmError::TruncatedData);
}
let (mut auth_area, param_area) = buf_after_auth_size.split_at(auth_area_size);
while !auth_area.is_empty() {
let (session, rest) = TpmsAuthCommand::parse(auth_area)?;
sessions.try_push(session)?;
auth_area = rest;
}
if !auth_area.is_empty() {
return Err(TpmError::TrailingData);
}
param_area
} else {
after_handles
};
let (command_data, param_remainder) = (dispatch.command_parser)(handle_area, param_area)?;
if !param_remainder.is_empty() {
return Err(TpmError::TrailingData);
}
let mut handles = TpmHandles::new();
let mut temp_handle_cursor = handle_area;
while !temp_handle_cursor.is_empty() {
let (handle, rest) = u32::parse(temp_handle_cursor)?;
handles.try_push(handle.into())?;
temp_handle_cursor = rest;
}
Ok((handles, command_data, sessions))
}
/// Parses a response from a TPM response buffer.
///
/// # Errors
///
/// * `TpmError::TruncatedData` if the buffer is too small
/// * `TpmError::UnknownDiscriminant` if the buffer contains an unsupported command code
/// * `TpmError::TrailingData` if the response has after spurious data left
pub fn tpm_parse_response(cc: TpmCc, buf: &[u8]) -> TpmResult<TpmResponseResult> {
if buf.len() < TPM_HEADER_SIZE {
return Err(TpmError::TruncatedData);
}
let (tag_raw, remainder) = u16::parse(buf)?;
let (size, remainder) = u32::parse(remainder)?;
let (code, body_buf) = u32::parse(remainder)?;
if buf.len() < size as usize {
return Err(TpmError::TruncatedData);
} else if buf.len() > size as usize {
return Err(TpmError::TrailingData);
}
let rc = TpmRc::try_from(code)?;
if !matches!(rc, TpmRc::Fmt0(TpmRcBase::Success)) {
return Ok(Err(rc));
}
let tag = TpmSt::try_from(tag_raw).map_err(|()| {
TpmError::UnknownDiscriminant("TpmSt", TpmDiscriminant::Unsigned(u64::from(tag_raw)))
})?;
let dispatch = TPM_DISPATCH_TABLE
.binary_search_by_key(&cc, |d| d.cc)
.map(|index| &TPM_DISPATCH_TABLE[index])
.map_err(|_| {
TpmError::UnknownDiscriminant("TpmCc", TpmDiscriminant::Unsigned(u64::from(cc as u32)))
})?;
let (body, mut session_area) = (dispatch.response_parser)(tag, body_buf)?;
let mut auth_responses = TpmAuthResponses::new();
if tag == TpmSt::Sessions {
while !session_area.is_empty() {
let (session, rest) = TpmsAuthResponse::parse(session_area)?;
auth_responses.try_push(session)?;
session_area = rest;
}
}
if !session_area.is_empty() {
return Err(TpmError::TrailingData);
}
Ok(Ok((body, auth_responses)))
}