pub mod extended;
pub mod hob;
pub mod known;
use crate::{BinaryGuid, performance::error::Error, performance_debug_assert};
use alloc::vec::Vec;
use core::{fmt, fmt::Debug, mem};
use scroll::Pread;
use zerocopy::{FromBytes, IntoBytes};
use zerocopy_derive::*;
pub const FPDT_MAX_PERF_RECORD_SIZE: usize = u8::MAX as usize;
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
pub struct PerformanceRecordHeader {
pub record_type: u16,
pub length: u8,
pub revision: u8,
}
impl PerformanceRecordHeader {
pub const SIZE: usize = core::mem::size_of::<Self>();
pub const fn new(record_type: u16, length: u8, revision: u8) -> Self {
Self { record_type, length, revision }
}
pub fn to_le(self) -> Self {
Self { record_type: self.record_type.to_le(), length: self.length, revision: self.revision }
}
}
impl TryFrom<&[u8]> for PerformanceRecordHeader {
type Error = &'static str;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() < Self::SIZE {
return Err("Insufficient bytes for PerformanceRecordHeader");
}
Self::read_from_prefix(bytes)
.map_err(|_| "Failed to parse PerformanceRecordHeader from bytes")
.map(|(header, _)| header.to_le())
}
}
impl From<PerformanceRecordHeader> for [u8; mem::size_of::<PerformanceRecordHeader>()] {
fn from(header: PerformanceRecordHeader) -> Self {
let le_header = header.to_le();
le_header.as_bytes().try_into().expect("Size mismatch in From implementation")
}
}
pub const PERFORMANCE_RECORD_HEADER_SIZE: usize = mem::size_of::<PerformanceRecordHeader>();
pub trait PerformanceRecord {
fn record_type(&self) -> u16;
fn revision(&self) -> u8;
fn write_data_into(&self, buff: &mut [u8], offset: &mut usize) -> Result<(), Error>;
fn write_into(&self, buff: &mut [u8], offset: &mut usize) -> Result<usize, Error> {
let start = *offset;
if start + PERFORMANCE_RECORD_HEADER_SIZE > buff.len() {
return Err(Error::Serialization);
}
let mut header = PerformanceRecordHeader::new(self.record_type(), 0, self.revision());
*offset += PERFORMANCE_RECORD_HEADER_SIZE;
self.write_data_into(buff, offset)?;
let record_size = *offset - start;
if record_size > u8::MAX as usize {
return Err(Error::RecordTooLarge { size: record_size });
}
header.length = record_size as u8;
let header_bytes: [u8; mem::size_of::<PerformanceRecordHeader>()] = header.into();
buff[start..start + PERFORMANCE_RECORD_HEADER_SIZE].copy_from_slice(&header_bytes);
Ok(record_size)
}
}
#[derive(Debug)]
pub struct GenericPerformanceRecord<T: AsRef<[u8]>> {
pub record_type: u16,
pub length: u8,
pub revision: u8,
pub data: T,
}
impl<T: AsRef<[u8]>> GenericPerformanceRecord<T> {
pub fn new(record_type: u16, length: u8, revision: u8, data: T) -> Self {
Self { record_type, length, revision, data }
}
pub fn header(&self) -> PerformanceRecordHeader {
PerformanceRecordHeader::new(self.record_type, self.length, self.revision)
}
}
impl<T: AsRef<[u8]>> PerformanceRecord for GenericPerformanceRecord<T> {
fn record_type(&self) -> u16 {
self.record_type
}
fn revision(&self) -> u8 {
self.revision
}
fn write_data_into(&self, buff: &mut [u8], offset: &mut usize) -> Result<(), Error> {
let remaining = buff.len().saturating_sub(*offset);
let data = self.data.as_ref();
if data.len() > remaining {
return Err(Error::Serialization);
}
buff[*offset..*offset + data.len()].copy_from_slice(data);
*offset += data.len();
Ok(())
}
}
pub enum PerformanceRecordBuffer {
Unpublished(Vec<u8>),
Published(&'static mut [u8], usize),
}
impl PerformanceRecordBuffer {
pub const fn new() -> Self {
Self::Unpublished(Vec::new())
}
pub fn push_record<T: PerformanceRecord>(&mut self, record: T) -> Result<usize, Error> {
match self {
Self::Unpublished(buffer) => {
let mut offset = buffer.len();
buffer.resize(offset + FPDT_MAX_PERF_RECORD_SIZE, 0);
let Ok(record_size) = record.write_into(buffer, &mut offset) else {
return performance_debug_assert!("Record size should not exceed FPDT_MAX_PERF_RECORD_SIZE");
};
buffer.truncate(offset);
Ok(record_size)
}
Self::Published(buffer, offset) => record.write_into(buffer, offset).map_err(|_| Error::OutOfResources),
}
}
pub fn report(&mut self, buffer: &'static mut [u8]) -> Result<(), Error> {
let current_buffer = match self {
PerformanceRecordBuffer::Unpublished(b) => b.as_slice(),
PerformanceRecordBuffer::Published(_, _) => {
return performance_debug_assert!("PerformanceRecordBuffer already reported.");
}
};
let size = current_buffer.len();
if buffer.len() < size {
return Err(Error::BufferTooSmall);
}
buffer[..size].clone_from_slice(current_buffer);
*self = Self::Published(buffer, size);
Ok(())
}
pub fn buffer(&self) -> &[u8] {
match &self {
Self::Unpublished(b) => b.as_slice(),
Self::Published(b, len) => &b[..*len],
}
}
pub fn iter(&self) -> Iter<'_> {
Iter::new(self.buffer())
}
pub fn size(&self) -> usize {
match &self {
Self::Unpublished(b) => b.len(),
Self::Published(_, len) => *len,
}
}
pub fn capacity(&self) -> usize {
match &self {
Self::Unpublished(b) => b.capacity(),
Self::Published(b, _) => b.len(),
}
}
}
impl Default for PerformanceRecordBuffer {
fn default() -> Self {
Self::new()
}
}
impl Debug for PerformanceRecordBuffer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let size = self.size();
let capacity = self.capacity();
let nb_report = self.iter().count();
let records = self.iter().collect::<Vec<_>>();
f.debug_struct("PerformanceRecordBuffer")
.field("size", &size)
.field("capacity", &capacity)
.field("nb_report", &nb_report)
.field("records", &records)
.finish()
}
}
pub struct Iter<'a> {
buffer: &'a [u8],
}
impl<'a> Iter<'a> {
pub fn new(buffer: &'a [u8]) -> Self {
Self { buffer }
}
}
impl<'a> Iterator for Iter<'a> {
type Item = GenericPerformanceRecord<&'a [u8]>;
fn next(&mut self) -> Option<Self::Item> {
if self.buffer.is_empty() {
return None;
}
let mut offset = 0;
let record_type = self.buffer.gread::<u16>(&mut offset).unwrap();
let length = self.buffer.gread::<u8>(&mut offset).unwrap();
let revision = self.buffer.gread::<u8>(&mut offset).unwrap();
let data = &self.buffer[offset..length as usize];
self.buffer = &self.buffer[length as usize..];
Some(GenericPerformanceRecord::new(record_type, length, revision, data))
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct GuidEventRecordData {
pub progress_id: u16,
pub apic_id: u32,
pub timestamp: u64,
pub guid: [u8; 16],
}
impl GuidEventRecordData {
pub const NAME: &'static str = "GUID Event";
}
impl fmt::Display for GuidEventRecordData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let progress_id = self.progress_id;
let apic_id = self.apic_id;
let timestamp = self.timestamp;
let guid = BinaryGuid::from_bytes(&self.guid);
write!(f, "progress_id={}, apic_id={}, timestamp={}, guid={}", progress_id, apic_id, timestamp, guid)
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct DynamicStringEventRecordData {
pub progress_id: u16,
pub apic_id: u32,
pub timestamp: u64,
pub guid: [u8; 16],
}
impl DynamicStringEventRecordData {
pub const NAME: &'static str = "Dynamic String Event";
pub fn extract_string(full_data: &[u8]) -> &str {
if full_data.len() <= core::mem::size_of::<Self>() {
return "";
}
let string_bytes = &full_data[core::mem::size_of::<Self>()..];
let string_len = string_bytes.iter().position(|&b| b == 0).unwrap_or(string_bytes.len());
core::str::from_utf8(&string_bytes[..string_len]).unwrap_or("<invalid UTF-8>")
}
}
impl fmt::Display for DynamicStringEventRecordData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let progress_id = self.progress_id;
let apic_id = self.apic_id;
let timestamp = self.timestamp;
let guid = BinaryGuid::from_bytes(&self.guid);
write!(f, "progress_id: 0x{:04X}, apic_id: {}, timestamp: {}, guid: {}", progress_id, apic_id, timestamp, guid)
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct DualGuidStringEventRecordData {
pub progress_id: u16,
pub apic_id: u32,
pub timestamp: u64,
pub guid_1: [u8; 16],
pub guid_2: [u8; 16],
}
impl DualGuidStringEventRecordData {
pub const NAME: &'static str = "Dual GUID String Event";
pub fn extract_string(full_data: &[u8]) -> &str {
if full_data.len() <= core::mem::size_of::<Self>() {
return "";
}
let string_bytes = &full_data[core::mem::size_of::<Self>()..];
let string_len = string_bytes.iter().position(|&b| b == 0).unwrap_or(string_bytes.len());
core::str::from_utf8(&string_bytes[..string_len]).unwrap_or("<invalid UTF-8>")
}
}
impl fmt::Display for DualGuidStringEventRecordData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let progress_id = self.progress_id;
let apic_id = self.apic_id;
let timestamp = self.timestamp;
let guid_1 = BinaryGuid::from_bytes(&self.guid_1);
let guid_2 = BinaryGuid::from_bytes(&self.guid_2);
write!(
f,
"progress_id: 0x{:04X}, apic_id: {}, timestamp: {}, guid_1: {}, guid_2: {}",
progress_id, apic_id, timestamp, guid_1, guid_2
)
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct GuidQwordEventRecordData {
pub progress_id: u16,
pub apic_id: u32,
pub timestamp: u64,
pub guid: [u8; 16],
pub qword: u64,
}
impl GuidQwordEventRecordData {
pub const NAME: &'static str = "GUID QWORD Event";
}
impl fmt::Display for GuidQwordEventRecordData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let progress_id = self.progress_id;
let apic_id = self.apic_id;
let timestamp = self.timestamp;
let guid = BinaryGuid::from_bytes(&self.guid);
let qword = self.qword;
write!(
f,
"progress_id: 0x{:04X}, apic_id: {}, timestamp: {}, guid: {}, qword: 0x{:016X}",
progress_id, apic_id, timestamp, guid, qword
)
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct GuidQwordStringEventRecordData {
pub progress_id: u16,
pub apic_id: u32,
pub timestamp: u64,
pub guid: [u8; 16],
pub qword: u64,
}
impl GuidQwordStringEventRecordData {
pub const NAME: &'static str = "GUID QWORD String Event";
pub fn extract_string(full_data: &[u8]) -> &str {
if full_data.len() <= core::mem::size_of::<Self>() {
return "";
}
let string_bytes = &full_data[core::mem::size_of::<Self>()..];
let string_len = string_bytes.iter().position(|&b| b == 0).unwrap_or(string_bytes.len());
core::str::from_utf8(&string_bytes[..string_len]).unwrap_or("<invalid UTF-8>")
}
}
impl fmt::Display for GuidQwordStringEventRecordData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let progress_id = self.progress_id;
let apic_id = self.apic_id;
let timestamp = self.timestamp;
let guid = BinaryGuid::from_bytes(&self.guid);
let qword = self.qword;
write!(
f,
"progress_id: 0x{:04X}, apic_id: {}, timestamp: {}, guid: {}, qword: 0x{:016X}",
progress_id, apic_id, timestamp, guid, qword
)
}
}
pub trait PerformanceRecordDetails {
fn print_details(&self, record_number: usize);
}
impl PerformanceRecordDetails for GuidEventRecordData {
fn print_details(&self, record_number: usize) {
log::debug!(" Record #{}: {}", record_number, self);
}
}
impl PerformanceRecordDetails for DynamicStringEventRecordData {
fn print_details(&self, record_number: usize) {
log::debug!(" Record #{}: {}", record_number, self);
}
}
impl PerformanceRecordDetails for DualGuidStringEventRecordData {
fn print_details(&self, record_number: usize) {
log::debug!(" Record #{}: {}", record_number, self);
}
}
impl PerformanceRecordDetails for GuidQwordEventRecordData {
fn print_details(&self, record_number: usize) {
log::debug!(" Record #{}: {}", record_number, self);
}
}
impl PerformanceRecordDetails for GuidQwordStringEventRecordData {
fn print_details(&self, record_number: usize) {
log::debug!(" Record #{}: {}", record_number, self);
}
}
pub fn print_record_details(record_type: u16, record_number: usize, data: &[u8]) {
match record_type {
0x1010 => {
if data.len() >= core::mem::size_of::<GuidEventRecordData>() {
let record = unsafe { &*(data.as_ptr() as *const GuidEventRecordData) };
record.print_details(record_number);
}
}
0x1011 => {
if data.len() >= core::mem::size_of::<DynamicStringEventRecordData>() {
let record = unsafe { &*(data.as_ptr() as *const DynamicStringEventRecordData) };
record.print_details(record_number);
let string_data = DynamicStringEventRecordData::extract_string(data);
if !string_data.is_empty() {
log::debug!(" String: \"{}\"", string_data);
}
}
}
0x1012 => {
if data.len() >= core::mem::size_of::<DualGuidStringEventRecordData>() {
let record = unsafe { &*(data.as_ptr() as *const DualGuidStringEventRecordData) };
record.print_details(record_number);
let string_data = DualGuidStringEventRecordData::extract_string(data);
if !string_data.is_empty() {
log::debug!(" String: \"{}\"", string_data);
}
}
}
0x1013 => {
if data.len() >= core::mem::size_of::<GuidQwordEventRecordData>() {
let record = unsafe { &*(data.as_ptr() as *const GuidQwordEventRecordData) };
record.print_details(record_number);
}
}
0x1014 => {
if data.len() >= core::mem::size_of::<GuidQwordStringEventRecordData>() {
let record = unsafe { &*(data.as_ptr() as *const GuidQwordStringEventRecordData) };
record.print_details(record_number);
let string_data = GuidQwordStringEventRecordData::extract_string(data);
if !string_data.is_empty() {
log::debug!(" String: \"{}\"", string_data);
}
}
}
_ => {
log::debug!(" Record #{}: Unknown type 0x{:04X}", record_number, record_type);
}
}
}
pub fn record_type_name(record_type: u16) -> &'static str {
match record_type {
0x1010 => GuidEventRecordData::NAME,
0x1011 => DynamicStringEventRecordData::NAME,
0x1012 => DualGuidStringEventRecordData::NAME,
0x1013 => GuidQwordEventRecordData::NAME,
0x1014 => GuidQwordStringEventRecordData::NAME,
_ => "Unknown",
}
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use super::*;
use core::{assert_eq, slice, unreachable};
use extended::{
DualGuidStringEventRecord, DynamicStringEventRecord, GuidEventRecord, GuidQwordEventRecord,
GuidQwordStringEventRecord,
};
#[test]
fn test_performance_record_buffer_new() {
let performance_record_buffer = PerformanceRecordBuffer::new();
println!("{performance_record_buffer:?}");
assert_eq!(0, performance_record_buffer.size());
}
#[test]
fn test_performance_record_buffer_push_record() {
let guid = crate::guids::ZERO;
let mut performance_record_buffer = PerformanceRecordBuffer::new();
let mut size = 0;
size += performance_record_buffer.push_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
assert_eq!(size, performance_record_buffer.size());
size += performance_record_buffer.push_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
assert_eq!(size, performance_record_buffer.size());
size += performance_record_buffer
.push_record(DualGuidStringEventRecord::new(1, 0, 10, guid, guid, "test"))
.unwrap();
assert_eq!(size, performance_record_buffer.size());
size += performance_record_buffer.push_record(GuidQwordEventRecord::new(1, 0, 10, guid, 64)).unwrap();
assert_eq!(size, performance_record_buffer.size());
size +=
performance_record_buffer.push_record(GuidQwordStringEventRecord::new(1, 0, 10, guid, 64, "test")).unwrap();
assert_eq!(size, performance_record_buffer.size());
}
#[test]
fn test_performance_record_buffer_iter() {
let guid = crate::guids::ZERO;
let mut performance_record_buffer = PerformanceRecordBuffer::new();
performance_record_buffer.push_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
performance_record_buffer.push_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
performance_record_buffer.push_record(DualGuidStringEventRecord::new(1, 0, 10, guid, guid, "test")).unwrap();
performance_record_buffer.push_record(GuidQwordEventRecord::new(1, 0, 10, guid, 64)).unwrap();
performance_record_buffer.push_record(GuidQwordStringEventRecord::new(1, 0, 10, guid, 64, "test")).unwrap();
for (i, record) in performance_record_buffer.iter().enumerate() {
match i {
_ if i == 0 => assert_eq!(
(GuidEventRecord::TYPE, GuidEventRecord::REVISION),
(record.record_type, record.revision)
),
_ if i == 1 => assert_eq!(
(DynamicStringEventRecord::TYPE, DynamicStringEventRecord::REVISION),
(record.record_type, record.revision)
),
_ if i == 2 => assert_eq!(
(DualGuidStringEventRecord::TYPE, DualGuidStringEventRecord::REVISION),
(record.record_type, record.revision)
),
_ if i == 3 => assert_eq!(
(GuidQwordEventRecord::TYPE, GuidQwordEventRecord::REVISION),
(record.record_type, record.revision)
),
_ if i == 4 => assert_eq!(
(GuidQwordStringEventRecord::TYPE, GuidQwordStringEventRecord::REVISION),
(record.record_type, record.revision)
),
_ => unreachable!(),
}
}
}
#[test]
fn test_performance_record_buffer_reported_table() {
let guid = crate::guids::ZERO;
let mut performance_record_buffer = PerformanceRecordBuffer::new();
performance_record_buffer.push_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
performance_record_buffer.push_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
let mut buffer = vec![0_u8; 1000];
let buffer = unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr(), buffer.len()) };
performance_record_buffer.report(buffer).unwrap();
performance_record_buffer.push_record(DualGuidStringEventRecord::new(1, 0, 10, guid, guid, "test")).unwrap();
performance_record_buffer.push_record(GuidQwordEventRecord::new(1, 0, 10, guid, 64)).unwrap();
performance_record_buffer.push_record(GuidQwordStringEventRecord::new(1, 0, 10, guid, 64, "test")).unwrap();
for (i, record) in performance_record_buffer.iter().enumerate() {
match i {
_ if i == 0 => assert_eq!(
(GuidEventRecord::TYPE, GuidEventRecord::REVISION),
(record.record_type, record.revision)
),
_ if i == 1 => assert_eq!(
(DynamicStringEventRecord::TYPE, DynamicStringEventRecord::REVISION),
(record.record_type, record.revision)
),
_ if i == 2 => assert_eq!(
(DualGuidStringEventRecord::TYPE, DualGuidStringEventRecord::REVISION),
(record.record_type, record.revision)
),
_ if i == 3 => assert_eq!(
(GuidQwordEventRecord::TYPE, GuidQwordEventRecord::REVISION),
(record.record_type, record.revision)
),
_ if i == 4 => assert_eq!(
(GuidQwordStringEventRecord::TYPE, GuidQwordStringEventRecord::REVISION),
(record.record_type, record.revision)
),
_ => unreachable!(),
}
}
}
#[test]
fn test_performance_record_header_try_from_valid_bytes() {
let original_header = PerformanceRecordHeader::new(0x1234, 42, 1);
let bytes: [u8; 4] = original_header.into();
let parsed_header = PerformanceRecordHeader::try_from(bytes.as_slice()).unwrap();
let parsed_type = parsed_header.record_type;
let parsed_length = parsed_header.length;
let parsed_revision = parsed_header.revision;
assert_eq!(parsed_type, 0x1234);
assert_eq!(parsed_length, 42);
assert_eq!(parsed_revision, 1);
}
#[test]
fn test_performance_record_header_try_from_insufficient_bytes() {
let bytes = [0x34, 0x12]; let result = PerformanceRecordHeader::try_from(bytes.as_slice());
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Insufficient bytes for PerformanceRecordHeader");
}
#[test]
fn test_performance_record_header_try_from_empty_bytes() {
let bytes: &[u8] = &[];
let result = PerformanceRecordHeader::try_from(bytes);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Insufficient bytes for PerformanceRecordHeader");
}
#[test]
fn test_performance_record_header_from_trait_conversion_le() {
let header = PerformanceRecordHeader::new(0xABCD, 100, 2);
let bytes: [u8; mem::size_of::<PerformanceRecordHeader>()] = header.into();
assert_eq!(bytes[0], 0xCD); assert_eq!(bytes[1], 0xAB); assert_eq!(bytes[2], 100); assert_eq!(bytes[3], 2); }
#[test]
fn test_performance_record_header_roundtrip_conversion() {
let original_header = PerformanceRecordHeader::new(0x5678, 200, 3);
let bytes: [u8; mem::size_of::<PerformanceRecordHeader>()] = original_header.into();
let parsed_header = PerformanceRecordHeader::try_from(bytes.as_slice()).unwrap();
let orig_type = original_header.record_type;
let orig_length = original_header.length;
let orig_revision = original_header.revision;
let parsed_type = parsed_header.record_type;
let parsed_length = parsed_header.length;
let parsed_revision = parsed_header.revision;
assert_eq!(orig_type, parsed_type);
assert_eq!(orig_length, parsed_length);
assert_eq!(orig_revision, parsed_revision);
}
#[test]
fn test_performance_record_header_le_handling() {
let header = PerformanceRecordHeader::new(0x0102, 50, 1);
let bytes: [u8; mem::size_of::<PerformanceRecordHeader>()] = header.into();
assert_eq!(bytes[0], 0x02);
assert_eq!(bytes[1], 0x01);
assert_eq!(bytes[2], 50);
assert_eq!(bytes[3], 1);
let parsed = PerformanceRecordHeader::try_from(bytes.as_slice()).unwrap();
let parsed_type = parsed.record_type;
let parsed_length = parsed.length;
let parsed_revision = parsed.revision;
assert_eq!(parsed_type, 0x0102);
assert_eq!(parsed_length, 50);
assert_eq!(parsed_revision, 1);
}
#[test]
fn test_performance_record_header_try_from_extra_bytes() {
let mut bytes = vec![0x34, 0x12, 42, 1]; bytes.extend_from_slice(&[0xFF, 0xFF, 0xFF]);
let parsed_header = PerformanceRecordHeader::try_from(bytes.as_slice()).unwrap();
let parsed_type = parsed_header.record_type;
let parsed_length = parsed_header.length;
let parsed_revision = parsed_header.revision;
assert_eq!(parsed_type, 0x1234);
assert_eq!(parsed_length, 42);
assert_eq!(parsed_revision, 1);
}
#[test]
fn test_guid_event_record_data_display() {
let record = GuidEventRecordData {
progress_id: 0x1234,
apic_id: 42,
timestamp: 1000000,
guid: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10],
};
let display_str = format!("{}", record);
assert!(display_str.contains("progress_id=4660"));
assert!(display_str.contains("apic_id=42"));
assert!(display_str.contains("timestamp=1000000"));
}
#[test]
fn test_dynamic_string_event_record_data_display() {
let record = DynamicStringEventRecordData {
progress_id: 0x5678,
apic_id: 99,
timestamp: 2000000,
guid: [0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20],
};
let display_str = format!("{}", record);
assert!(display_str.contains("progress_id: 0x5678"));
assert!(display_str.contains("apic_id: 99"));
assert!(display_str.contains("timestamp: 2000000"));
}
#[test]
fn test_dynamic_string_event_record_data_extract_string() {
let mut data = vec![0u8; core::mem::size_of::<DynamicStringEventRecordData>()];
let test_string = b"Test String\0";
data.extend_from_slice(test_string);
let extracted = DynamicStringEventRecordData::extract_string(&data);
assert_eq!(extracted, "Test String");
}
#[test]
fn test_dynamic_string_event_record_data_extract_string_empty() {
let data = vec![0u8; core::mem::size_of::<DynamicStringEventRecordData>()];
let extracted = DynamicStringEventRecordData::extract_string(&data);
assert_eq!(extracted, "");
}
#[test]
fn test_dynamic_string_event_record_data_extract_string_invalid_utf8() {
let mut data = vec![0u8; core::mem::size_of::<DynamicStringEventRecordData>()];
data.extend_from_slice(&[0xFF, 0xFE, 0xFD, 0x00]);
let extracted = DynamicStringEventRecordData::extract_string(&data);
assert_eq!(extracted, "<invalid UTF-8>");
}
#[test]
fn test_dynamic_string_event_record_data_extract_string_no_null_terminator() {
let mut data = vec![0u8; core::mem::size_of::<DynamicStringEventRecordData>()];
data.extend_from_slice(b"NoNull");
let extracted = DynamicStringEventRecordData::extract_string(&data);
assert_eq!(extracted, "NoNull");
}
#[test]
fn test_dual_guid_string_event_record_data_display() {
let record = DualGuidStringEventRecordData {
progress_id: 0xABCD,
apic_id: 123,
timestamp: 3000000,
guid_1: [0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30],
guid_2: [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40],
};
let display_str = format!("{}", record);
assert!(display_str.contains("progress_id: 0xABCD"));
assert!(display_str.contains("apic_id: 123"));
assert!(display_str.contains("timestamp: 3000000"));
assert!(display_str.contains("guid_1:"));
assert!(display_str.contains("guid_2:"));
}
#[test]
fn test_dual_guid_string_event_record_data_extract_string() {
let mut data = vec![0u8; core::mem::size_of::<DualGuidStringEventRecordData>()];
let test_string = b"DualGuidTest\0";
data.extend_from_slice(test_string);
let extracted = DualGuidStringEventRecordData::extract_string(&data);
assert_eq!(extracted, "DualGuidTest");
}
#[test]
fn test_dual_guid_string_event_record_data_extract_string_empty() {
let data = vec![0u8; core::mem::size_of::<DualGuidStringEventRecordData>()];
let extracted = DualGuidStringEventRecordData::extract_string(&data);
assert_eq!(extracted, "");
}
#[test]
fn test_guid_qword_event_record_data_display() {
let record = GuidQwordEventRecordData {
progress_id: 0xEF01,
apic_id: 200,
timestamp: 4000000,
guid: [0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50],
qword: 0x123456789ABCDEF0,
};
let display_str = format!("{}", record);
assert!(display_str.contains("progress_id: 0xEF01"));
assert!(display_str.contains("apic_id: 200"));
assert!(display_str.contains("timestamp: 4000000"));
assert!(display_str.contains("qword: 0x123456789ABCDEF0"));
}
#[test]
fn test_guid_qword_string_event_record_data_display() {
let record = GuidQwordStringEventRecordData {
progress_id: 0x2345,
apic_id: 77,
timestamp: 5000000,
guid: [0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60],
qword: 0xFEDCBA9876543210,
};
let display_str = format!("{}", record);
assert!(display_str.contains("progress_id: 0x2345"));
assert!(display_str.contains("apic_id: 77"));
assert!(display_str.contains("timestamp: 5000000"));
assert!(display_str.contains("qword: 0xFEDCBA9876543210"));
}
#[test]
fn test_guid_qword_string_event_record_data_extract_string() {
let mut data = vec![0u8; core::mem::size_of::<GuidQwordStringEventRecordData>()];
let test_string = b"QwordString\0";
data.extend_from_slice(test_string);
let extracted = GuidQwordStringEventRecordData::extract_string(&data);
assert_eq!(extracted, "QwordString");
}
#[test]
fn test_guid_qword_string_event_record_data_extract_string_empty() {
let data = vec![0u8; core::mem::size_of::<GuidQwordStringEventRecordData>()];
let extracted = GuidQwordStringEventRecordData::extract_string(&data);
assert_eq!(extracted, "");
}
#[test]
fn test_record_type_name_all_types() {
assert_eq!(record_type_name(0x1010), GuidEventRecordData::NAME);
assert_eq!(record_type_name(0x1011), DynamicStringEventRecordData::NAME);
assert_eq!(record_type_name(0x1012), DualGuidStringEventRecordData::NAME);
assert_eq!(record_type_name(0x1013), GuidQwordEventRecordData::NAME);
assert_eq!(record_type_name(0x1014), GuidQwordStringEventRecordData::NAME);
assert_eq!(record_type_name(0x9999), "Unknown");
}
#[test]
fn test_record_type_name_boundary_values() {
assert_eq!(record_type_name(0x0000), "Unknown");
assert_eq!(record_type_name(0xFFFF), "Unknown");
assert_eq!(record_type_name(0x100F), "Unknown");
assert_eq!(record_type_name(0x1015), "Unknown");
}
#[test]
fn test_guid_event_record_data_name_constant() {
assert_eq!(GuidEventRecordData::NAME, "GUID Event");
}
#[test]
fn test_dynamic_string_event_record_data_name_constant() {
assert_eq!(DynamicStringEventRecordData::NAME, "Dynamic String Event");
}
#[test]
fn test_dual_guid_string_event_record_data_name_constant() {
assert_eq!(DualGuidStringEventRecordData::NAME, "Dual GUID String Event");
}
#[test]
fn test_guid_qword_event_record_data_name_constant() {
assert_eq!(GuidQwordEventRecordData::NAME, "GUID QWORD Event");
}
#[test]
fn test_guid_qword_string_event_record_data_name_constant() {
assert_eq!(GuidQwordStringEventRecordData::NAME, "GUID QWORD String Event");
}
}