Skip to main content

patina/performance/
record.rs

1//! Defines performance record and the performance record buffer types.
2//!
3//! ## License
4//!
5//! Copyright (c) Microsoft Corporation.
6//!
7//! SPDX-License-Identifier: Apache-2.0
8//!
9
10pub mod extended;
11pub mod hob;
12pub mod known;
13
14use crate::{BinaryGuid, performance::error::Error, performance_debug_assert};
15use alloc::vec::Vec;
16use core::{fmt, fmt::Debug, mem};
17use scroll::Pread;
18use zerocopy::{FromBytes, IntoBytes};
19use zerocopy_derive::*;
20
21/// Maximum size in byte that a performance record can have.
22pub const FPDT_MAX_PERF_RECORD_SIZE: usize = u8::MAX as usize;
23
24/// Performance record header structure.
25#[repr(C, packed)]
26#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
27pub struct PerformanceRecordHeader {
28    /// This value depicts the format and contents of the performance record.
29    pub record_type: u16,
30    /// This value depicts the length of the performance record, in bytes.
31    pub length: u8,
32    /// This value is updated if the format of the record type is extended.
33    pub revision: u8,
34}
35
36impl PerformanceRecordHeader {
37    /// Size of the header structure in bytes
38    pub const SIZE: usize = core::mem::size_of::<Self>();
39
40    /// Create a new performance record header.
41    pub const fn new(record_type: u16, length: u8, revision: u8) -> Self {
42        Self { record_type, length, revision }
43    }
44
45    /// Convert the header to little-endian format.
46    pub fn to_le(self) -> Self {
47        Self { record_type: self.record_type.to_le(), length: self.length, revision: self.revision }
48    }
49}
50
51impl TryFrom<&[u8]> for PerformanceRecordHeader {
52    type Error = &'static str;
53
54    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
55        if bytes.len() < Self::SIZE {
56            return Err("Insufficient bytes for PerformanceRecordHeader");
57        }
58
59        Self::read_from_prefix(bytes)
60            .map_err(|_| "Failed to parse PerformanceRecordHeader from bytes")
61            .map(|(header, _)| header.to_le())
62    }
63}
64
65impl From<PerformanceRecordHeader> for [u8; mem::size_of::<PerformanceRecordHeader>()] {
66    fn from(header: PerformanceRecordHeader) -> Self {
67        let le_header = header.to_le();
68        le_header.as_bytes().try_into().expect("Size mismatch in From implementation")
69    }
70}
71
72/// Size in byte of the header of a performance record.
73pub const PERFORMANCE_RECORD_HEADER_SIZE: usize = mem::size_of::<PerformanceRecordHeader>();
74
75/// Trait implemented by all performance record types that can be serialized into
76/// the Firmware Basic Boot Performance Table (FBPT) buffer.
77/// [`crate::performance::error::Error`].
78pub trait PerformanceRecord {
79    /// returns the type ID (NOT Rust's `TypeId`) value of the record
80    fn record_type(&self) -> u16;
81
82    /// Returns the revision of the record.
83    fn revision(&self) -> u8;
84
85    /// Write just the record payload (not including the common header)
86    /// into `buff` at `offset`, advancing `offset` on success.
87    fn write_data_into(&self, buff: &mut [u8], offset: &mut usize) -> Result<(), Error>;
88
89    /// Serialize the full record (header + payload) into `buff` at `offset`.
90    ///
91    /// ## Errors
92    ///
93    /// - On success returns the total size (header + payload).
94    /// - Fails with:
95    ///   - `Error::Serialization` if there is insufficient remaining space.
96    ///   - `Error::RecordTooLarge` if the final size exceeds `u8::MAX`.
97    fn write_into(&self, buff: &mut [u8], offset: &mut usize) -> Result<usize, Error> {
98        let start = *offset;
99        if start + PERFORMANCE_RECORD_HEADER_SIZE > buff.len() {
100            return Err(Error::Serialization);
101        }
102
103        // Create header with placeholder length
104        let mut header = PerformanceRecordHeader::new(self.record_type(), 0, self.revision());
105
106        // Skip header space and write data first
107        *offset += PERFORMANCE_RECORD_HEADER_SIZE;
108        self.write_data_into(buff, offset)?;
109
110        // Calculate total record size and update header
111        let record_size = *offset - start;
112        if record_size > u8::MAX as usize {
113            return Err(Error::RecordTooLarge { size: record_size });
114        }
115        header.length = record_size as u8;
116
117        // Write the complete header
118        let header_bytes: [u8; mem::size_of::<PerformanceRecordHeader>()] = header.into();
119        buff[start..start + PERFORMANCE_RECORD_HEADER_SIZE].copy_from_slice(&header_bytes);
120
121        Ok(record_size)
122    }
123}
124
125/// Performance record used to store any specific type of record.
126#[derive(Debug)]
127pub struct GenericPerformanceRecord<T: AsRef<[u8]>> {
128    /// This value depicts the format and contents of the performance record.
129    pub record_type: u16,
130    /// This value depicts the length of the performance record, in bytes.
131    pub length: u8,
132    /// This value is updated if the format of the record type is extended.
133    /// Any changes to a performance record layout must be backwards-compatible
134    /// in that all previously defined fields must be maintained if still applicable,
135    /// but newly defined fields allow the length of the performance record to be increased.
136    /// Previously defined record fields must not be redefined, but are permitted to be deprecated.
137    pub revision: u8,
138    /// The underlying data of the specific performance record.
139    pub data: T,
140}
141
142impl<T: AsRef<[u8]>> GenericPerformanceRecord<T> {
143    /// Create a new generic performance record.
144    pub fn new(record_type: u16, length: u8, revision: u8, data: T) -> Self {
145        Self { record_type, length, revision, data }
146    }
147
148    /// Get the header as a structured type.
149    pub fn header(&self) -> PerformanceRecordHeader {
150        PerformanceRecordHeader::new(self.record_type, self.length, self.revision)
151    }
152}
153
154impl<T: AsRef<[u8]>> PerformanceRecord for GenericPerformanceRecord<T> {
155    fn record_type(&self) -> u16 {
156        self.record_type
157    }
158
159    fn revision(&self) -> u8 {
160        self.revision
161    }
162
163    fn write_data_into(&self, buff: &mut [u8], offset: &mut usize) -> Result<(), Error> {
164        let remaining = buff.len().saturating_sub(*offset);
165        let data = self.data.as_ref();
166        if data.len() > remaining {
167            return Err(Error::Serialization);
168        }
169        buff[*offset..*offset + data.len()].copy_from_slice(data);
170        *offset += data.len();
171        Ok(())
172    }
173}
174
175/// Performance record buffer that can be used to collect performance records
176pub enum PerformanceRecordBuffer {
177    /// Unpublished state, where records can be added and the enum owns the buffer.
178    Unpublished(Vec<u8>),
179    /// Published state, where the buffer is leaked to it's final destination.
180    Published(&'static mut [u8], usize),
181}
182
183impl PerformanceRecordBuffer {
184    /// Create a new performance record buffer in unpublished state.
185    pub const fn new() -> Self {
186        Self::Unpublished(Vec::new())
187    }
188
189    /// Add a performance record to the buffer.
190    pub fn push_record<T: PerformanceRecord>(&mut self, record: T) -> Result<usize, Error> {
191        match self {
192            Self::Unpublished(buffer) => {
193                let mut offset = buffer.len();
194                buffer.resize(offset + FPDT_MAX_PERF_RECORD_SIZE, 0);
195                let Ok(record_size) = record.write_into(buffer, &mut offset) else {
196                    return performance_debug_assert!("Record size should not exceed FPDT_MAX_PERF_RECORD_SIZE");
197                };
198                buffer.truncate(offset);
199                Ok(record_size)
200            }
201            Self::Published(buffer, offset) => record.write_into(buffer, offset).map_err(|_| Error::OutOfResources),
202        }
203    }
204
205    /// Move the performance buffer into the memory buffer given as an argument and put itself in a publish state.
206    pub fn report(&mut self, buffer: &'static mut [u8]) -> Result<(), Error> {
207        let current_buffer = match self {
208            PerformanceRecordBuffer::Unpublished(b) => b.as_slice(),
209            PerformanceRecordBuffer::Published(_, _) => {
210                return performance_debug_assert!("PerformanceRecordBuffer already reported.");
211            }
212        };
213        let size = current_buffer.len();
214        if buffer.len() < size {
215            return Err(Error::BufferTooSmall);
216        }
217        buffer[..size].clone_from_slice(current_buffer);
218        *self = Self::Published(buffer, size);
219        Ok(())
220    }
221
222    /// Return a reference to the performance buffer in bytes.
223    pub fn buffer(&self) -> &[u8] {
224        match &self {
225            Self::Unpublished(b) => b.as_slice(),
226            Self::Published(b, len) => &b[..*len],
227        }
228    }
229
230    /// Return a performance record iterator.
231    pub fn iter(&self) -> Iter<'_> {
232        Iter::new(self.buffer())
233    }
234
235    /// Return the size in bytes of the buffer.
236    pub fn size(&self) -> usize {
237        match &self {
238            Self::Unpublished(b) => b.len(),
239            Self::Published(_, len) => *len,
240        }
241    }
242
243    /// Return the capacity in bytes of the buffer.
244    pub fn capacity(&self) -> usize {
245        match &self {
246            Self::Unpublished(b) => b.capacity(),
247            Self::Published(b, _) => b.len(),
248        }
249    }
250}
251
252impl Default for PerformanceRecordBuffer {
253    fn default() -> Self {
254        Self::new()
255    }
256}
257
258impl Debug for PerformanceRecordBuffer {
259    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
260        let size = self.size();
261        let capacity = self.capacity();
262        let nb_report = self.iter().count();
263        let records = self.iter().collect::<Vec<_>>();
264        f.debug_struct("PerformanceRecordBuffer")
265            .field("size", &size)
266            .field("capacity", &capacity)
267            .field("nb_report", &nb_report)
268            .field("records", &records)
269            .finish()
270    }
271}
272
273/// Performance record iterator.
274pub struct Iter<'a> {
275    buffer: &'a [u8],
276}
277
278impl<'a> Iter<'a> {
279    /// Iterate through performance records in a memory buffer. The buffer must contains valid records.
280    pub fn new(buffer: &'a [u8]) -> Self {
281        Self { buffer }
282    }
283}
284
285impl<'a> Iterator for Iter<'a> {
286    type Item = GenericPerformanceRecord<&'a [u8]>;
287
288    fn next(&mut self) -> Option<Self::Item> {
289        if self.buffer.is_empty() {
290            return None;
291        }
292        let mut offset = 0;
293        let record_type = self.buffer.gread::<u16>(&mut offset).unwrap();
294        let length = self.buffer.gread::<u8>(&mut offset).unwrap();
295        let revision = self.buffer.gread::<u8>(&mut offset).unwrap();
296
297        let data = &self.buffer[offset..length as usize];
298        self.buffer = &self.buffer[length as usize..];
299        Some(GenericPerformanceRecord::new(record_type, length, revision, data))
300    }
301}
302
303// ============================================================================
304// MM Performance Record Data Structures
305// ============================================================================
306
307/// GUID Event Record (Type 0x1010)
308///
309/// A performance event record which includes a GUID.
310#[repr(C, packed)]
311#[derive(Debug, Clone, Copy)]
312pub struct GuidEventRecordData {
313    /// ProgressID < 0x10 are reserved for core performance entries.
314    pub progress_id: u16,
315    /// APIC ID for the processor in the system used as a timestamp clock source.
316    pub apic_id: u32,
317    /// 64-bit value (nanosecond) describing elapsed time since the most recent deassertion of processor reset.
318    pub timestamp: u64,
319    /// If ProgressID < 0x10, GUID of the referenced module; otherwise, GUID of the module logging the event.
320    pub guid: [u8; 16],
321}
322
323impl GuidEventRecordData {
324    /// Name of the record type
325    pub const NAME: &'static str = "GUID Event";
326}
327
328impl fmt::Display for GuidEventRecordData {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        // Note: Copying packed fields to local variables to avoid unaligned references
331        let progress_id = self.progress_id;
332        let apic_id = self.apic_id;
333        let timestamp = self.timestamp;
334        let guid = BinaryGuid::from_bytes(&self.guid);
335        write!(f, "progress_id={}, apic_id={}, timestamp={}, guid={}", progress_id, apic_id, timestamp, guid)
336    }
337}
338
339/// Dynamic String Event Record (Type 0x1011)
340///
341/// A performance event record which includes an ASCII string.
342/// Note: The string is variable-length and follows this fixed header.
343#[repr(C, packed)]
344#[derive(Debug, Clone, Copy)]
345pub struct DynamicStringEventRecordData {
346    /// ProgressID < 0x10 are reserved for core performance entries.
347    pub progress_id: u16,
348    /// APIC ID for the processor in the system used as a timestamp clock source.
349    pub apic_id: u32,
350    /// 64-bit value (nanosecond) describing elapsed time since the most recent deassertion of processor reset.
351    pub timestamp: u64,
352    /// If ProgressID < 0x10, GUID of the referenced module; otherwise, GUID of the module logging the event.
353    pub guid: [u8; 16],
354    // String data follows but is not a part of this fixed structure
355}
356
357impl DynamicStringEventRecordData {
358    /// Name of the record type
359    pub const NAME: &'static str = "Dynamic String Event";
360
361    /// Get the string portion from the full record data
362    pub fn extract_string(full_data: &[u8]) -> &str {
363        if full_data.len() <= core::mem::size_of::<Self>() {
364            return "";
365        }
366        let string_bytes = &full_data[core::mem::size_of::<Self>()..];
367        // Find the null terminator
368        let string_len = string_bytes.iter().position(|&b| b == 0).unwrap_or(string_bytes.len());
369        core::str::from_utf8(&string_bytes[..string_len]).unwrap_or("<invalid UTF-8>")
370    }
371}
372
373impl fmt::Display for DynamicStringEventRecordData {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        // Note: Copying packed fields to local variables to avoid unaligned references
376        let progress_id = self.progress_id;
377        let apic_id = self.apic_id;
378        let timestamp = self.timestamp;
379        let guid = BinaryGuid::from_bytes(&self.guid);
380        write!(f, "progress_id: 0x{:04X}, apic_id: {}, timestamp: {}, guid: {}", progress_id, apic_id, timestamp, guid)
381    }
382}
383
384/// Dual GUID String Event Record (Type 0x1012)
385///
386/// A performance event record which includes two GUIDs and an ASCII string.
387#[repr(C, packed)]
388#[derive(Debug, Clone, Copy)]
389pub struct DualGuidStringEventRecordData {
390    /// ProgressID < 0x10 are reserved for core performance entries.
391    pub progress_id: u16,
392    /// APIC ID for the processor in the system used as a timestamp clock source.
393    pub apic_id: u32,
394    /// 64-bit value (nanosecond) describing elapsed time since the most recent deassertion of processor reset.
395    pub timestamp: u64,
396    /// GUID of the module logging the event.
397    pub guid_1: [u8; 16],
398    /// Event or PPI or Protocol GUID for Callback.
399    pub guid_2: [u8; 16],
400    // String data follows but is not part of this fixed structure
401}
402
403impl DualGuidStringEventRecordData {
404    /// Name of the record type
405    pub const NAME: &'static str = "Dual GUID String Event";
406
407    /// Get the string portion from the full record data
408    pub fn extract_string(full_data: &[u8]) -> &str {
409        if full_data.len() <= core::mem::size_of::<Self>() {
410            return "";
411        }
412        let string_bytes = &full_data[core::mem::size_of::<Self>()..];
413        // Find the null terminator
414        let string_len = string_bytes.iter().position(|&b| b == 0).unwrap_or(string_bytes.len());
415        core::str::from_utf8(&string_bytes[..string_len]).unwrap_or("<invalid UTF-8>")
416    }
417}
418
419impl fmt::Display for DualGuidStringEventRecordData {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        // Note: Copying packed fields to local variables to avoid unaligned references
422        let progress_id = self.progress_id;
423        let apic_id = self.apic_id;
424        let timestamp = self.timestamp;
425        let guid_1 = BinaryGuid::from_bytes(&self.guid_1);
426        let guid_2 = BinaryGuid::from_bytes(&self.guid_2);
427        write!(
428            f,
429            "progress_id: 0x{:04X}, apic_id: {}, timestamp: {}, guid_1: {}, guid_2: {}",
430            progress_id, apic_id, timestamp, guid_1, guid_2
431        )
432    }
433}
434
435/// GUID QWORD Event Record (Type 0x1013)
436///
437/// A performance event record which includes a GUID and a QWORD value.
438#[repr(C, packed)]
439#[derive(Debug, Clone, Copy)]
440pub struct GuidQwordEventRecordData {
441    /// ProgressID < 0x10 are reserved for core performance entries.
442    pub progress_id: u16,
443    /// APIC ID for the processor in the system used as a timestamp clock source.
444    pub apic_id: u32,
445    /// 64-bit value (nanosecond) describing elapsed time since the most recent deassertion of processor reset.
446    pub timestamp: u64,
447    /// If ProgressID < 0x10, GUID of the referenced module; otherwise, GUID of the module logging the event.
448    pub guid: [u8; 16],
449    /// Event-specific QWORD value.
450    pub qword: u64,
451}
452
453impl GuidQwordEventRecordData {
454    /// Name of the record type
455    pub const NAME: &'static str = "GUID QWORD Event";
456}
457
458impl fmt::Display for GuidQwordEventRecordData {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460        // Note: Copying packed fields to local variables to avoid unaligned references
461        let progress_id = self.progress_id;
462        let apic_id = self.apic_id;
463        let timestamp = self.timestamp;
464        let guid = BinaryGuid::from_bytes(&self.guid);
465        let qword = self.qword;
466        write!(
467            f,
468            "progress_id: 0x{:04X}, apic_id: {}, timestamp: {}, guid: {}, qword: 0x{:016X}",
469            progress_id, apic_id, timestamp, guid, qword
470        )
471    }
472}
473
474/// GUID QWORD String Event Record (Type 0x1014)
475///
476/// A performance event record which includes a GUID, a QWORD value, and an ASCII string.
477#[repr(C, packed)]
478#[derive(Debug, Clone, Copy)]
479pub struct GuidQwordStringEventRecordData {
480    /// ProgressID < 0x10 are reserved for core performance entries.
481    pub progress_id: u16,
482    /// APIC ID for the processor in the system used as a timestamp clock source.
483    pub apic_id: u32,
484    /// 64-bit value (nanosecond) describing elapsed time since the most recent deassertion of processor reset.
485    pub timestamp: u64,
486    /// If ProgressID < 0x10, GUID of the referenced module; otherwise, GUID of the module logging the event.
487    pub guid: [u8; 16],
488    /// Event-specific QWORD value.
489    pub qword: u64,
490    // String data follows but is not part of this fixed structure
491}
492
493impl GuidQwordStringEventRecordData {
494    /// Name of the record type
495    pub const NAME: &'static str = "GUID QWORD String Event";
496
497    /// Get the string portion from the full record data
498    pub fn extract_string(full_data: &[u8]) -> &str {
499        if full_data.len() <= core::mem::size_of::<Self>() {
500            return "";
501        }
502        let string_bytes = &full_data[core::mem::size_of::<Self>()..];
503        // Find the null terminator
504        let string_len = string_bytes.iter().position(|&b| b == 0).unwrap_or(string_bytes.len());
505        core::str::from_utf8(&string_bytes[..string_len]).unwrap_or("<invalid UTF-8>")
506    }
507}
508
509impl fmt::Display for GuidQwordStringEventRecordData {
510    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511        // Note: Copying packed fields to local variables to avoid unaligned references
512        let progress_id = self.progress_id;
513        let apic_id = self.apic_id;
514        let timestamp = self.timestamp;
515        let guid = BinaryGuid::from_bytes(&self.guid);
516        let qword = self.qword;
517        write!(
518            f,
519            "progress_id: 0x{:04X}, apic_id: {}, timestamp: {}, guid: {}, qword: 0x{:016X}",
520            progress_id, apic_id, timestamp, guid, qword
521        )
522    }
523}
524
525/// Trait for types that can print detailed record information
526pub trait PerformanceRecordDetails {
527    /// Print detailed information about the record
528    fn print_details(&self, record_number: usize);
529}
530
531impl PerformanceRecordDetails for GuidEventRecordData {
532    fn print_details(&self, record_number: usize) {
533        log::debug!("  Record #{}: {}", record_number, self);
534    }
535}
536
537impl PerformanceRecordDetails for DynamicStringEventRecordData {
538    fn print_details(&self, record_number: usize) {
539        log::debug!("  Record #{}: {}", record_number, self);
540    }
541}
542
543impl PerformanceRecordDetails for DualGuidStringEventRecordData {
544    fn print_details(&self, record_number: usize) {
545        log::debug!("  Record #{}: {}", record_number, self);
546    }
547}
548
549impl PerformanceRecordDetails for GuidQwordEventRecordData {
550    fn print_details(&self, record_number: usize) {
551        log::debug!("  Record #{}: {}", record_number, self);
552    }
553}
554
555impl PerformanceRecordDetails for GuidQwordStringEventRecordData {
556    fn print_details(&self, record_number: usize) {
557        log::debug!("  Record #{}: {}", record_number, self);
558    }
559}
560
561/// Print detailed information about a performance record based on its type
562pub fn print_record_details(record_type: u16, record_number: usize, data: &[u8]) {
563    match record_type {
564        0x1010 => {
565            if data.len() >= core::mem::size_of::<GuidEventRecordData>() {
566                // SAFETY: We've verified the data is large enough and the struct is packed
567                let record = unsafe { &*(data.as_ptr() as *const GuidEventRecordData) };
568                record.print_details(record_number);
569            }
570        }
571        0x1011 => {
572            if data.len() >= core::mem::size_of::<DynamicStringEventRecordData>() {
573                // SAFETY: We've verified the data is large enough and the struct is packed
574                let record = unsafe { &*(data.as_ptr() as *const DynamicStringEventRecordData) };
575                record.print_details(record_number);
576                let string_data = DynamicStringEventRecordData::extract_string(data);
577                if !string_data.is_empty() {
578                    log::debug!("    String: \"{}\"", string_data);
579                }
580            }
581        }
582        0x1012 => {
583            if data.len() >= core::mem::size_of::<DualGuidStringEventRecordData>() {
584                // SAFETY: We've verified the data is large enough and the struct is packed
585                let record = unsafe { &*(data.as_ptr() as *const DualGuidStringEventRecordData) };
586                record.print_details(record_number);
587                let string_data = DualGuidStringEventRecordData::extract_string(data);
588                if !string_data.is_empty() {
589                    log::debug!("    String: \"{}\"", string_data);
590                }
591            }
592        }
593        0x1013 => {
594            if data.len() >= core::mem::size_of::<GuidQwordEventRecordData>() {
595                // SAFETY: We've verified the data is large enough and the struct is packed
596                let record = unsafe { &*(data.as_ptr() as *const GuidQwordEventRecordData) };
597                record.print_details(record_number);
598            }
599        }
600        0x1014 => {
601            if data.len() >= core::mem::size_of::<GuidQwordStringEventRecordData>() {
602                // SAFETY: We've verified the data is large enough and the struct is packed
603                let record = unsafe { &*(data.as_ptr() as *const GuidQwordStringEventRecordData) };
604                record.print_details(record_number);
605                let string_data = GuidQwordStringEventRecordData::extract_string(data);
606                if !string_data.is_empty() {
607                    log::debug!("    String: \"{}\"", string_data);
608                }
609            }
610        }
611        _ => {
612            log::debug!("  Record #{}: Unknown type 0x{:04X}", record_number, record_type);
613        }
614    }
615}
616
617/// Get a human-readable name for a record type
618pub fn record_type_name(record_type: u16) -> &'static str {
619    match record_type {
620        0x1010 => GuidEventRecordData::NAME,
621        0x1011 => DynamicStringEventRecordData::NAME,
622        0x1012 => DualGuidStringEventRecordData::NAME,
623        0x1013 => GuidQwordEventRecordData::NAME,
624        0x1014 => GuidQwordStringEventRecordData::NAME,
625        _ => "Unknown",
626    }
627}
628
629#[cfg(test)]
630#[coverage(off)]
631mod tests {
632    use super::*;
633    use core::{assert_eq, slice, unreachable};
634
635    use r_efi::efi;
636
637    use extended::{
638        DualGuidStringEventRecord, DynamicStringEventRecord, GuidEventRecord, GuidQwordEventRecord,
639        GuidQwordStringEventRecord,
640    };
641
642    #[test]
643    fn test_performance_record_buffer_new() {
644        let performance_record_buffer = PerformanceRecordBuffer::new();
645        println!("{performance_record_buffer:?}");
646        assert_eq!(0, performance_record_buffer.size());
647    }
648
649    #[test]
650    fn test_performance_record_buffer_push_record() {
651        let guid = efi::Guid::from_bytes(&[0; 16]);
652        let mut performance_record_buffer = PerformanceRecordBuffer::new();
653        let mut size = 0;
654
655        size += performance_record_buffer.push_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
656        assert_eq!(size, performance_record_buffer.size());
657
658        size += performance_record_buffer.push_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
659        assert_eq!(size, performance_record_buffer.size());
660
661        size += performance_record_buffer
662            .push_record(DualGuidStringEventRecord::new(1, 0, 10, guid, guid, "test"))
663            .unwrap();
664        assert_eq!(size, performance_record_buffer.size());
665
666        size += performance_record_buffer.push_record(GuidQwordEventRecord::new(1, 0, 10, guid, 64)).unwrap();
667        assert_eq!(size, performance_record_buffer.size());
668
669        size +=
670            performance_record_buffer.push_record(GuidQwordStringEventRecord::new(1, 0, 10, guid, 64, "test")).unwrap();
671        assert_eq!(size, performance_record_buffer.size());
672    }
673
674    #[test]
675    fn test_performance_record_buffer_iter() {
676        let guid = efi::Guid::from_bytes(&[0; 16]);
677        let mut performance_record_buffer = PerformanceRecordBuffer::new();
678
679        performance_record_buffer.push_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
680        performance_record_buffer.push_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
681        performance_record_buffer.push_record(DualGuidStringEventRecord::new(1, 0, 10, guid, guid, "test")).unwrap();
682        performance_record_buffer.push_record(GuidQwordEventRecord::new(1, 0, 10, guid, 64)).unwrap();
683        performance_record_buffer.push_record(GuidQwordStringEventRecord::new(1, 0, 10, guid, 64, "test")).unwrap();
684
685        for (i, record) in performance_record_buffer.iter().enumerate() {
686            match i {
687                _ if i == 0 => assert_eq!(
688                    (GuidEventRecord::TYPE, GuidEventRecord::REVISION),
689                    (record.record_type, record.revision)
690                ),
691                _ if i == 1 => assert_eq!(
692                    (DynamicStringEventRecord::TYPE, DynamicStringEventRecord::REVISION),
693                    (record.record_type, record.revision)
694                ),
695                _ if i == 2 => assert_eq!(
696                    (DualGuidStringEventRecord::TYPE, DualGuidStringEventRecord::REVISION),
697                    (record.record_type, record.revision)
698                ),
699                _ if i == 3 => assert_eq!(
700                    (GuidQwordEventRecord::TYPE, GuidQwordEventRecord::REVISION),
701                    (record.record_type, record.revision)
702                ),
703                _ if i == 4 => assert_eq!(
704                    (GuidQwordStringEventRecord::TYPE, GuidQwordStringEventRecord::REVISION),
705                    (record.record_type, record.revision)
706                ),
707                _ => unreachable!(),
708            }
709        }
710    }
711
712    #[test]
713    fn test_performance_record_buffer_reported_table() {
714        let guid = efi::Guid::from_bytes(&[0; 16]);
715        let mut performance_record_buffer = PerformanceRecordBuffer::new();
716
717        performance_record_buffer.push_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
718        performance_record_buffer.push_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
719
720        let mut buffer = vec![0_u8; 1000];
721        // SAFETY: Test code - creating a mutable slice from vector for testing record reporting.
722        let buffer = unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr(), buffer.len()) };
723
724        performance_record_buffer.report(buffer).unwrap();
725
726        performance_record_buffer.push_record(DualGuidStringEventRecord::new(1, 0, 10, guid, guid, "test")).unwrap();
727        performance_record_buffer.push_record(GuidQwordEventRecord::new(1, 0, 10, guid, 64)).unwrap();
728        performance_record_buffer.push_record(GuidQwordStringEventRecord::new(1, 0, 10, guid, 64, "test")).unwrap();
729
730        for (i, record) in performance_record_buffer.iter().enumerate() {
731            match i {
732                _ if i == 0 => assert_eq!(
733                    (GuidEventRecord::TYPE, GuidEventRecord::REVISION),
734                    (record.record_type, record.revision)
735                ),
736                _ if i == 1 => assert_eq!(
737                    (DynamicStringEventRecord::TYPE, DynamicStringEventRecord::REVISION),
738                    (record.record_type, record.revision)
739                ),
740                _ if i == 2 => assert_eq!(
741                    (DualGuidStringEventRecord::TYPE, DualGuidStringEventRecord::REVISION),
742                    (record.record_type, record.revision)
743                ),
744                _ if i == 3 => assert_eq!(
745                    (GuidQwordEventRecord::TYPE, GuidQwordEventRecord::REVISION),
746                    (record.record_type, record.revision)
747                ),
748                _ if i == 4 => assert_eq!(
749                    (GuidQwordStringEventRecord::TYPE, GuidQwordStringEventRecord::REVISION),
750                    (record.record_type, record.revision)
751                ),
752                _ => unreachable!(),
753            }
754        }
755    }
756
757    #[test]
758    fn test_performance_record_header_try_from_valid_bytes() {
759        let original_header = PerformanceRecordHeader::new(0x1234, 42, 1);
760        let bytes: [u8; 4] = original_header.into();
761
762        let parsed_header = PerformanceRecordHeader::try_from(bytes.as_slice()).unwrap();
763
764        // Copy values locally since `PerformanceRecordHeader` is packed
765        let parsed_type = parsed_header.record_type;
766        let parsed_length = parsed_header.length;
767        let parsed_revision = parsed_header.revision;
768
769        assert_eq!(parsed_type, 0x1234);
770        assert_eq!(parsed_length, 42);
771        assert_eq!(parsed_revision, 1);
772    }
773
774    #[test]
775    fn test_performance_record_header_try_from_insufficient_bytes() {
776        let bytes = [0x34, 0x12]; // Only 2 bytes instead of 4
777        let result = PerformanceRecordHeader::try_from(bytes.as_slice());
778
779        assert!(result.is_err());
780        assert_eq!(result.unwrap_err(), "Insufficient bytes for PerformanceRecordHeader");
781    }
782
783    #[test]
784    fn test_performance_record_header_try_from_empty_bytes() {
785        let bytes: &[u8] = &[];
786        let result = PerformanceRecordHeader::try_from(bytes);
787
788        assert!(result.is_err());
789        assert_eq!(result.unwrap_err(), "Insufficient bytes for PerformanceRecordHeader");
790    }
791
792    #[test]
793    fn test_performance_record_header_from_trait_conversion_le() {
794        let header = PerformanceRecordHeader::new(0xABCD, 100, 2);
795        let bytes: [u8; mem::size_of::<PerformanceRecordHeader>()] = header.into();
796
797        // Check little-endian order
798        assert_eq!(bytes[0], 0xCD); // Low byte of 0xABCD
799        assert_eq!(bytes[1], 0xAB); // High byte of 0xABCD
800        assert_eq!(bytes[2], 100); // Length
801        assert_eq!(bytes[3], 2); // Revision
802    }
803
804    #[test]
805    fn test_performance_record_header_roundtrip_conversion() {
806        // Test that we can convert header -> bytes -> header and get the same result
807        let original_header = PerformanceRecordHeader::new(0x5678, 200, 3);
808
809        let bytes: [u8; mem::size_of::<PerformanceRecordHeader>()] = original_header.into();
810        let parsed_header = PerformanceRecordHeader::try_from(bytes.as_slice()).unwrap();
811
812        let orig_type = original_header.record_type;
813        let orig_length = original_header.length;
814        let orig_revision = original_header.revision;
815        let parsed_type = parsed_header.record_type;
816        let parsed_length = parsed_header.length;
817        let parsed_revision = parsed_header.revision;
818
819        assert_eq!(orig_type, parsed_type);
820        assert_eq!(orig_length, parsed_length);
821        assert_eq!(orig_revision, parsed_revision);
822    }
823
824    #[test]
825    fn test_performance_record_header_le_handling() {
826        // Test that little-endian conversion works correctly for multi-byte fields
827        let header = PerformanceRecordHeader::new(0x0102, 50, 1);
828        let bytes: [u8; mem::size_of::<PerformanceRecordHeader>()] = header.into();
829
830        assert_eq!(bytes[0], 0x02);
831        assert_eq!(bytes[1], 0x01);
832        assert_eq!(bytes[2], 50);
833        assert_eq!(bytes[3], 1);
834
835        // Parse it back
836        let parsed = PerformanceRecordHeader::try_from(bytes.as_slice()).unwrap();
837
838        let parsed_type = parsed.record_type;
839        let parsed_length = parsed.length;
840        let parsed_revision = parsed.revision;
841
842        assert_eq!(parsed_type, 0x0102);
843        assert_eq!(parsed_length, 50);
844        assert_eq!(parsed_revision, 1);
845    }
846
847    #[test]
848    fn test_performance_record_header_try_from_extra_bytes() {
849        // Test with more bytes than needed (should still work)
850        let mut bytes = vec![0x34, 0x12, 42, 1]; // Valid header
851        bytes.extend_from_slice(&[0xFF, 0xFF, 0xFF]); // Extra bytes
852
853        let parsed_header = PerformanceRecordHeader::try_from(bytes.as_slice()).unwrap();
854
855        let parsed_type = parsed_header.record_type;
856        let parsed_length = parsed_header.length;
857        let parsed_revision = parsed_header.revision;
858
859        assert_eq!(parsed_type, 0x1234);
860        assert_eq!(parsed_length, 42);
861        assert_eq!(parsed_revision, 1);
862    }
863
864    #[test]
865    fn test_guid_event_record_data_display() {
866        let record = GuidEventRecordData {
867            progress_id: 0x1234,
868            apic_id: 42,
869            timestamp: 1000000,
870            guid: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10],
871        };
872
873        let display_str = format!("{}", record);
874        assert!(display_str.contains("progress_id=4660"));
875        assert!(display_str.contains("apic_id=42"));
876        assert!(display_str.contains("timestamp=1000000"));
877    }
878
879    #[test]
880    fn test_dynamic_string_event_record_data_display() {
881        let record = DynamicStringEventRecordData {
882            progress_id: 0x5678,
883            apic_id: 99,
884            timestamp: 2000000,
885            guid: [0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20],
886        };
887
888        let display_str = format!("{}", record);
889        assert!(display_str.contains("progress_id: 0x5678"));
890        assert!(display_str.contains("apic_id: 99"));
891        assert!(display_str.contains("timestamp: 2000000"));
892    }
893
894    #[test]
895    fn test_dynamic_string_event_record_data_extract_string() {
896        let mut data = vec![0u8; core::mem::size_of::<DynamicStringEventRecordData>()];
897        let test_string = b"Test String\0";
898        data.extend_from_slice(test_string);
899
900        let extracted = DynamicStringEventRecordData::extract_string(&data);
901        assert_eq!(extracted, "Test String");
902    }
903
904    #[test]
905    fn test_dynamic_string_event_record_data_extract_string_empty() {
906        let data = vec![0u8; core::mem::size_of::<DynamicStringEventRecordData>()];
907        let extracted = DynamicStringEventRecordData::extract_string(&data);
908        assert_eq!(extracted, "");
909    }
910
911    #[test]
912    fn test_dynamic_string_event_record_data_extract_string_invalid_utf8() {
913        let mut data = vec![0u8; core::mem::size_of::<DynamicStringEventRecordData>()];
914        data.extend_from_slice(&[0xFF, 0xFE, 0xFD, 0x00]); // Invalid UTF-8
915
916        let extracted = DynamicStringEventRecordData::extract_string(&data);
917        assert_eq!(extracted, "<invalid UTF-8>");
918    }
919
920    #[test]
921    fn test_dynamic_string_event_record_data_extract_string_no_null_terminator() {
922        let mut data = vec![0u8; core::mem::size_of::<DynamicStringEventRecordData>()];
923        data.extend_from_slice(b"NoNull");
924
925        let extracted = DynamicStringEventRecordData::extract_string(&data);
926        assert_eq!(extracted, "NoNull");
927    }
928
929    #[test]
930    fn test_dual_guid_string_event_record_data_display() {
931        let record = DualGuidStringEventRecordData {
932            progress_id: 0xABCD,
933            apic_id: 123,
934            timestamp: 3000000,
935            guid_1: [0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30],
936            guid_2: [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40],
937        };
938
939        let display_str = format!("{}", record);
940        assert!(display_str.contains("progress_id: 0xABCD"));
941        assert!(display_str.contains("apic_id: 123"));
942        assert!(display_str.contains("timestamp: 3000000"));
943        assert!(display_str.contains("guid_1:"));
944        assert!(display_str.contains("guid_2:"));
945    }
946
947    #[test]
948    fn test_dual_guid_string_event_record_data_extract_string() {
949        let mut data = vec![0u8; core::mem::size_of::<DualGuidStringEventRecordData>()];
950        let test_string = b"DualGuidTest\0";
951        data.extend_from_slice(test_string);
952
953        let extracted = DualGuidStringEventRecordData::extract_string(&data);
954        assert_eq!(extracted, "DualGuidTest");
955    }
956
957    #[test]
958    fn test_dual_guid_string_event_record_data_extract_string_empty() {
959        let data = vec![0u8; core::mem::size_of::<DualGuidStringEventRecordData>()];
960        let extracted = DualGuidStringEventRecordData::extract_string(&data);
961        assert_eq!(extracted, "");
962    }
963
964    #[test]
965    fn test_guid_qword_event_record_data_display() {
966        let record = GuidQwordEventRecordData {
967            progress_id: 0xEF01,
968            apic_id: 200,
969            timestamp: 4000000,
970            guid: [0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50],
971            qword: 0x123456789ABCDEF0,
972        };
973
974        let display_str = format!("{}", record);
975        assert!(display_str.contains("progress_id: 0xEF01"));
976        assert!(display_str.contains("apic_id: 200"));
977        assert!(display_str.contains("timestamp: 4000000"));
978        assert!(display_str.contains("qword: 0x123456789ABCDEF0"));
979    }
980
981    #[test]
982    fn test_guid_qword_string_event_record_data_display() {
983        let record = GuidQwordStringEventRecordData {
984            progress_id: 0x2345,
985            apic_id: 77,
986            timestamp: 5000000,
987            guid: [0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60],
988            qword: 0xFEDCBA9876543210,
989        };
990
991        let display_str = format!("{}", record);
992        assert!(display_str.contains("progress_id: 0x2345"));
993        assert!(display_str.contains("apic_id: 77"));
994        assert!(display_str.contains("timestamp: 5000000"));
995        assert!(display_str.contains("qword: 0xFEDCBA9876543210"));
996    }
997
998    #[test]
999    fn test_guid_qword_string_event_record_data_extract_string() {
1000        let mut data = vec![0u8; core::mem::size_of::<GuidQwordStringEventRecordData>()];
1001        let test_string = b"QwordString\0";
1002        data.extend_from_slice(test_string);
1003
1004        let extracted = GuidQwordStringEventRecordData::extract_string(&data);
1005        assert_eq!(extracted, "QwordString");
1006    }
1007
1008    #[test]
1009    fn test_guid_qword_string_event_record_data_extract_string_empty() {
1010        let data = vec![0u8; core::mem::size_of::<GuidQwordStringEventRecordData>()];
1011        let extracted = GuidQwordStringEventRecordData::extract_string(&data);
1012        assert_eq!(extracted, "");
1013    }
1014
1015    #[test]
1016    fn test_record_type_name_all_types() {
1017        assert_eq!(record_type_name(0x1010), GuidEventRecordData::NAME);
1018        assert_eq!(record_type_name(0x1011), DynamicStringEventRecordData::NAME);
1019        assert_eq!(record_type_name(0x1012), DualGuidStringEventRecordData::NAME);
1020        assert_eq!(record_type_name(0x1013), GuidQwordEventRecordData::NAME);
1021        assert_eq!(record_type_name(0x1014), GuidQwordStringEventRecordData::NAME);
1022        assert_eq!(record_type_name(0x9999), "Unknown");
1023    }
1024
1025    #[test]
1026    fn test_record_type_name_boundary_values() {
1027        assert_eq!(record_type_name(0x0000), "Unknown");
1028        assert_eq!(record_type_name(0xFFFF), "Unknown");
1029        assert_eq!(record_type_name(0x100F), "Unknown");
1030        assert_eq!(record_type_name(0x1015), "Unknown");
1031    }
1032
1033    #[test]
1034    fn test_guid_event_record_data_name_constant() {
1035        assert_eq!(GuidEventRecordData::NAME, "GUID Event");
1036    }
1037
1038    #[test]
1039    fn test_dynamic_string_event_record_data_name_constant() {
1040        assert_eq!(DynamicStringEventRecordData::NAME, "Dynamic String Event");
1041    }
1042
1043    #[test]
1044    fn test_dual_guid_string_event_record_data_name_constant() {
1045        assert_eq!(DualGuidStringEventRecordData::NAME, "Dual GUID String Event");
1046    }
1047
1048    #[test]
1049    fn test_guid_qword_event_record_data_name_constant() {
1050        assert_eq!(GuidQwordEventRecordData::NAME, "GUID QWORD Event");
1051    }
1052
1053    #[test]
1054    fn test_guid_qword_string_event_record_data_name_constant() {
1055        assert_eq!(GuidQwordStringEventRecordData::NAME, "GUID QWORD String Event");
1056    }
1057}