1use crate::consts::{RESERVED_NOT_EMITTED_V01, TAG_ENTR, VALID_STR_LENGTHS};
4use crate::envelope;
5use crate::error::{Error, Result};
6use crate::payload::Payload;
7use crate::tag::Tag;
8use codex32::Codex32String;
9
10pub fn decode(s: &str) -> Result<(Tag, Payload)> {
20 if !VALID_STR_LENGTHS.contains(&s.len()) {
22 return Err(Error::UnexpectedStringLength {
23 got: s.len(),
24 allowed: VALID_STR_LENGTHS,
25 });
26 }
27
28 let c = Codex32String::from_string(s.to_string())?;
31
32 let (tag, payload_bytes) = envelope::discriminate(&c)?;
34
35 if RESERVED_NOT_EMITTED_V01.contains(tag.as_bytes()) {
37 return Err(Error::ReservedTagNotEmittedInV01 {
38 got: *tag.as_bytes(),
39 });
40 }
41
42 use zeroize::Zeroizing;
48 let payload = match *tag.as_bytes() {
49 x if x == TAG_ENTR => {
50 let scrubbed: Zeroizing<Vec<u8>> = Zeroizing::new(payload_bytes);
51 let p = Payload::Entr((*scrubbed).clone());
52 p.validate()?;
54 p
55 }
56 _ => {
57 return Err(Error::UnknownTag {
58 got: *tag.as_bytes(),
59 });
60 }
61 };
62
63 Ok((tag, payload))
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::encode;
70
71 #[test]
72 fn round_trip_entr_all_lengths() {
73 for len in [16usize, 20, 24, 28, 32] {
74 let entropy = (0..len as u8)
75 .map(|i| i.wrapping_mul(7))
76 .collect::<Vec<_>>();
77 let p = Payload::Entr(entropy.clone());
78 let s = encode::encode(Tag::ENTR, &p).unwrap();
79 let (tag, recovered) = decode(&s).unwrap();
80 assert_eq!(tag, Tag::ENTR);
81 assert_eq!(recovered, p);
82 }
83 }
84
85 #[test]
86 fn decode_rejects_unexpected_length() {
87 let s = "ms10entrsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
89 assert!(matches!(
90 decode(s),
91 Err(Error::UnexpectedStringLength { .. })
92 ));
93 }
94
95 #[test]
96 fn decode_rejects_short_seed_string_with_reserved_tag() {
97 let mut data = vec![0x00u8];
100 data.extend_from_slice(&[0xAAu8; 16]);
101 let c = Codex32String::from_seed("ms", 0, "seed", codex32::Fe::S, &data).unwrap();
102 let s = c.to_string();
103 assert_eq!(s.len(), 50, "expected str.len 50 for 16-B + prefix");
104 assert!(matches!(
105 decode(&s),
106 Err(Error::ReservedTagNotEmittedInV01 { .. })
107 ));
108 }
109}