use std::{os::raw::c_void, ptr::NonNull};
use crate::{
ConstantEntry, errors::Error, perf_data::PerfDataEntryHeader, safish_pointer::SafishPointer,
variable_data_reference::VariableDataReference,
};
const MAJOR_VERSION: u8 = 2;
const MINOR_VERSION: u8 = 0;
const BIG_ENDIAN_MAGIC_NUMBER: u32 = 0xcafec0c0u32;
const LITTLE_ENDIAN_MAGIC_NUMBER: u32 = 0xc0c0fecau32;
#[repr(u32)]
#[derive(Debug, PartialEq)]
enum MagicNumber {
LittleEndian = LITTLE_ENDIAN_MAGIC_NUMBER,
BigEndian = BIG_ENDIAN_MAGIC_NUMBER,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum Endianness {
BigEndian = 0,
LittleEndian = 1,
}
#[repr(C)]
#[derive(Debug)]
pub(crate) struct PerfDataProlog {
magic: MagicNumber,
byte_order: Endianness,
major_version: u8,
minor_version: u8,
accessible: u8,
used: i32,
overflow: i32,
mod_time_stamp: i64,
entry_offset: i32,
num_entries: i32,
}
impl PerfDataProlog {
pub(crate) fn read_entries(
prolog_addr: &NonNull<c_void>,
length: usize,
) -> Result<(Vec<ConstantEntry>, Vec<VariableDataReference>), Error> {
let prolog = Self::new(prolog_addr).validate()?.validate_length(length)?;
prolog.map_entries(prolog_addr)
}
fn new(prolog_addr: &NonNull<c_void>) -> Self {
unsafe { (prolog_addr.as_ptr() as *const PerfDataProlog).read_volatile() }
}
fn validate_length(self, length: usize) -> Result<Self, crate::errors::Error> {
if length >= self.used as usize {
Ok(self)
} else {
Err(Error::WontBeAbleToRead)
}
}
fn validate(self) -> Result<Self, crate::errors::Error> {
if self.major_version != MAJOR_VERSION {
Err(crate::errors::Error::UnsupportedMajorVersion)
} else if self.minor_version != MINOR_VERSION {
Err(crate::errors::Error::UnsupportedMinorVersion)
} else if self.byte_order == Endianness::BigEndian && self.magic != MagicNumber::BigEndian {
Err(crate::errors::Error::InvalidMagicNumber)
} else if self.byte_order == Endianness::LittleEndian
&& self.magic != MagicNumber::LittleEndian
{
Err(crate::errors::Error::InvalidMagicNumber)
} else {
Ok(self)
}
}
fn map_entries(
self,
prolog_addr: &NonNull<c_void>,
) -> Result<(Vec<ConstantEntry>, Vec<VariableDataReference>), Error> {
let entries_ptr = self.entries_ptr(&prolog_addr)?;
let entries_count = self.num_entries as usize;
let mut variables: Vec<VariableDataReference> = Vec::with_capacity(entries_count);
let mut constants: Vec<ConstantEntry> = Vec::with_capacity(entries_count);
let mut offset = 0 as usize;
for _ in 0..entries_count {
let entry_ptr: SafishPointer<PerfDataEntryHeader> =
entries_ptr.clone().add(offset)?.convert()?;
let header = entry_ptr.read();
if header.is_variable_entry() {
variables.push(header.read_variable_entry(entry_ptr)?);
} else {
constants.push(header.read_constant_entry(entry_ptr)?);
};
offset += header.entry_length() as usize;
}
constants.shrink_to_fit();
variables.shrink_to_fit();
Ok((constants, variables))
}
fn entries_ptr(&self, prolog_addr: &NonNull<c_void>) -> Result<SafishPointer<u8>, Error> {
SafishPointer::new(
prolog_addr.as_ptr() as *const u8,
self.used as usize,
self.byte_order,
)
.and_then(|ptr| ptr.add(self.entry_offset as usize))
}
}
#[cfg(test)]
mod tests {
use parameterized::parameterized;
use crate::perf_data::{
Endianness,
perf_data_prolog::{MAJOR_VERSION, MINOR_VERSION, MagicNumber, PerfDataProlog},
};
#[test]
fn validate_is_success() {
let tested_prolog = PerfDataProlog {
magic: super::MagicNumber::BigEndian,
byte_order: super::Endianness::BigEndian,
major_version: MAJOR_VERSION,
minor_version: MINOR_VERSION,
accessible: 0 as u8,
used: size_of::<PerfDataProlog>() as i32,
overflow: 0,
mod_time_stamp: 12345,
entry_offset: 0,
num_entries: 0,
};
let validation_result = tested_prolog.validate();
assert!(validation_result.is_ok());
}
#[test]
fn validate_is_an_error_when_major_version_value_is_incorrect() {
let tested_prolog = PerfDataProlog {
magic: super::MagicNumber::BigEndian,
byte_order: super::Endianness::BigEndian,
major_version: 123 as u8,
minor_version: MINOR_VERSION,
accessible: 0 as u8,
used: size_of::<PerfDataProlog>() as i32,
overflow: 0,
mod_time_stamp: 12345,
entry_offset: 0,
num_entries: 0,
};
let validation_result = tested_prolog.validate();
assert!(validation_result.is_err());
assert!(matches!(
validation_result.err().unwrap(),
crate::errors::Error::UnsupportedMajorVersion
));
}
#[test]
fn validate_is_an_error_when_minor_version_value_is_incorrect() {
let tested_prolog = PerfDataProlog {
magic: super::MagicNumber::BigEndian,
byte_order: super::Endianness::BigEndian,
major_version: MAJOR_VERSION,
minor_version: 123 as u8,
accessible: 0 as u8,
used: size_of::<PerfDataProlog>() as i32,
overflow: 0,
mod_time_stamp: 12345,
entry_offset: 0,
num_entries: 0,
};
let validation_result = tested_prolog.validate();
assert!(validation_result.is_err());
assert!(matches!(
validation_result.err().unwrap(),
crate::errors::Error::UnsupportedMinorVersion
));
}
#[parameterized(magic_number = {
MagicNumber::BigEndian, MagicNumber::LittleEndian
}, byte_order = {
Endianness::LittleEndian, Endianness::BigEndian
})]
fn validate_is_an_error_when_magic_number_does_not_match_byte_order(
magic_number: MagicNumber,
byte_order: Endianness,
) {
let tested_prolog = PerfDataProlog {
magic: magic_number,
byte_order: byte_order,
major_version: MAJOR_VERSION,
minor_version: MINOR_VERSION,
accessible: 0 as u8,
used: size_of::<PerfDataProlog>() as i32,
overflow: 0,
mod_time_stamp: 12345,
entry_offset: 0,
num_entries: 0,
};
let validation_result = tested_prolog.validate();
assert!(validation_result.is_err());
assert!(matches!(
validation_result.err().unwrap(),
crate::errors::Error::InvalidMagicNumber
));
}
}