1use crate::consts::MNEM_PREFIX;
4use crate::envelope;
5use crate::error::Result;
6use crate::tag::Tag;
7use codex32::Codex32String;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum InspectKind {
12 Entr,
14 Mnem,
16 Unknown,
18}
19
20impl InspectKind {
21 pub fn as_str(self) -> &'static str {
23 match self {
24 InspectKind::Entr => "entr",
25 InspectKind::Mnem => "mnem",
26 InspectKind::Unknown => "unknown",
27 }
28 }
29}
30
31#[derive(Debug, Clone)]
35#[non_exhaustive]
36pub struct InspectReport {
37 pub hrp: String,
39 pub threshold: u8,
41 pub tag: Tag,
43 pub share_index: char,
45 pub prefix_byte: u8,
47 pub payload_bytes: Vec<u8>,
49 pub checksum_valid: bool,
51 pub kind: InspectKind,
53 pub language: Option<u8>,
56}
57
58pub fn inspect(s: &str) -> Result<InspectReport> {
63 let c = Codex32String::from_string(s.to_string())?;
65 let s_owned = c.to_string();
66 let fields = envelope::extract_wire_fields(&s_owned)?;
67
68 let tag = match std::str::from_utf8(&fields.id_bytes) {
71 Ok(t) => Tag::try_new(t).unwrap_or_else(|_| Tag::from_raw_bytes(fields.id_bytes)),
72 Err(_) => Tag::from_raw_bytes(fields.id_bytes),
73 };
74
75 let payload_with_prefix = c.parts().data();
76 let (prefix_byte, payload_bytes) = if payload_with_prefix.is_empty() {
77 (0u8, Vec::new())
78 } else {
79 (payload_with_prefix[0], payload_with_prefix[1..].to_vec())
80 };
81
82 let (kind, language) = match prefix_byte {
84 0x00 => (InspectKind::Entr, None),
85 MNEM_PREFIX => {
86 let lang = payload_bytes.first().copied();
88 (InspectKind::Mnem, lang)
89 }
90 _ => (InspectKind::Unknown, None),
91 };
92
93 Ok(InspectReport {
94 hrp: fields.hrp.to_string(),
95 threshold: fields.threshold_byte - b'0', tag,
97 share_index: fields.share_index_byte as char,
98 prefix_byte,
99 payload_bytes,
100 checksum_valid: true, kind,
102 language,
103 })
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::{encode, payload::Payload};
110
111 #[test]
112 fn inspect_v01_entr_returns_expected_fields() {
113 let entropy = vec![0xAAu8; 16];
114 let s = encode::encode(Tag::ENTR, &Payload::Entr(entropy.clone())).unwrap();
115 let r = inspect(&s).unwrap();
116 assert_eq!(r.hrp, "ms");
117 assert_eq!(r.threshold, 0);
118 assert_eq!(r.tag, Tag::ENTR);
119 assert_eq!(r.share_index, 's');
120 assert_eq!(r.prefix_byte, 0x00);
121 assert_eq!(r.payload_bytes, entropy);
122 assert!(r.checksum_valid);
123 }
124
125 #[test]
126 fn inspect_returns_report_for_decoder_rejects() {
127 let mut data = vec![0x01u8];
129 data.extend_from_slice(&[0xAAu8; 16]);
130 let c = Codex32String::from_seed("ms", 0, "entr", codex32::Fe::S, &data).unwrap();
131 let r = inspect(&c.to_string()).unwrap();
132 assert_eq!(r.prefix_byte, 0x01); }
134}