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 = envelope::wire_string(&c);
70 let fields = envelope::extract_wire_fields(&s_owned)?;
71
72 let tag = match std::str::from_utf8(&fields.id_bytes) {
75 Ok(t) => Tag::try_new(t).unwrap_or_else(|_| Tag::from_raw_bytes(fields.id_bytes)),
76 Err(_) => Tag::from_raw_bytes(fields.id_bytes),
77 };
78
79 let payload_with_prefix = c.parts().data();
80 let (prefix_byte, payload_bytes) = if payload_with_prefix.is_empty() {
81 (0u8, Vec::new())
82 } else {
83 (payload_with_prefix[0], payload_with_prefix[1..].to_vec())
84 };
85
86 let (kind, language) = match prefix_byte {
88 0x00 => (InspectKind::Entr, None),
89 MNEM_PREFIX => {
90 let lang = payload_bytes.first().copied();
92 (InspectKind::Mnem, lang)
93 }
94 _ => (InspectKind::Unknown, None),
95 };
96
97 Ok(InspectReport {
98 hrp: fields.hrp.to_string(),
99 threshold: fields.threshold_byte - b'0', tag,
101 share_index: fields.share_index_byte as char,
102 prefix_byte,
103 payload_bytes,
104 checksum_valid: true, kind,
106 language,
107 })
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::{encode, payload::Payload};
114
115 #[test]
116 fn inspect_v01_entr_returns_expected_fields() {
117 let entropy = vec![0xAAu8; 16];
118 let s = encode::encode(Tag::ENTR, &Payload::Entr(entropy.clone())).unwrap();
119 let r = inspect(&s).unwrap();
120 assert_eq!(r.hrp, "ms");
121 assert_eq!(r.threshold, 0);
122 assert_eq!(r.tag, Tag::ENTR);
123 assert_eq!(r.share_index, 's');
124 assert_eq!(r.prefix_byte, 0x00);
125 assert_eq!(r.payload_bytes, entropy);
126 assert!(r.checksum_valid);
127 }
128
129 #[test]
130 fn inspect_returns_report_for_decoder_rejects() {
131 let mut data = vec![0x01u8];
133 data.extend_from_slice(&[0xAAu8; 16]);
134 let c = Codex32String::from_seed("ms", 0, "entr", codex32::Fe::S, &data).unwrap();
135 let r = inspect(&c.to_string()).unwrap();
136 assert_eq!(r.prefix_byte, 0x01); }
138}