use crate::codex32::Codex32String;
use crate::consts::MNEM_PREFIX;
use crate::envelope;
use crate::error::Result;
use crate::tag::Tag;
use std::fmt;
use zeroize::Zeroizing;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InspectKind {
Entr,
Mnem,
Unknown,
}
impl InspectKind {
pub fn as_str(self) -> &'static str {
match self {
InspectKind::Entr => "entr",
InspectKind::Mnem => "mnem",
InspectKind::Unknown => "unknown",
}
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct InspectReport {
pub hrp: String,
pub threshold: u8,
pub tag: Tag,
pub share_index: char,
pub prefix_byte: u8,
pub payload_bytes: Zeroizing<Vec<u8>>,
pub checksum_valid: bool,
pub kind: InspectKind,
pub language: Option<u8>,
}
impl fmt::Debug for InspectReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InspectReport")
.field("hrp", &self.hrp)
.field("threshold", &self.threshold)
.field("tag", &self.tag)
.field("share_index", &self.share_index)
.field("prefix_byte", &self.prefix_byte)
.field(
"payload_bytes",
&format_args!("[REDACTED; {} bytes]", self.payload_bytes.len()),
)
.field("checksum_valid", &self.checksum_valid)
.field("kind", &self.kind)
.field("language", &self.language)
.finish()
}
}
pub fn inspect(s: &str) -> Result<InspectReport> {
let c = Codex32String::from_string(s.to_string())?;
let s_owned = envelope::wire_string(&c);
let fields = envelope::extract_wire_fields(&s_owned)?;
let tag = match std::str::from_utf8(&fields.id_bytes) {
Ok(t) => Tag::try_new(t).unwrap_or_else(|_| Tag::from_raw_bytes(fields.id_bytes)),
Err(_) => Tag::from_raw_bytes(fields.id_bytes),
};
let payload_with_prefix = c.parts().data();
let (prefix_byte, payload_bytes) = if payload_with_prefix.is_empty() {
(0u8, Vec::new())
} else {
(payload_with_prefix[0], payload_with_prefix[1..].to_vec())
};
let (kind, language) = match prefix_byte {
0x00 => (InspectKind::Entr, None),
MNEM_PREFIX => {
let lang = payload_bytes.first().copied();
(InspectKind::Mnem, lang)
}
_ => (InspectKind::Unknown, None),
};
Ok(InspectReport {
hrp: fields.hrp.to_string(),
threshold: fields.threshold_byte - b'0', tag,
share_index: fields.share_index_byte as char,
prefix_byte,
payload_bytes: Zeroizing::new(payload_bytes),
checksum_valid: true, kind,
language,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{encode, payload::Payload};
#[test]
fn inspect_v01_entr_returns_expected_fields() {
let entropy = vec![0xAAu8; 16];
let s = encode::encode(Tag::ENTR, &Payload::Entr(entropy.clone())).unwrap();
let r = inspect(&s).unwrap();
assert_eq!(r.hrp, "ms");
assert_eq!(r.threshold, 0);
assert_eq!(r.tag, Tag::ENTR);
assert_eq!(r.share_index, 's');
assert_eq!(r.prefix_byte, 0x00);
assert_eq!(*r.payload_bytes, entropy);
assert!(r.checksum_valid);
}
#[test]
fn inspect_returns_report_for_decoder_rejects() {
let mut data = vec![0x01u8];
data.extend_from_slice(&[0xAAu8; 16]);
let c = Codex32String::from_seed("ms", 0, "entr", crate::codex32::Fe::S, &data).unwrap();
let r = inspect(&c.to_string()).unwrap();
assert_eq!(r.prefix_byte, 0x01); }
}