1use crate::blocks::{Block, BlockTag};
28use crate::error::{MeshError, Result};
29
30pub const TAG_CHAR_BASE: u32 = 0xE0061;
32pub const PLANE15_BASE: u32 = 0xF0000;
34pub const PLANE16_BASE: u32 = 0x100000;
36pub const SUBRANGE_SIZE: u32 = 4096;
38pub const MAX_PAYLOAD: usize = 0xFF_FFFF;
40
41#[must_use]
43pub fn tag_char(tag: BlockTag) -> char {
44 char::from_u32(TAG_CHAR_BASE + tag.index() as u32).unwrap_or('\u{E0061}')
46}
47
48#[must_use]
50pub fn tag_from_char(c: char) -> Option<BlockTag> {
51 let v = c as u32;
52 let idx = v.checked_sub(TAG_CHAR_BASE)?;
53 BlockTag::from_index(u8::try_from(idx).ok()?)
54}
55
56fn plane15_char(tag: BlockTag, chunk: u16) -> Result<char> {
57 if chunk >= SUBRANGE_SIZE as u16 {
58 return Err(MeshError::Unicode(format!("chunk {chunk} out of range")));
59 }
60 let v = PLANE15_BASE + tag.index() as u32 * SUBRANGE_SIZE + chunk as u32;
61 char::from_u32(v).ok_or_else(|| MeshError::Unicode(format!("invalid char U+{v:X}")))
62}
63
64fn plane15_value(c: char, tag: BlockTag) -> Result<u16> {
65 let v = c as u32;
66 let base = PLANE15_BASE + tag.index() as u32 * SUBRANGE_SIZE;
67 if !(base..base + SUBRANGE_SIZE).contains(&v) {
68 return Err(MeshError::Unicode(format!(
69 "expected plane-15 length char for {:?}, got U+{:X}",
70 tag, v
71 )));
72 }
73 Ok((v - base) as u16)
74}
75
76fn plane16_char(value: u16) -> Result<char> {
77 let v = PLANE16_BASE + value as u32;
78 char::from_u32(v).ok_or_else(|| MeshError::Unicode(format!("invalid char U+{v:X}")))
79}
80
81fn plane16_value(c: char) -> Result<u16> {
82 let v = c as u32;
83 if !(PLANE16_BASE..=PLANE16_BASE + 0xFFFF).contains(&v) {
84 return Err(MeshError::Unicode(format!(
85 "expected plane-16 data char, got U+{v:X}"
86 )));
87 }
88 Ok((v - PLANE16_BASE) as u16)
89}
90
91pub fn encode_blocks(blocks: &[Block]) -> Result<String> {
93 let mut out = String::new();
94 for block in blocks {
95 let tag = block.tag();
96 let payload = block.payload()?;
97 if payload.len() > MAX_PAYLOAD {
98 return Err(MeshError::Unicode(format!(
99 "payload of {} bytes exceeds the 24-bit limit",
100 payload.len()
101 )));
102 }
103 out.push(tag_char(tag));
104 let len = payload.len() as u32;
105 out.push(plane15_char(tag, ((len >> 12) & 0xFFF) as u16)?);
106 out.push(plane15_char(tag, (len & 0xFFF) as u16)?);
107 let mut chunks = payload.chunks_exact(2);
108 for pair in &mut chunks {
109 out.push(plane16_char(u16::from_be_bytes([pair[0], pair[1]]))?);
110 }
111 let rem = chunks.remainder();
112 if let [last] = rem {
113 out.push(plane16_char(u16::from_be_bytes([*last, 0]))?);
114 }
115 }
116 Ok(out)
117}
118
119pub fn decode_blocks(s: &str) -> Result<Vec<Block>> {
124 let mut blocks = Vec::new();
125 let mut chars = s.chars();
126 while let Some(c) = chars.next() {
127 let Some(tag) = tag_from_char(c) else { continue };
128 let hi = next_char(&mut chars).and_then(|c| plane15_value(c, tag))?;
129 let lo = next_char(&mut chars).and_then(|c| plane15_value(c, tag))?;
130 let len = ((hi as usize) << 12) | lo as usize;
131 let count = len.div_ceil(2);
132 let mut payload = Vec::with_capacity(count * 2);
133 for _ in 0..count {
134 let value = next_char(&mut chars).and_then(plane16_value)?;
135 payload.extend_from_slice(&value.to_be_bytes());
136 }
137 payload.truncate(len);
138 blocks.push(Block::from_payload(tag, &payload)?);
139 }
140 Ok(blocks)
141}
142
143fn next_char(chars: &mut std::str::Chars<'_>) -> Result<char> {
144 chars
145 .next()
146 .ok_or_else(|| MeshError::Unicode("truncated envelope".into()))
147}