use std::fmt::Write as FmtWrite;
use crate::core::{layout::BitRange, read::unchecked_bit_range_be_read};
pub struct Annotations {
buckets: Vec<AnnotationBucket>,
current_offset: usize,
}
impl Default for Annotations {
fn default() -> Self {
Self::new()
}
}
impl Annotations {
pub fn new() -> Self {
Self {
buckets: Vec::new(),
current_offset: 0,
}
}
pub fn new_with(title: String, annotations: Vec<(BitRange, &'static str)>) -> Self {
let mut ann = Self::new();
ann.add(title, annotations);
ann
}
pub fn extend(&mut self, other: Annotations) {
for bucket in other.buckets {
let ann = offset_annotations(bucket.annotations, self.current_offset);
self.buckets.push(AnnotationBucket {
title: bucket.title,
annotations: ann,
});
}
self.current_offset = self
.buckets
.last()
.map_or_else(|| 0, |b| b.annotations.last().map_or(0, |a| a.0.end));
}
pub fn add(&mut self, title: String, annotations: Vec<(BitRange, &'static str)>) {
let annotations = offset_annotations(annotations, self.current_offset);
self.buckets.push(AnnotationBucket { title, annotations });
self.current_offset = self
.buckets
.last()
.map_or_else(|| 0, |b| b.annotations.last().map_or(0, |a| a.0.end));
}
}
impl Annotations {
pub fn fmt_on_buffer(
&self,
out: &mut impl FmtWrite,
buffer: &[u8],
bytes_per_line: usize,
) -> std::fmt::Result {
const PALETTE: [u8; 24] = [
160, 34, 27, 130, 161, 35, 33, 136, 162, 36, 69, 142, 163, 37, 75, 148, 164, 38, 81,
154, 165, 39, 87, 190,
];
const COLOR_PLAIN: u8 = 244;
let total_bits = buffer.len() * 8;
let bits_per_line = bytes_per_line * 8;
use std::collections::HashMap;
let mut label_colors: HashMap<&str, u8> = HashMap::new();
let mut color_idx = 0usize;
for bucket in &self.buckets {
for (_, label) in &bucket.annotations {
label_colors.entry(*label).or_insert_with(|| {
let c = PALETTE[color_idx % PALETTE.len()];
color_idx += 1;
c
});
}
}
let write_colored = |buf: &mut String,
i: usize,
ch: char,
annotations: &[(BitRange, &str)]|
-> std::fmt::Result {
let color = annotations
.iter()
.find(|(r, _)| r.contains(i))
.and_then(|(_, l)| label_colors.get(l))
.copied()
.unwrap_or(COLOR_PLAIN);
write!(buf, "\x1b[38;5;{}m{}\x1b[0m", color, ch)
};
let mut global_offset = 0usize;
for (idx, bucket) in self.buckets.iter().enumerate() {
let annotations = &bucket.annotations;
let bucket_start = annotations
.iter()
.map(|(r, _)| r.start)
.min()
.unwrap_or(global_offset);
let bucket_end = annotations
.iter()
.map(|(r, _)| r.end)
.max()
.unwrap_or(bucket_start);
if idx == 0 || bucket_start >= global_offset {
writeln!(out, " | {}", bucket.title)?;
}
let span_start = global_offset.min(bucket_start);
let span_end = bucket_end.max(span_start);
for chunk_start in (span_start..span_end).step_by(bits_per_line) {
let chunk_end = (chunk_start + bits_per_line).saturating_sub(1);
let byte_offset = chunk_start / 8;
let mut line = String::new();
for bit_idx in chunk_start..=chunk_end {
if bit_idx % 4 == 0 && bit_idx != chunk_start {
write!(line, " ")?;
}
if bit_idx < total_bits {
let bit = (buffer[bit_idx / 8] >> (7 - (bit_idx % 8))) & 1;
write_colored(
&mut line,
bit_idx,
if bit == 1 { '1' } else { '0' },
annotations,
)?;
} else if annotations.iter().any(|(r, _)| r.contains(bit_idx)) {
write_colored(&mut line, bit_idx, 'X', annotations)?;
} else {
write!(line, " ")?;
}
}
let mut label_str = String::new();
let active_annotations: Vec<_> = annotations
.iter()
.filter(|(r, _)| r.start <= chunk_end && r.end > chunk_start)
.collect();
if active_annotations.is_empty() {
write!(label_str, "-")?;
} else {
for (range, label) in active_annotations {
let color = label_colors[label];
let bit_width = range.end - range.start;
if bit_width > 64 {
write!(label_str, "\x1b[38;5;{}m{}\x1b[0m ", color, label)?;
continue;
}
let value_str = if range.end > total_bits {
"n/a".to_string()
} else {
let v = unsafe { unchecked_bit_range_be_read::<u128>(buffer, *range) };
v.to_string()
};
write!(
label_str,
"\x1b[38;5;{}m{}={}\x1b[0m ",
color, label, value_str
)?;
}
label_str.pop();
}
writeln!(
out,
"0x{:04X} {:03}–{:03} | {:<width$} | {}",
byte_offset,
chunk_start,
chunk_end,
line,
label_str,
width = bits_per_line + (bits_per_line / 4 - 1)
)?;
}
global_offset = span_end;
}
Ok(())
}
}
struct AnnotationBucket {
title: String,
annotations: Vec<(BitRange, &'static str)>,
}
fn offset_annotations(
annotations: Vec<(BitRange, &str)>,
bit_offset: usize,
) -> Vec<(BitRange, &str)> {
annotations
.iter()
.map(|(r, label)| (r.shift_bits(bit_offset), *label))
.collect()
}
#[cfg(test)]
mod test {
use crate::{
core::layout::Layout, header::layout::ScionHeaderLayout,
path::standard::layout::StdPathDataLayout,
};
#[test]
#[ignore = "can only be inspected manually"]
fn it_works() {
let layout = ScionHeaderLayout::from_parts(4, 8, StdPathDataLayout::new(1, 2, 2).into(), 0);
let annotations = layout.annotations();
let mut buf = vec![0u8; layout.size_bytes() - 3];
for (i, byte) in buf.iter_mut().enumerate() {
*byte = (i * 37 + 13) as u8;
}
let mut out = String::new();
annotations.fmt_on_buffer(&mut out, &buf, 4).unwrap();
println!("{}", out);
}
}