⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/security/
tpm_commands.rs

1//! TPM 2.0 Command Structures
2//!
3//! TPM command and response packet formats per TPM 2.0 specification (Part 3).
4//!
5//! ## TPM 2.0 Command Format
6//!
7//! All TPM commands follow this structure:
8//! ```text
9//! +-------------------+
10//! | Tag (2 bytes)     |  TPM_ST_SESSIONS or TPM_ST_NO_SESSIONS
11//! +-------------------+
12//! | Size (4 bytes)    |  Total packet size
13//! +-------------------+
14//! | Command (4 bytes) |  TPM_CC_* command code
15//! +-------------------+
16//! | Parameters        |  Command-specific
17//! +-------------------+
18//! ```
19//!
20//! ## Supported Commands
21//!
22//! - `TPM2_Startup` / `TPM2_Shutdown` -- lifecycle
23//! - `TPM2_GetRandom` -- hardware RNG
24//! - `TPM2_PCR_Read` / `TPM2_PCR_Extend` -- measured boot
25//! - `TPM2_SelfTest` -- POST diagnostics
26//! - `TPM2_GetCapability` -- feature query
27
28use alloc::{vec, vec::Vec};
29
30use crate::error::KernelError;
31
32/// TPM Structure Tags (TPM_ST)
33#[repr(u16)]
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum TpmStructureTag {
36    /// No sessions in command/response
37    NoSessions = 0x8001,
38    /// Command/response has sessions
39    Sessions = 0x8002,
40}
41
42/// TPM Command Codes (TPM_CC, partial list)
43#[repr(u32)]
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum TpmCommandCode {
46    Startup = 0x00000144,
47    Shutdown = 0x00000145,
48    SelfTest = 0x00000143,
49    GetCapability = 0x0000017A,
50    GetRandom = 0x0000017B,
51    PcrRead = 0x0000017E,
52    PcrExtend = 0x00000182,
53    Create = 0x00000153,
54    Load = 0x00000157,
55    Sign = 0x0000015D,
56    VerifySignature = 0x00000177,
57    Quote = 0x00000158,
58    CreatePrimary = 0x00000131,
59}
60
61/// TPM Response Codes (TPM_RC)
62#[repr(u32)]
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum TpmResponseCode {
65    Success = 0x00000000,
66    Failure = 0x00000101,
67    BadTag = 0x0000001E,
68    Retry = 0x00000922,
69    Yielded = 0x00000908,
70    Canceled = 0x00000909,
71}
72
73impl TpmResponseCode {
74    pub fn from_u32(value: u32) -> Self {
75        match value {
76            0x00000000 => TpmResponseCode::Success,
77            0x00000101 => TpmResponseCode::Failure,
78            0x0000001E => TpmResponseCode::BadTag,
79            0x00000922 => TpmResponseCode::Retry,
80            0x00000908 => TpmResponseCode::Yielded,
81            0x00000909 => TpmResponseCode::Canceled,
82            _ => TpmResponseCode::Failure,
83        }
84    }
85
86    pub fn is_success(&self) -> bool {
87        matches!(self, TpmResponseCode::Success)
88    }
89}
90
91/// TPM Startup Types (TPM_SU)
92#[repr(u16)]
93#[derive(Debug, Clone, Copy)]
94pub enum TpmStartupType {
95    /// TPM2_Startup(CLEAR) -- reset all PCRs, clear state
96    Clear = 0x0000,
97    /// TPM2_Startup(STATE) -- restore saved state
98    State = 0x0001,
99}
100
101/// TPM Shutdown Types (TPM_SU)
102#[repr(u16)]
103#[derive(Debug, Clone, Copy)]
104pub enum TpmShutdownType {
105    /// TPM2_Shutdown(CLEAR) -- discard state
106    Clear = 0x0000,
107    /// TPM2_Shutdown(STATE) -- save state for resume
108    State = 0x0001,
109}
110
111// ============================================================================
112// Marshaling helpers
113// ============================================================================
114
115/// Marshal a command header into big-endian bytes.
116///
117/// The header is always 10 bytes: tag(2) + size(4) + command_code(4).
118fn marshal_header(tag: TpmStructureTag, command: TpmCommandCode, total_size: u32) -> Vec<u8> {
119    let mut bytes = Vec::with_capacity(10);
120    bytes.extend_from_slice(&(tag as u16).to_be_bytes());
121    bytes.extend_from_slice(&total_size.to_be_bytes());
122    bytes.extend_from_slice(&(command as u32).to_be_bytes());
123    bytes
124}
125
126/// Marshal a generic TPM command into a byte buffer.
127///
128/// Takes the command code, tag, and parameter bytes and produces a complete
129/// command packet with the correct header.
130pub fn marshal_command(tag: TpmStructureTag, command: TpmCommandCode, params: &[u8]) -> Vec<u8> {
131    let total_size = (10 + params.len()) as u32;
132    let mut bytes = marshal_header(tag, command, total_size);
133    bytes.extend_from_slice(params);
134    bytes
135}
136
137/// Parse a TPM response buffer and extract the response code and payload.
138///
139/// Returns `(response_code, payload)` where payload is everything after the
140/// 10-byte header. Returns `None` if the buffer is too short.
141pub fn parse_response(data: &[u8]) -> Option<(TpmResponseCode, &[u8])> {
142    let header = TpmResponseHeader::parse(data)?;
143    let total_size = header.size as usize;
144
145    if data.len() < total_size || total_size < 10 {
146        return None;
147    }
148
149    let code = header.response_code();
150    let payload = &data[10..total_size];
151    Some((code, payload))
152}
153
154// ============================================================================
155// Header structures
156// ============================================================================
157
158/// TPM Command Header (10 bytes, big-endian on wire)
159#[repr(C, packed)]
160#[derive(Debug, Clone, Copy)]
161pub struct TpmCommandHeader {
162    pub tag: u16,     // TpmStructureTag
163    pub size: u32,    // Total command size in bytes
164    pub command: u32, // TpmCommandCode
165}
166
167impl TpmCommandHeader {
168    pub fn new(tag: TpmStructureTag, command: TpmCommandCode, size: u32) -> Self {
169        Self {
170            tag: tag as u16,
171            size: size.to_be(),
172            command: (command as u32).to_be(),
173        }
174    }
175}
176
177/// TPM Response Header (10 bytes, big-endian on wire)
178#[repr(C, packed)]
179#[derive(Debug, Clone, Copy)]
180pub struct TpmResponseHeader {
181    pub tag: u16,
182    pub size: u32,
183    pub response_code: u32,
184}
185
186impl TpmResponseHeader {
187    /// Parse a response header from a byte slice (at least 10 bytes).
188    pub fn parse(data: &[u8]) -> Option<Self> {
189        if data.len() < 10 {
190            return None;
191        }
192
193        Some(Self {
194            tag: u16::from_be_bytes([data[0], data[1]]),
195            size: u32::from_be_bytes([data[2], data[3], data[4], data[5]]),
196            response_code: u32::from_be_bytes([data[6], data[7], data[8], data[9]]),
197        })
198    }
199
200    /// Decode the response code enum from the raw field.
201    pub fn response_code(&self) -> TpmResponseCode {
202        // The field was stored in big-endian by parse(), so it is already host-order.
203        TpmResponseCode::from_u32(self.response_code)
204    }
205}
206
207// ============================================================================
208// TPM2_Startup
209// ============================================================================
210
211/// TPM2_Startup command
212pub struct TpmStartupCommand {
213    startup_type: TpmStartupType,
214}
215
216impl TpmStartupCommand {
217    pub fn new(startup_type: TpmStartupType) -> Self {
218        Self { startup_type }
219    }
220
221    pub fn to_bytes(&self) -> Vec<u8> {
222        let params = (self.startup_type as u16).to_be_bytes();
223        marshal_command(
224            TpmStructureTag::NoSessions,
225            TpmCommandCode::Startup,
226            &params,
227        )
228    }
229}
230
231// ============================================================================
232// TPM2_Shutdown
233// ============================================================================
234
235/// TPM2_Shutdown command
236pub struct TpmShutdownCommand {
237    shutdown_type: TpmShutdownType,
238}
239
240impl TpmShutdownCommand {
241    pub fn new(shutdown_type: TpmShutdownType) -> Self {
242        Self { shutdown_type }
243    }
244
245    pub fn to_bytes(&self) -> Vec<u8> {
246        let params = (self.shutdown_type as u16).to_be_bytes();
247        marshal_command(
248            TpmStructureTag::NoSessions,
249            TpmCommandCode::Shutdown,
250            &params,
251        )
252    }
253}
254
255// ============================================================================
256// TPM2_SelfTest
257// ============================================================================
258
259/// TPM2_SelfTest command
260pub struct TpmSelfTestCommand {
261    /// If true, run full self-test; if false, incremental only
262    full_test: bool,
263}
264
265impl TpmSelfTestCommand {
266    pub fn new(full_test: bool) -> Self {
267        Self { full_test }
268    }
269
270    pub fn to_bytes(&self) -> Vec<u8> {
271        let params = [if self.full_test { 1u8 } else { 0u8 }];
272        marshal_command(
273            TpmStructureTag::NoSessions,
274            TpmCommandCode::SelfTest,
275            &params,
276        )
277    }
278}
279
280// ============================================================================
281// TPM2_GetRandom
282// ============================================================================
283
284/// TPM2_GetRandom command
285pub struct TpmGetRandomCommand {
286    bytes_requested: u16,
287}
288
289impl TpmGetRandomCommand {
290    pub fn new(bytes_requested: u16) -> Self {
291        Self { bytes_requested }
292    }
293
294    pub fn to_bytes(&self) -> Vec<u8> {
295        let params = self.bytes_requested.to_be_bytes();
296        marshal_command(
297            TpmStructureTag::NoSessions,
298            TpmCommandCode::GetRandom,
299            &params,
300        )
301    }
302}
303
304/// TPM2_GetRandom response parser
305pub struct TpmGetRandomResponse {
306    pub random_bytes: Vec<u8>,
307}
308
309impl TpmGetRandomResponse {
310    /// Parse a TPM2_GetRandom response.
311    ///
312    /// Format: header(10) + randomBytesCount(2) + randomBytes(N)
313    pub fn parse(data: &[u8]) -> Result<Self, KernelError> {
314        if data.len() < 12 {
315            return Err(KernelError::InvalidArgument {
316                name: "tpm_response",
317                value: "response too short",
318            });
319        }
320
321        let header = TpmResponseHeader::parse(data).ok_or(KernelError::InvalidArgument {
322            name: "tpm_response",
323            value: "invalid response header",
324        })?;
325
326        if !header.response_code().is_success() {
327            return Err(KernelError::HardwareError {
328                device: "TPM",
329                code: header.response_code() as u32,
330            });
331        }
332
333        let bytes_len = u16::from_be_bytes([data[10], data[11]]) as usize;
334
335        if data.len() < 12 + bytes_len {
336            return Err(KernelError::InvalidArgument {
337                name: "tpm_response",
338                value: "invalid random bytes length",
339            });
340        }
341
342        let random_bytes = data[12..12 + bytes_len].to_vec();
343
344        Ok(Self { random_bytes })
345    }
346}
347
348// ============================================================================
349// TPM2_PCR_Read
350// ============================================================================
351
352/// PCR selection structure (TPMS_PCR_SELECTION)
353#[derive(Debug, Clone)]
354pub struct PcrSelection {
355    /// Hash algorithm (TPM_ALG_*), e.g., SHA256 = 0x000B
356    pub hash_alg: u16,
357    /// Bitmap of selected PCRs (3 bytes = up to 24 PCRs)
358    pub pcr_bitmap: Vec<u8>,
359}
360
361/// TPM2_PCR_Read command
362pub struct TpmPcrReadCommand {
363    pcr_selection: PcrSelection,
364}
365
366impl TpmPcrReadCommand {
367    /// Create a PCR_Read command for the given hash algorithm and PCR indices.
368    pub fn new(hash_alg: u16, pcr_indices: &[u8]) -> Self {
369        let mut bitmap = vec![0u8; 3];
370
371        for &pcr in pcr_indices {
372            if (pcr as usize) < 24 {
373                let byte_idx = (pcr / 8) as usize;
374                let bit_idx = pcr % 8;
375                bitmap[byte_idx] |= 1 << bit_idx;
376            }
377        }
378
379        Self {
380            pcr_selection: PcrSelection {
381                hash_alg,
382                pcr_bitmap: bitmap,
383            },
384        }
385    }
386
387    pub fn to_bytes(&self) -> Vec<u8> {
388        // Parameters: pcrSelectionIn (TPML_PCR_SELECTION)
389        //   count(4) + [ hashAlg(2) + sizeOfSelect(1) + pcrSelect(N) ]
390        let mut params = Vec::new();
391
392        // Count of selections (1)
393        params.extend_from_slice(&1u32.to_be_bytes());
394
395        // Hash algorithm
396        params.extend_from_slice(&self.pcr_selection.hash_alg.to_be_bytes());
397
398        // Size of select bitmap
399        params.push(self.pcr_selection.pcr_bitmap.len() as u8);
400
401        // PCR bitmap
402        params.extend_from_slice(&self.pcr_selection.pcr_bitmap);
403
404        marshal_command(
405            TpmStructureTag::NoSessions,
406            TpmCommandCode::PcrRead,
407            &params,
408        )
409    }
410}
411
412/// TPM2_PCR_Read response parser
413pub struct TpmPcrReadResponse {
414    /// PCR update counter
415    pub pcr_update_counter: u32,
416    /// The PCR digest values returned
417    pub pcr_values: Vec<Vec<u8>>,
418}
419
420impl TpmPcrReadResponse {
421    /// Parse a TPM2_PCR_Read response.
422    ///
423    /// Format: header(10) + pcrUpdateCounter(4) + pcrSelectionOut(var) +
424    ///         pcrValues: count(4) + [ size(2) + digest(N) ]*
425    pub fn parse(data: &[u8]) -> Result<Self, KernelError> {
426        if data.len() < 14 {
427            return Err(KernelError::InvalidArgument {
428                name: "tpm_pcr_response",
429                value: "response too short for PCR_Read",
430            });
431        }
432
433        let header = TpmResponseHeader::parse(data).ok_or(KernelError::InvalidArgument {
434            name: "tpm_pcr_response",
435            value: "invalid response header",
436        })?;
437        if !header.response_code().is_success() {
438            return Err(KernelError::HardwareError {
439                device: "TPM",
440                code: header.response_code() as u32,
441            });
442        }
443
444        let pcr_update_counter = u32::from_be_bytes([data[10], data[11], data[12], data[13]]);
445
446        // Skip pcrSelectionOut: count(4) + [ hashAlg(2) + sizeOfSelect(1) + select(N) ]
447        let mut offset = 14;
448        if data.len() < offset + 4 {
449            return Err(KernelError::InvalidArgument {
450                name: "tpm_pcr_response",
451                value: "response too short for selection count",
452            });
453        }
454        let sel_count = u32::from_be_bytes([
455            data[offset],
456            data[offset + 1],
457            data[offset + 2],
458            data[offset + 3],
459        ]) as usize;
460        offset += 4;
461
462        for _ in 0..sel_count {
463            if data.len() < offset + 3 {
464                return Err(KernelError::InvalidArgument {
465                    name: "tpm_pcr_response",
466                    value: "response too short for selection entry",
467                });
468            }
469            offset += 2; // hashAlg
470            let select_size = data[offset] as usize;
471            offset += 1 + select_size;
472        }
473
474        // Parse pcrValues (TPML_DIGEST)
475        if data.len() < offset + 4 {
476            return Err(KernelError::InvalidArgument {
477                name: "tpm_pcr_response",
478                value: "response too short for digest count",
479            });
480        }
481        let digest_count = u32::from_be_bytes([
482            data[offset],
483            data[offset + 1],
484            data[offset + 2],
485            data[offset + 3],
486        ]) as usize;
487        offset += 4;
488
489        let mut pcr_values = Vec::with_capacity(digest_count);
490        for _ in 0..digest_count {
491            if data.len() < offset + 2 {
492                return Err(KernelError::InvalidArgument {
493                    name: "tpm_pcr_response",
494                    value: "response too short for digest size",
495                });
496            }
497            let digest_size = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
498            offset += 2;
499            if data.len() < offset + digest_size {
500                return Err(KernelError::InvalidArgument {
501                    name: "tpm_pcr_response",
502                    value: "response too short for digest data",
503                });
504            }
505            pcr_values.push(data[offset..offset + digest_size].to_vec());
506            offset += digest_size;
507        }
508
509        Ok(Self {
510            pcr_update_counter,
511            pcr_values,
512        })
513    }
514}
515
516// ============================================================================
517// TPM2_PCR_Extend
518// ============================================================================
519
520/// TPM2_PCR_Extend command
521///
522/// Extends a PCR with a SHA-256 measurement digest.
523/// This command requires a session (TPM_ST_SESSIONS) because the PCR handle
524/// is an authorization handle.
525pub struct TpmPcrExtendCommand {
526    /// PCR index to extend (0-23)
527    pcr_index: u8,
528    /// SHA-256 digest to extend into the PCR
529    digest: [u8; 32],
530}
531
532impl TpmPcrExtendCommand {
533    pub fn new(pcr_index: u8, digest: &[u8; 32]) -> Self {
534        Self {
535            pcr_index,
536            digest: *digest,
537        }
538    }
539
540    /// Marshal the TPM2_PCR_Extend command.
541    ///
542    /// Wire format (TPM_ST_SESSIONS because pcrHandle is an auth handle):
543    ///   header(10) + pcrHandle(4) + authSize(4) + authSession(9) +
544    ///   digests: count(4) + [ hashAlg(2) + digest(32) ]
545    pub fn to_bytes(&self) -> Vec<u8> {
546        let mut params = Vec::new();
547
548        // pcrHandle (4 bytes) -- PCR handles are 0x00000000 + index
549        let pcr_handle: u32 = self.pcr_index as u32;
550        params.extend_from_slice(&pcr_handle.to_be_bytes());
551
552        // Authorization area (for TPM_ST_SESSIONS)
553        // Minimal password session (TPM_RS_PW):
554        //   authorizationSize(4) = 9 (the size of the session block below)
555        //   sessionHandle(4) = TPM_RS_PW = 0x40000009
556        //   nonceCaller(2) = size(2)=0
557        //   sessionAttributes(1) = 0x01 (continueSession)
558        //   hmac(2) = size(2)=0
559        let auth_session_size: u32 = 9; // 4 + 2 + 1 + 2
560        params.extend_from_slice(&auth_session_size.to_be_bytes());
561
562        // sessionHandle = TPM_RS_PW
563        params.extend_from_slice(&0x40000009u32.to_be_bytes());
564        // nonceCaller = empty (size = 0)
565        params.extend_from_slice(&0u16.to_be_bytes());
566        // sessionAttributes = continueSession
567        params.push(0x01);
568        // hmac = empty (size = 0)
569        params.extend_from_slice(&0u16.to_be_bytes());
570
571        // TPML_DIGEST_VALUES: count(4) + [ TPMT_HA: hashAlg(2) + digest ]
572        let digest_count: u32 = 1; // One digest (SHA-256)
573        params.extend_from_slice(&digest_count.to_be_bytes());
574
575        // hashAlg = SHA-256
576        params.extend_from_slice(&super::tpm_commands::hash_alg::SHA256.to_be_bytes());
577
578        // digest (32 bytes)
579        params.extend_from_slice(&self.digest);
580
581        marshal_command(
582            TpmStructureTag::Sessions,
583            TpmCommandCode::PcrExtend,
584            &params,
585        )
586    }
587}
588
589// ============================================================================
590// TPM2_GetCapability
591// ============================================================================
592
593/// TPM2_GetCapability command for querying TPM properties.
594pub struct TpmGetCapabilityCommand {
595    /// Capability group (e.g., TPM_CAP_TPM_PROPERTIES = 0x00000006)
596    capability: u32,
597    /// Property within the group
598    property: u32,
599    /// Maximum number of properties to return
600    property_count: u32,
601}
602
603impl TpmGetCapabilityCommand {
604    pub fn new(capability: u32, property: u32, property_count: u32) -> Self {
605        Self {
606            capability,
607            property,
608            property_count,
609        }
610    }
611
612    pub fn to_bytes(&self) -> Vec<u8> {
613        let mut params = Vec::new();
614        params.extend_from_slice(&self.capability.to_be_bytes());
615        params.extend_from_slice(&self.property.to_be_bytes());
616        params.extend_from_slice(&self.property_count.to_be_bytes());
617
618        marshal_command(
619            TpmStructureTag::NoSessions,
620            TpmCommandCode::GetCapability,
621            &params,
622        )
623    }
624}
625
626/// Well-known capability constants
627pub mod capability {
628    /// Query TPM properties
629    pub const TPM_CAP_TPM_PROPERTIES: u32 = 0x00000006;
630    /// Query supported algorithms
631    pub const TPM_CAP_ALGS: u32 = 0x00000000;
632    /// Query PCR properties
633    pub const TPM_CAP_PCRS: u32 = 0x00000005;
634
635    /// Property: TPM family indicator
636    pub const PT_FAMILY_INDICATOR: u32 = 0x00000100;
637    /// Property: TPM firmware version 1
638    pub const PT_FIRMWARE_VERSION_1: u32 = 0x00000111;
639    /// Property: TPM firmware version 2
640    pub const PT_FIRMWARE_VERSION_2: u32 = 0x00000112;
641    /// Property: manufacturer
642    pub const PT_MANUFACTURER: u32 = 0x00000105;
643}
644
645/// TPM Hash Algorithms (TPM_ALG_*)
646pub mod hash_alg {
647    pub const SHA1: u16 = 0x0004;
648    pub const SHA256: u16 = 0x000B;
649    pub const SHA384: u16 = 0x000C;
650    pub const SHA512: u16 = 0x000D;
651}
652
653#[cfg(test)]
654mod tests {
655    use super::*;
656
657    #[test]
658    fn test_startup_command() {
659        let cmd = TpmStartupCommand::new(TpmStartupType::Clear);
660        let bytes = cmd.to_bytes();
661
662        assert_eq!(bytes.len(), 12);
663        assert_eq!(u16::from_be_bytes([bytes[0], bytes[1]]), 0x8001); // NoSessions
664                                                                      // Size = 12
665        assert_eq!(
666            u32::from_be_bytes([bytes[2], bytes[3], bytes[4], bytes[5]]),
667            12
668        );
669        // Command = Startup (0x144)
670        assert_eq!(
671            u32::from_be_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]),
672            0x144
673        );
674        // StartupType = Clear (0x0000)
675        assert_eq!(u16::from_be_bytes([bytes[10], bytes[11]]), 0x0000);
676    }
677
678    #[test]
679    fn test_shutdown_command() {
680        let cmd = TpmShutdownCommand::new(TpmShutdownType::State);
681        let bytes = cmd.to_bytes();
682
683        assert_eq!(bytes.len(), 12);
684        // Command = Shutdown (0x145)
685        assert_eq!(
686            u32::from_be_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]),
687            0x145
688        );
689        // ShutdownType = State (0x0001)
690        assert_eq!(u16::from_be_bytes([bytes[10], bytes[11]]), 0x0001);
691    }
692
693    #[test]
694    fn test_get_random_command() {
695        let cmd = TpmGetRandomCommand::new(32);
696        let bytes = cmd.to_bytes();
697
698        assert_eq!(bytes.len(), 12);
699        assert_eq!(u16::from_be_bytes([bytes[10], bytes[11]]), 32);
700    }
701
702    #[test]
703    fn test_pcr_extend_command() {
704        let digest = [0xABu8; 32];
705        let cmd = TpmPcrExtendCommand::new(7, &digest);
706        let bytes = cmd.to_bytes();
707
708        // Tag should be Sessions (0x8002) for PCR_Extend
709        assert_eq!(u16::from_be_bytes([bytes[0], bytes[1]]), 0x8002);
710
711        // Command code = PCR_Extend (0x182)
712        assert_eq!(
713            u32::from_be_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]),
714            0x182
715        );
716
717        // PCR handle = 7
718        assert_eq!(
719            u32::from_be_bytes([bytes[10], bytes[11], bytes[12], bytes[13]]),
720            7
721        );
722    }
723
724    #[test]
725    fn test_response_header_parsing() {
726        let data = [
727            0x80, 0x01, // Tag
728            0x00, 0x00, 0x00, 0x0A, // Size = 10
729            0x00, 0x00, 0x00, 0x00, // Success
730        ];
731
732        let header = TpmResponseHeader::parse(&data).unwrap();
733        assert!(header.response_code().is_success());
734    }
735
736    #[test]
737    fn test_marshal_command_generic() {
738        let params = [0x01, 0x02, 0x03, 0x04];
739        let bytes = marshal_command(
740            TpmStructureTag::NoSessions,
741            TpmCommandCode::GetCapability,
742            &params,
743        );
744
745        assert_eq!(bytes.len(), 14); // 10 header + 4 params
746        assert_eq!(
747            u32::from_be_bytes([bytes[2], bytes[3], bytes[4], bytes[5]]),
748            14 // total size
749        );
750    }
751
752    #[test]
753    fn test_parse_response() {
754        let data = [
755            0x80, 0x01, // Tag
756            0x00, 0x00, 0x00, 0x0E, // Size = 14
757            0x00, 0x00, 0x00, 0x00, // Success
758            0xDE, 0xAD, 0xBE, 0xEF, // payload
759        ];
760
761        let (code, payload) = parse_response(&data).unwrap();
762        assert!(code.is_success());
763        assert_eq!(payload, &[0xDE, 0xAD, 0xBE, 0xEF]);
764    }
765
766    #[test]
767    fn test_get_random_response_parse() {
768        let data = [
769            0x80, 0x01, // Tag
770            0x00, 0x00, 0x00, 0x10, // Size = 16
771            0x00, 0x00, 0x00, 0x00, // Success
772            0x00, 0x04, // 4 random bytes
773            0xDE, 0xAD, 0xBE, 0xEF, // random data
774        ];
775
776        let response = TpmGetRandomResponse::parse(&data).unwrap();
777        assert_eq!(response.random_bytes, vec![0xDE, 0xAD, 0xBE, 0xEF]);
778    }
779
780    #[test]
781    fn test_self_test_command() {
782        let cmd = TpmSelfTestCommand::new(true);
783        let bytes = cmd.to_bytes();
784
785        assert_eq!(bytes.len(), 11); // 10 header + 1 byte (fullTest)
786        assert_eq!(
787            u32::from_be_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]),
788            0x143 // SelfTest
789        );
790        assert_eq!(bytes[10], 1); // fullTest = true
791    }
792}