///|
pub(all) enum AuditSeverity {
AuditInfo
AuditWarning
AuditError
} derive(Eq, Debug)
///|
pub struct AuditPolicy {
max_gap_bytes_value : UInt64?
allowed_start_value : UInt64?
allowed_end_exclusive_value : UInt64?
max_segments_value : Int?
max_total_bytes_value : Int?
require_entry_in_data_value : Bool
} derive(Eq, Debug)
///|
pub struct AuditFinding {
code_value : String
severity_value : AuditSeverity
message_value : String
address_value : UInt64?
} derive(Eq, Debug)
///|
pub struct ImageAudit {
finding_values : Array[AuditFinding]
segment_count_value : Int
total_bytes_value : Int
gap_count_value : Int
} derive(Eq, Debug)
///|
pub fn AuditPolicy::new(
max_gap_bytes? : UInt64? = None,
allowed_start? : UInt64? = None,
allowed_end_exclusive? : UInt64? = None,
max_segments? : Int? = None,
max_total_bytes? : Int? = None,
require_entry_in_data? : Bool = false,
) -> Result[AuditPolicy, FirmwareError] {
match (allowed_start, allowed_end_exclusive) {
(Some(start), Some(end)) if start >= end =>
return Err(invalid_image_range("audit address range must be non-empty"))
_ => ()
}
match max_segments {
Some(value) if value < 0 =>
return Err(invalid_image_range("audit segment limit cannot be negative"))
_ => ()
}
match max_total_bytes {
Some(value) if value < 0 =>
return Err(invalid_image_range("audit byte limit cannot be negative"))
_ => ()
}
Ok({
max_gap_bytes_value: max_gap_bytes,
allowed_start_value: allowed_start,
allowed_end_exclusive_value: allowed_end_exclusive,
max_segments_value: max_segments,
max_total_bytes_value: max_total_bytes,
require_entry_in_data_value: require_entry_in_data,
})
}
///|
pub fn AuditPolicy::standard() -> AuditPolicy {
{
max_gap_bytes_value: None,
allowed_start_value: None,
allowed_end_exclusive_value: None,
max_segments_value: None,
max_total_bytes_value: None,
require_entry_in_data_value: false,
}
}
///|
fn audit_finding(
code : String,
severity : AuditSeverity,
message : String,
address? : UInt64? = None,
) -> AuditFinding {
{
code_value: code,
severity_value: severity,
message_value: message,
address_value: address,
}
}
///|
fn image_contains_address(image : FirmwareImage, address : UInt64) -> Bool {
image.byte_at(address) is Some(_)
}
///|
/// Evaluate deterministic image acceptance rules without modifying the image.
pub fn audit_image(image : FirmwareImage, policy : AuditPolicy) -> ImageAudit {
let findings : Array[AuditFinding] = []
let segments = image.segments()
let gaps = image.gaps()
if segments.length() == 0 {
findings.push(
audit_finding(
"image.empty",
AuditWarning,
"firmware image contains no data bytes",
),
)
}
match policy.max_segments_value {
Some(limit) if segments.length() > limit =>
findings.push(
audit_finding(
"image.segment_limit",
AuditError,
"firmware image exceeds the configured segment limit",
),
)
_ => ()
}
match policy.max_total_bytes_value {
Some(limit) if image.total_bytes() > limit =>
findings.push(
audit_finding(
"image.byte_limit",
AuditError,
"firmware image exceeds the configured byte limit",
),
)
_ => ()
}
for gap in gaps {
match policy.max_gap_bytes_value {
Some(limit) if gap.length() > limit =>
findings.push(
audit_finding(
"image.gap_limit",
AuditWarning,
"sparse image gap exceeds the configured limit",
address=Some(gap.start()),
),
)
_ => ()
}
}
for segment in segments {
match policy.allowed_start_value {
Some(start) if segment.address() < start =>
findings.push(
audit_finding(
"image.address_below_range",
AuditError,
"segment begins below the allowed address range",
address=Some(segment.address()),
),
)
_ => ()
}
match policy.allowed_end_exclusive_value {
Some(end) if segment.end_exclusive() > end =>
findings.push(
audit_finding(
"image.address_above_range",
AuditError,
"segment ends above the allowed address range",
address=Some(segment.address()),
),
)
_ => ()
}
}
if policy.require_entry_in_data_value {
match image.entry_point() {
Some(value) if !image_contains_address(image, value) =>
findings.push(
audit_finding(
"image.entry_outside_data",
AuditError,
"entry point does not reference a byte in the image",
address=Some(value),
),
)
None =>
findings.push(
audit_finding(
"image.entry_missing",
AuditError,
"image has no entry point",
),
)
_ => ()
}
}
{
finding_values: findings,
segment_count_value: segments.length(),
total_bytes_value: image.total_bytes(),
gap_count_value: gaps.length(),
}
}
///|
pub fn AuditFinding::code(self : AuditFinding) -> String {
self.code_value
}
///|
pub fn AuditFinding::severity(self : AuditFinding) -> AuditSeverity {
self.severity_value
}
///|
pub fn AuditFinding::message(self : AuditFinding) -> String {
self.message_value
}
///|
pub fn AuditFinding::address(self : AuditFinding) -> UInt64? {
self.address_value
}
///|
pub fn ImageAudit::findings(self : ImageAudit) -> Array[AuditFinding] {
self.finding_values.copy()
}
///|
pub fn ImageAudit::segment_count(self : ImageAudit) -> Int {
self.segment_count_value
}
///|
pub fn ImageAudit::total_bytes(self : ImageAudit) -> Int {
self.total_bytes_value
}
///|
pub fn ImageAudit::gap_count(self : ImageAudit) -> Int {
self.gap_count_value
}
///|
pub fn ImageAudit::error_count(self : ImageAudit) -> Int {
let mut count = 0
for finding in self.finding_values {
if finding.severity() == AuditError {
count = count + 1
}
}
count
}
///|
pub fn ImageAudit::warning_count(self : ImageAudit) -> Int {
let mut count = 0
for finding in self.finding_values {
if finding.severity() == AuditWarning {
count = count + 1
}
}
count
}
///|
pub fn ImageAudit::accepted(self : ImageAudit) -> Bool {
self.error_count() == 0
}