use comfy_table::{Attribute, Cell};
use owo_colors::OwoColorize;
use crate::format::human_bytes;
use crate::heatmap::{self, CellColor};
use crate::render::{header_cell, make_table};
#[repr(usize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Verdict {
Preserved = 0,
Overwritten = 1,
Noise = 2,
Partial = 3,
}
pub fn classify_dual_block(block_start: u32, a: &[u32], b: &[u32]) -> Verdict {
debug_assert_eq!(a.len(), b.len());
let mut all_preserved = true;
let mut all_overwritten = true;
let mut any_overwritten = false;
let mut any_noise = false;
for (i, (&av, &bv)) in a.iter().zip(b.iter()).enumerate() {
let addr = block_start + (i as u32) * 4;
let preserved = av == addr && bv == !addr;
let overwritten = av == bv && !preserved;
let noise = !preserved && !overwritten;
if !preserved {
all_preserved = false;
}
if !overwritten {
all_overwritten = false;
}
if overwritten {
any_overwritten = true;
}
if noise {
any_noise = true;
}
}
if all_preserved {
Verdict::Preserved
} else if all_overwritten {
Verdict::Overwritten
} else if any_overwritten && any_noise {
Verdict::Partial
} else {
Verdict::Noise
}
}
pub fn print_dual_pattern_verdict(start: u32, end: u32, block: u32, a: &[u32], b: &[u32]) {
let words_per_block = (block / 4) as usize;
let n_blocks = ((end - start) / block) as usize;
let mut verdicts = Vec::with_capacity(n_blocks);
for bi in 0..n_blocks {
let bs = start + (bi as u32) * block;
let aw = &a[bi * words_per_block..(bi + 1) * words_per_block];
let bw = &b[bi * words_per_block..(bi + 1) * words_per_block];
verdicts.push(classify_dual_block(bs, aw, bw));
}
heatmap::render(
"Dual-pattern verdict heatmap",
"(each cell = 1 block)",
start,
block,
n_blocks,
|cell| match verdicts[cell] {
Verdict::Preserved => CellColor::Green,
Verdict::Overwritten => CellColor::Red,
Verdict::Noise => CellColor::Magenta,
Verdict::Partial => CellColor::Yellow,
},
);
println!();
heatmap::legend(&[
(CellColor::Green, "preserved (writes survived)"),
(CellColor::Red, "overwritten (deterministic ROM action)"),
(CellColor::Magenta, "noise (floating cells)"),
(CellColor::Yellow, "partial"),
]);
let mut counts = [0usize; 4];
for v in &verdicts {
counts[*v as usize] += 1;
}
let mut t = make_table();
t.set_header(vec![
header_cell("Verdict"),
header_cell("Blocks"),
header_cell("Bytes"),
]);
for (name, color, count) in [
(
"PRESERVED",
comfy_table::Color::Green,
counts[Verdict::Preserved as usize],
),
(
"OVERWRITTEN",
comfy_table::Color::Red,
counts[Verdict::Overwritten as usize],
),
(
"NOISE",
comfy_table::Color::Magenta,
counts[Verdict::Noise as usize],
),
(
"PARTIAL",
comfy_table::Color::Yellow,
counts[Verdict::Partial as usize],
),
] {
t.add_row(vec![
Cell::new(name).fg(color).add_attribute(Attribute::Bold),
Cell::new(count.to_string()),
Cell::new(human_bytes(count as u64 * block as u64)),
]);
}
println!("{t}");
let noise_or_partial = counts[Verdict::Noise as usize] + counts[Verdict::Partial as usize];
let overwritten = counts[Verdict::Overwritten as usize];
println!();
if noise_or_partial > overwritten && noise_or_partial > 0 {
println!(
" {} most non-preserved blocks read differently between the two passes — strong evidence of {}.",
"VERDICT:".red().bold(),
"undriven RAM (power-gated partitions)".bold()
);
} else if overwritten > 0 && noise_or_partial == 0 {
println!(
" {} non-preserved blocks read identically between both passes — strong evidence of {}.",
"VERDICT:".yellow().bold(),
"deterministic ROM overwrite".bold()
);
} else if overwritten > 0 && noise_or_partial > 0 {
println!(
" {} mixed: {} blocks deterministic-overwrite, {} blocks floating/partial.",
"VERDICT:".yellow().bold(),
overwritten,
noise_or_partial
);
} else {
println!(
" {} all blocks were preserved — nothing to explain.",
"VERDICT:".green().bold()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pair(addr: u32, n: usize) -> (Vec<u32>, Vec<u32>) {
let a: Vec<u32> = (0..n as u32).map(|i| addr + i * 4).collect();
let b: Vec<u32> = a.iter().map(|&w| !w).collect();
(a, b)
}
#[test]
fn preserved_when_both_patterns_intact() {
let (a, b) = pair(0x2000_0000, 4);
assert_eq!(classify_dual_block(0x2000_0000, &a, &b), Verdict::Preserved);
}
#[test]
fn overwritten_when_both_reads_equal_but_wrong() {
let a = vec![0u32; 4];
let b = vec![0u32; 4];
assert_eq!(
classify_dual_block(0x2000_0000, &a, &b),
Verdict::Overwritten
);
}
#[test]
fn noise_when_reads_differ_and_dont_match_pattern() {
let a = vec![0xAAAA_AAAA, 0xBBBB_BBBB, 0xCCCC_CCCC, 0xDDDD_DDDD];
let b = vec![0x1111_1111, 0x2222_2222, 0x3333_3333, 0x4444_4444];
assert_eq!(classify_dual_block(0x2000_0000, &a, &b), Verdict::Noise);
}
#[test]
fn partial_mixes_overwritten_and_noise() {
let a = vec![0u32, 0xAAAA_AAAA, 0u32, 0xCCCC_CCCC];
let b = vec![0u32, 0xBBBB_BBBB, 0u32, 0xDDDD_DDDD];
assert_eq!(classify_dual_block(0x2000_0000, &a, &b), Verdict::Partial);
}
}