use core::fmt;
use crate::ApmPartition;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Severity {
Info,
Low,
Medium,
High,
Critical,
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Severity::Info => "INFO",
Severity::Low => "LOW",
Severity::Medium => "MEDIUM",
Severity::High => "HIGH",
Severity::Critical => "CRITICAL",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum AnomalyKind {
MapCountMismatch {
index: usize,
found: u32,
expected: u32,
},
OverlappingPartitions { a: usize, b: usize },
PartitionOutOfBounds {
index: usize,
last_block: u32,
device_last_block: u32,
},
ResidualEntry { block: u32 },
ZeroLengthPartition { index: usize },
NoPartitionMapEntry,
UnknownPartitionType { index: usize, type_name: String },
UnmappedRegion { start_block: u32, end_block: u32 },
}
impl AnomalyKind {
#[must_use]
pub fn severity(&self) -> Severity {
use AnomalyKind as K;
match self {
K::OverlappingPartitions { .. } => Severity::Critical,
K::PartitionOutOfBounds { .. } | K::ResidualEntry { .. } | K::NoPartitionMapEntry => {
Severity::High
}
K::MapCountMismatch { .. } | K::UnmappedRegion { .. } => Severity::Medium,
K::ZeroLengthPartition { .. } => Severity::Low,
K::UnknownPartitionType { .. } => Severity::Info,
}
}
#[must_use]
pub fn code(&self) -> &'static str {
use AnomalyKind as K;
match self {
K::MapCountMismatch { .. } => "APM-MAP-COUNT",
K::OverlappingPartitions { .. } => "APM-PART-OVERLAP",
K::PartitionOutOfBounds { .. } => "APM-PART-OOB",
K::ResidualEntry { .. } => "APM-PART-RESIDUAL",
K::ZeroLengthPartition { .. } => "APM-PART-ZEROLEN",
K::NoPartitionMapEntry => "APM-NO-MAP-ENTRY",
K::UnknownPartitionType { .. } => "APM-PART-UNKNOWN",
K::UnmappedRegion { .. } => "APM-UNMAPPED",
}
}
#[must_use]
pub fn note(&self) -> String {
use AnomalyKind as K;
match self {
K::MapCountMismatch {
index,
found,
expected,
} => format!(
"Entry {index}: pmMapBlkCnt {found} disagrees with the map's {expected} \
— corruption or tampering"
),
K::OverlappingPartitions { a, b } => {
format!("Partitions {a} and {b} claim overlapping block ranges")
}
K::PartitionOutOfBounds {
index,
last_block,
device_last_block,
} => format!(
"Partition {index} ends at block {last_block}, beyond the device's last block \
{device_last_block}"
),
K::ResidualEntry { block } => format!(
"A PM partition entry exists at block {block}, beyond the declared map count \
— hidden or residual entry"
),
K::ZeroLengthPartition { index } => format!("Partition {index} has a zero block count"),
K::NoPartitionMapEntry => {
"No Apple_partition_map entry — the map does not describe itself".to_string()
}
K::UnknownPartitionType { index, type_name } => {
format!("Partition {index}: unrecognised type \"{type_name}\" — possibly custom or hidden")
}
K::UnmappedRegion {
start_block,
end_block,
} => format!(
"Blocks {start_block}–{end_block} are described by no partition entry — \
unaccounted/hidden space"
),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Anomaly {
pub severity: Severity,
pub code: &'static str,
pub kind: AnomalyKind,
pub note: String,
}
impl Anomaly {
#[must_use]
pub fn new(kind: AnomalyKind) -> Self {
Anomaly {
severity: kind.severity(),
code: kind.code(),
note: kind.note(),
kind,
}
}
}
impl fmt::Display for Anomaly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}: {}", self.severity, self.code, self.note)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ApmAnalysis {
pub block_size: u32,
pub device_block_count: u32,
pub partitions: Vec<ApmPartition>,
pub anomalies: Vec<Anomaly>,
}
impl ApmAnalysis {
#[must_use]
pub fn max_severity(&self) -> Option<Severity> {
self.anomalies.iter().map(|a| a.severity).max()
}
}