Skip to main content

tpm2_protocol/frame/
wire.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2026 Jarkko Sakkinen
4
5use super::{TPM_DISPATCH_TABLE, TPM_HEADER_SIZE};
6use crate::{
7    TpmCast, TpmCastMut, TpmProtocolError, TpmResult,
8    constant::MAX_SESSIONS,
9    data::{TpmCc, TpmRc, TpmRcBase, TpmSt},
10};
11use core::{mem::size_of, ops::Range};
12
13const HEADER_SIZE: usize = TPM_HEADER_SIZE as usize;
14const TAG_OFFSET: usize = 0;
15const SIZE_OFFSET: usize = 2;
16const CODE_OFFSET: usize = 6;
17
18/// A zero-copy TPM command wire view over caller-owned bytes.
19#[repr(transparent)]
20pub struct TpmCommand([u8]);
21
22impl TpmCommand {
23    /// Casts a byte slice into a TPM command wire view.
24    ///
25    /// # Errors
26    ///
27    /// Returns `Err(TpmProtocolError)` when the command envelope is malformed.
28    pub fn cast(buf: &[u8]) -> TpmResult<&Self> {
29        Self::validate_envelope(buf)?;
30
31        // SAFETY: `validate_envelope` checked the command frame bounds and
32        // dispatch invariants required for this transparent wire view.
33        Ok(unsafe { Self::cast_unchecked(buf) })
34    }
35
36    /// Casts a byte slice into a TPM command wire view without validation.
37    ///
38    /// # Safety
39    ///
40    /// The caller must ensure that `buf` contains exactly one complete TPM
41    /// command frame with a valid command code and handle area layout.
42    #[must_use]
43    pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
44        let ptr = core::ptr::from_ref(buf) as *const Self;
45
46        // SAFETY: `TpmCommand` is `repr(transparent)` over `[u8]`, so it has
47        // the same layout, metadata, and alignment as the referenced slice.
48        unsafe { &*ptr }
49    }
50
51    /// Casts a mutable byte slice into a mutable TPM command wire view.
52    ///
53    /// # Errors
54    ///
55    /// Returns `Err(TpmProtocolError)` when the command envelope is malformed.
56    pub fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
57        Self::validate_envelope(buf)?;
58
59        // SAFETY: `validate_envelope` checked the command frame bounds and
60        // dispatch invariants required for this transparent wire view. The
61        // `&mut` input provides exclusive access.
62        Ok(unsafe { Self::cast_mut_unchecked(buf) })
63    }
64
65    /// Casts a mutable byte slice into a mutable TPM command wire view without validation.
66    ///
67    /// # Safety
68    ///
69    /// The caller must ensure that `buf` contains exactly one complete TPM
70    /// command frame with a valid command code and handle area layout. The
71    /// returned reference inherits the exclusive access represented by `buf`.
72    #[must_use]
73    pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
74        let ptr = core::ptr::from_mut(buf) as *mut Self;
75
76        // SAFETY: `TpmCommand` is `repr(transparent)` over `[u8]`, so it has
77        // the same layout, metadata, and alignment as the referenced slice.
78        unsafe { &mut *ptr }
79    }
80
81    /// Returns the complete command frame bytes.
82    #[must_use]
83    pub const fn as_bytes(&self) -> &[u8] {
84        &self.0
85    }
86
87    /// Returns the mutable command frame bytes.
88    #[must_use]
89    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
90        &mut self.0
91    }
92
93    /// Returns the command tag.
94    ///
95    /// # Errors
96    ///
97    /// Returns [`VariantNotAvailable`](crate::TpmProtocolError::VariantNotAvailable)
98    /// when the tag value is not defined.
99    pub fn tag(&self) -> TpmResult<TpmSt> {
100        TpmSt::try_from(read_u16(&self.0, TAG_OFFSET))
101    }
102
103    /// Returns the command frame size field.
104    #[must_use]
105    pub fn size(&self) -> u32 {
106        read_u32(&self.0, SIZE_OFFSET)
107    }
108
109    /// Returns the command code.
110    ///
111    /// # Errors
112    ///
113    /// Returns [`InvalidCc`](crate::TpmProtocolError::InvalidCc) when the
114    /// command code has no dispatch entry.
115    pub fn cc(&self) -> TpmResult<TpmCc> {
116        command_code(&self.0)
117    }
118
119    /// Sets the command tag without changing the frame shape.
120    pub fn set_tag(&mut self, tag: TpmSt) {
121        write_u16(&mut self.0, TAG_OFFSET, tag.value());
122    }
123
124    /// Sets the command code without changing the frame shape.
125    ///
126    /// # Errors
127    ///
128    /// Returns [`InvalidCc`](crate::TpmProtocolError::InvalidCc) when the
129    /// command code has no dispatch entry.
130    pub fn set_cc(&mut self, cc: TpmCc) -> TpmResult<()> {
131        let _ = dispatch_for(cc)?;
132
133        write_u32(&mut self.0, CODE_OFFSET, cc.value());
134        Ok(())
135    }
136
137    /// Returns the command handle area bytes.
138    ///
139    /// # Errors
140    ///
141    /// Returns `Err(TpmProtocolError)` when the command envelope is malformed.
142    pub fn handles(&self) -> TpmResult<&[u8]> {
143        let range = self.handle_area_range()?;
144
145        Ok(&self.0[range])
146    }
147
148    /// Returns the mutable command handle area bytes.
149    ///
150    /// # Errors
151    ///
152    /// Returns `Err(TpmProtocolError)` when the command envelope is malformed.
153    pub fn handles_mut(&mut self) -> TpmResult<&mut [u8]> {
154        let range = self.handle_area_range()?;
155
156        Ok(&mut self.0[range])
157    }
158
159    /// Returns the command authorization area bytes.
160    ///
161    /// # Errors
162    ///
163    /// Returns `Err(TpmProtocolError)` when the command has no sessions or its
164    /// authorization area is malformed.
165    pub fn auth_area(&self) -> TpmResult<&[u8]> {
166        let (auth_area, _) = self.session_and_parameter_ranges()?;
167
168        Ok(&self.0[auth_area])
169    }
170
171    /// Returns the mutable command authorization area bytes.
172    ///
173    /// # Errors
174    ///
175    /// Returns `Err(TpmProtocolError)` when the command has no sessions or its
176    /// authorization area is malformed.
177    pub fn auth_area_mut(&mut self) -> TpmResult<&mut [u8]> {
178        let (auth_area, _) = self.session_and_parameter_ranges()?;
179
180        Ok(&mut self.0[auth_area])
181    }
182
183    /// Returns the command parameter area bytes.
184    ///
185    /// # Errors
186    ///
187    /// Returns `Err(TpmProtocolError)` when the command envelope is malformed.
188    pub fn parameters(&self) -> TpmResult<&[u8]> {
189        let (_, parameters) = self.session_and_parameter_ranges()?;
190
191        Ok(&self.0[parameters])
192    }
193
194    /// Returns the mutable command parameter area bytes.
195    ///
196    /// # Errors
197    ///
198    /// Returns `Err(TpmProtocolError)` when the command envelope is malformed.
199    pub fn parameters_mut(&mut self) -> TpmResult<&mut [u8]> {
200        let (_, parameters) = self.session_and_parameter_ranges()?;
201
202        Ok(&mut self.0[parameters])
203    }
204
205    /// Validates command frame structure without constructing an owned command body.
206    ///
207    /// # Errors
208    ///
209    /// Returns `Err(TpmProtocolError)` when the command frame is malformed.
210    pub fn validate(&self) -> TpmResult<()> {
211        Self::validate_envelope(&self.0)?;
212        validate_auth_commands(self.auth_area()?)
213    }
214
215    /// Returns `true` when the command frame contains no bytes.
216    #[must_use]
217    pub const fn is_empty(&self) -> bool {
218        self.0.is_empty()
219    }
220
221    /// Returns the command frame length.
222    #[must_use]
223    pub const fn len(&self) -> usize {
224        self.0.len()
225    }
226
227    fn handle_area_range(&self) -> TpmResult<Range<usize>> {
228        let dispatch = dispatch_for(self.cc()?)?;
229        let handle_area_size = dispatch.handles * size_of::<u32>();
230
231        Ok(HEADER_SIZE..HEADER_SIZE + handle_area_size)
232    }
233
234    fn session_and_parameter_ranges(&self) -> TpmResult<(Range<usize>, Range<usize>)> {
235        let handle_area = self.handle_area_range()?;
236        let tag = self.tag()?;
237        let after_handles_start = handle_area.end;
238
239        if tag != TpmSt::Sessions {
240            return Ok((
241                after_handles_start..after_handles_start,
242                after_handles_start..self.0.len(),
243            ));
244        }
245
246        let after_handles = &self.0[after_handles_start..];
247
248        if after_handles.len() < size_of::<u32>() {
249            return Err(TpmProtocolError::UnexpectedEnd);
250        }
251
252        let auth_size = read_u32(after_handles, 0) as usize;
253        let auth_start = size_of::<u32>();
254        let auth_end = auth_start
255            .checked_add(auth_size)
256            .ok_or(TpmProtocolError::IntegerTooLarge)?;
257
258        if after_handles.len() < auth_end {
259            return Err(TpmProtocolError::UnexpectedEnd);
260        }
261
262        let auth_start = after_handles_start + auth_start;
263        let auth_end = after_handles_start + auth_end;
264
265        Ok((auth_start..auth_end, auth_end..self.0.len()))
266    }
267
268    fn validate_envelope(buf: &[u8]) -> TpmResult<()> {
269        validate_frame_size(buf)?;
270
271        let tag = TpmSt::try_from(read_u16(buf, TAG_OFFSET))?;
272        if tag != TpmSt::NoSessions && tag != TpmSt::Sessions {
273            return Err(TpmProtocolError::InvalidTag);
274        }
275
276        let dispatch = dispatch_for(command_code(buf)?)?;
277        let body = &buf[HEADER_SIZE..];
278        let handle_area_size = dispatch.handles * size_of::<u32>();
279
280        if body.len() < handle_area_size {
281            return Err(TpmProtocolError::UnexpectedEnd);
282        }
283
284        if tag == TpmSt::Sessions {
285            let after_handles = &body[handle_area_size..];
286            if after_handles.len() < size_of::<u32>() {
287                return Err(TpmProtocolError::UnexpectedEnd);
288            }
289
290            let auth_size = read_u32(after_handles, 0) as usize;
291            let auth_end = size_of::<u32>()
292                .checked_add(auth_size)
293                .ok_or(TpmProtocolError::IntegerTooLarge)?;
294
295            if after_handles.len() < auth_end {
296                return Err(TpmProtocolError::UnexpectedEnd);
297            }
298        }
299
300        Ok(())
301    }
302}
303
304impl TpmCast for TpmCommand {
305    fn cast(buf: &[u8]) -> TpmResult<&Self> {
306        Self::cast(buf)
307    }
308
309    unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
310        // SAFETY: The caller upholds the unchecked cast contract for `TpmCommand`.
311        unsafe { Self::cast_unchecked(buf) }
312    }
313}
314
315impl TpmCastMut for TpmCommand {
316    fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
317        Self::cast_mut(buf)
318    }
319
320    unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
321        // SAFETY: The caller upholds the unchecked mutable cast contract for
322        // `TpmCommand`.
323        unsafe { Self::cast_mut_unchecked(buf) }
324    }
325}
326
327impl AsRef<[u8]> for TpmCommand {
328    fn as_ref(&self) -> &[u8] {
329        self.as_bytes()
330    }
331}
332
333impl AsMut<[u8]> for TpmCommand {
334    fn as_mut(&mut self) -> &mut [u8] {
335        self.as_bytes_mut()
336    }
337}
338
339/// A zero-copy TPM response wire view over caller-owned bytes.
340#[repr(transparent)]
341pub struct TpmResponse([u8]);
342
343impl TpmResponse {
344    /// Casts a byte slice into a TPM response wire view.
345    ///
346    /// # Errors
347    ///
348    /// Returns `Err(TpmProtocolError)` when the response envelope is malformed.
349    pub fn cast(buf: &[u8]) -> TpmResult<&Self> {
350        Self::validate_envelope(buf)?;
351
352        // SAFETY: `validate_envelope` checked the response frame bounds
353        // required for this transparent wire view.
354        Ok(unsafe { Self::cast_unchecked(buf) })
355    }
356
357    /// Casts a byte slice into a TPM response wire view without validation.
358    ///
359    /// # Safety
360    ///
361    /// The caller must ensure that `buf` contains exactly one complete TPM
362    /// response frame.
363    #[must_use]
364    pub unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
365        let ptr = core::ptr::from_ref(buf) as *const Self;
366
367        // SAFETY: `TpmResponse` is `repr(transparent)` over `[u8]`, so it has
368        // the same layout, metadata, and alignment as the referenced slice.
369        unsafe { &*ptr }
370    }
371
372    /// Casts a mutable byte slice into a mutable TPM response wire view.
373    ///
374    /// # Errors
375    ///
376    /// Returns `Err(TpmProtocolError)` when the response envelope is malformed.
377    pub fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
378        Self::validate_envelope(buf)?;
379
380        // SAFETY: `validate_envelope` checked the response frame bounds
381        // required for this transparent wire view. The `&mut` input provides
382        // exclusive access.
383        Ok(unsafe { Self::cast_mut_unchecked(buf) })
384    }
385
386    /// Casts a mutable byte slice into a mutable TPM response wire view without validation.
387    ///
388    /// # Safety
389    ///
390    /// The caller must ensure that `buf` contains exactly one complete TPM
391    /// response frame. The returned reference inherits the exclusive access
392    /// represented by `buf`.
393    #[must_use]
394    pub unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
395        let ptr = core::ptr::from_mut(buf) as *mut Self;
396
397        // SAFETY: `TpmResponse` is `repr(transparent)` over `[u8]`, so it has
398        // the same layout, metadata, and alignment as the referenced slice.
399        unsafe { &mut *ptr }
400    }
401
402    /// Returns the complete response frame bytes.
403    #[must_use]
404    pub const fn as_bytes(&self) -> &[u8] {
405        &self.0
406    }
407
408    /// Returns the mutable response frame bytes.
409    #[must_use]
410    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
411        &mut self.0
412    }
413
414    /// Returns the response tag.
415    ///
416    /// # Errors
417    ///
418    /// Returns [`VariantNotAvailable`](crate::TpmProtocolError::VariantNotAvailable)
419    /// when the tag value is not defined.
420    pub fn tag(&self) -> TpmResult<TpmSt> {
421        TpmSt::try_from(read_u16(&self.0, TAG_OFFSET))
422    }
423
424    /// Returns the response frame size field.
425    #[must_use]
426    pub fn size(&self) -> u32 {
427        read_u32(&self.0, SIZE_OFFSET)
428    }
429
430    /// Returns the response code.
431    ///
432    /// # Errors
433    ///
434    /// Returns `Err(TpmProtocolError)` when the response code is malformed.
435    pub fn rc(&self) -> TpmResult<TpmRc> {
436        TpmRc::try_from(read_u32(&self.0, CODE_OFFSET))
437    }
438
439    /// Sets the response tag without changing the frame shape.
440    pub fn set_tag(&mut self, tag: TpmSt) {
441        write_u16(&mut self.0, TAG_OFFSET, tag.value());
442    }
443
444    /// Sets the response code without changing the frame shape.
445    pub fn set_rc(&mut self, rc: TpmRc) {
446        write_u32(&mut self.0, CODE_OFFSET, rc.value());
447    }
448
449    /// Returns the response body bytes after the TPM header.
450    #[must_use]
451    pub fn body(&self) -> &[u8] {
452        &self.0[HEADER_SIZE..]
453    }
454
455    /// Returns the mutable response body bytes after the TPM header.
456    #[must_use]
457    pub fn body_mut(&mut self) -> &mut [u8] {
458        &mut self.0[HEADER_SIZE..]
459    }
460
461    /// Validates response frame structure without constructing an owned response body.
462    ///
463    /// # Errors
464    ///
465    /// Returns `Err(TpmProtocolError)` when the response frame is malformed or
466    /// `cc` has no dispatch entry.
467    pub fn validate(&self, cc: TpmCc) -> TpmResult<()> {
468        Self::validate_envelope(&self.0)?;
469        let dispatch = dispatch_for(cc)?;
470
471        if !matches!(self.rc()?, TpmRc::Fmt0(TpmRcBase::Success)) {
472            return Ok(());
473        }
474
475        if self.tag()? != TpmSt::Sessions {
476            return Ok(());
477        }
478
479        let handle_area_size = dispatch.response_handles * size_of::<u32>();
480        let body = self.body();
481        if body.len() < handle_area_size {
482            return Err(TpmProtocolError::UnexpectedEnd);
483        }
484
485        let after_handles = &body[handle_area_size..];
486        if after_handles.len() < size_of::<u32>() {
487            return Err(TpmProtocolError::UnexpectedEnd);
488        }
489
490        let params_len = read_u32(after_handles, 0) as usize;
491        let sessions_start = size_of::<u32>()
492            .checked_add(params_len)
493            .ok_or(TpmProtocolError::IntegerTooLarge)?;
494
495        if after_handles.len() < sessions_start {
496            return Err(TpmProtocolError::UnexpectedEnd);
497        }
498
499        validate_auth_responses(&after_handles[sessions_start..])
500    }
501
502    /// Returns `true` when the response frame contains no bytes.
503    #[must_use]
504    pub const fn is_empty(&self) -> bool {
505        self.0.is_empty()
506    }
507
508    /// Returns the response frame length.
509    #[must_use]
510    pub const fn len(&self) -> usize {
511        self.0.len()
512    }
513
514    fn validate_envelope(buf: &[u8]) -> TpmResult<()> {
515        validate_frame_size(buf)?;
516        let _ = TpmSt::try_from(read_u16(buf, TAG_OFFSET))?;
517        let _ = TpmRc::try_from(read_u32(buf, CODE_OFFSET))?;
518
519        Ok(())
520    }
521}
522
523impl TpmCast for TpmResponse {
524    fn cast(buf: &[u8]) -> TpmResult<&Self> {
525        Self::cast(buf)
526    }
527
528    unsafe fn cast_unchecked(buf: &[u8]) -> &Self {
529        // SAFETY: The caller upholds the unchecked cast contract for `TpmResponse`.
530        unsafe { Self::cast_unchecked(buf) }
531    }
532}
533
534impl TpmCastMut for TpmResponse {
535    fn cast_mut(buf: &mut [u8]) -> TpmResult<&mut Self> {
536        Self::cast_mut(buf)
537    }
538
539    unsafe fn cast_mut_unchecked(buf: &mut [u8]) -> &mut Self {
540        // SAFETY: The caller upholds the unchecked mutable cast contract for
541        // `TpmResponse`.
542        unsafe { Self::cast_mut_unchecked(buf) }
543    }
544}
545
546impl AsRef<[u8]> for TpmResponse {
547    fn as_ref(&self) -> &[u8] {
548        self.as_bytes()
549    }
550}
551
552impl AsMut<[u8]> for TpmResponse {
553    fn as_mut(&mut self) -> &mut [u8] {
554        self.as_bytes_mut()
555    }
556}
557
558fn command_code(buf: &[u8]) -> TpmResult<TpmCc> {
559    TpmCc::try_from(read_u32(buf, CODE_OFFSET)).map_err(|_| TpmProtocolError::InvalidCc)
560}
561
562fn dispatch_for(cc: TpmCc) -> TpmResult<&'static super::TpmDispatch> {
563    TPM_DISPATCH_TABLE
564        .binary_search_by_key(&cc, |d| d.cc)
565        .map(|index| &TPM_DISPATCH_TABLE[index])
566        .map_err(|_| TpmProtocolError::InvalidCc)
567}
568
569fn validate_frame_size(buf: &[u8]) -> TpmResult<()> {
570    if buf.len() < HEADER_SIZE {
571        return Err(TpmProtocolError::UnexpectedEnd);
572    }
573
574    let size = read_u32(buf, SIZE_OFFSET) as usize;
575    if buf.len() < size {
576        return Err(TpmProtocolError::UnexpectedEnd);
577    }
578
579    if buf.len() > size {
580        return Err(TpmProtocolError::TrailingData);
581    }
582
583    Ok(())
584}
585
586fn read_u16(buf: &[u8], offset: usize) -> u16 {
587    u16::from_be_bytes([buf[offset], buf[offset + 1]])
588}
589
590fn read_u32(buf: &[u8], offset: usize) -> u32 {
591    u32::from_be_bytes([
592        buf[offset],
593        buf[offset + 1],
594        buf[offset + 2],
595        buf[offset + 3],
596    ])
597}
598
599fn write_u16(buf: &mut [u8], offset: usize, value: u16) {
600    buf[offset..offset + size_of::<u16>()].copy_from_slice(&value.to_be_bytes());
601}
602
603fn write_u32(buf: &mut [u8], offset: usize, value: u32) {
604    buf[offset..offset + size_of::<u32>()].copy_from_slice(&value.to_be_bytes());
605}
606
607fn validate_auth_commands(mut buf: &[u8]) -> TpmResult<()> {
608    let mut count = 0;
609
610    while !buf.is_empty() {
611        if count >= MAX_SESSIONS {
612            return Err(TpmProtocolError::TooManyItems);
613        }
614
615        if buf.len() < size_of::<u32>() {
616            return Err(TpmProtocolError::UnexpectedEnd);
617        }
618
619        buf = &buf[size_of::<u32>()..];
620        buf = skip_tpm2b(buf)?;
621
622        if buf.is_empty() {
623            return Err(TpmProtocolError::UnexpectedEnd);
624        }
625
626        buf = &buf[1..];
627        buf = skip_tpm2b(buf)?;
628        count += 1;
629    }
630
631    Ok(())
632}
633
634fn validate_auth_responses(mut buf: &[u8]) -> TpmResult<()> {
635    let mut count = 0;
636
637    while !buf.is_empty() {
638        if count >= MAX_SESSIONS {
639            return Err(TpmProtocolError::TooManyItems);
640        }
641
642        buf = skip_tpm2b(buf)?;
643
644        if buf.is_empty() {
645            return Err(TpmProtocolError::UnexpectedEnd);
646        }
647
648        buf = &buf[1..];
649        buf = skip_tpm2b(buf)?;
650        count += 1;
651    }
652
653    Ok(())
654}
655
656fn skip_tpm2b(buf: &[u8]) -> TpmResult<&[u8]> {
657    if buf.len() < size_of::<u16>() {
658        return Err(TpmProtocolError::UnexpectedEnd);
659    }
660
661    let size = read_u16(buf, 0) as usize;
662    let end = size_of::<u16>()
663        .checked_add(size)
664        .ok_or(TpmProtocolError::IntegerTooLarge)?;
665
666    if buf.len() < end {
667        return Err(TpmProtocolError::UnexpectedEnd);
668    }
669
670    Ok(&buf[end..])
671}