vintage_schematics/
ascii85.rs1#[derive(Debug, Copy, Clone, PartialEq, Eq)]
14pub struct DecodeError {
15 pub position: usize,
17
18 pub kind: ErrorKind,
20}
21
22impl std::fmt::Display for DecodeError {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "ascii85 decode error at position {}: {}", self.position, self.kind)
25 }
26}
27
28impl std::error::Error for DecodeError {}
29
30#[derive(Debug, Copy, Clone, PartialEq, Eq)]
32pub enum ErrorKind {
33 InvalidCharacter(char),
35
36 Overflow { char: char, encoded_count: usize },
38
39 BadYPosition,
41
42 BadZPosition,
44}
45
46impl std::fmt::Display for ErrorKind {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 Self::InvalidCharacter(char) => write!(f, "invalid character: {char}"),
50 Self::Overflow { char, encoded_count } => {
51 write!(f, "malformed input: character '{char}' at chunk position {encoded_count} would overflow",)
52 }
53 Self::BadYPosition => write!(f, "encountered 'y' at invalid position"),
54 Self::BadZPosition => write!(f, "encountered 'z' at invalid position"),
55 }
56 }
57}
58
59const POWS: [u32; 5] = [85u32.pow(4), 85u32.pow(3), 85u32.pow(2), 85u32.pow(1), 85u32.pow(0)];
72
73#[allow(clippy::missing_panics_doc)]
80pub fn decode(input: &str) -> Result<Vec<u8>, DecodeError> {
81 let mut encoded_count = 0u32;
102 let mut decoded_chunk = 0u32;
103
104 let mut decoded = Vec::with_capacity(((input.len() / 5) * 4) + 5);
106
107 for (position, char) in input.chars().filter(|c| !c.is_ascii_whitespace()).enumerate() {
108 let decode_err = |kind| DecodeError { position, kind };
109
110 match char {
111 'z' if encoded_count == 0 => decoded.extend_from_slice(&[0u8; 4]),
113 'y' if encoded_count == 0 => decoded.extend_from_slice(&[b' '; 4]),
114 'z' => {
115 return Err(decode_err(ErrorKind::BadZPosition));
116 }
117 'y' => {
118 return Err(decode_err(ErrorKind::BadYPosition));
119 }
120
121 _ => decode_character(char, &mut encoded_count, &mut decoded_chunk, &mut decoded).map_err(decode_err)?,
123 }
124 }
125
126 let mut padding = 0;
129 while encoded_count != 0 {
130 decode_character('u', &mut encoded_count, &mut decoded_chunk, &mut decoded).map_err(|kind| DecodeError {
131 position: input.len() + padding,
132 kind,
133 })?;
134 padding += 1;
135 }
136
137 decoded.drain((decoded.len() - padding)..decoded.len());
139
140 Ok(decoded)
141}
142
143fn decode_character(
144 char: char,
145 encoded_count: &mut u32,
146 decoded_chunk: &mut u32,
147 decoded: &mut Vec<u8>,
148) -> Result<(), ErrorKind> {
149 if !('!'..='u').contains(&char) {
150 return Err(ErrorKind::InvalidCharacter(char));
151 }
152 let value = (char as u8) - 33;
153
154 let overflow_err = || ErrorKind::Overflow {
157 char,
158 encoded_count: *encoded_count as usize,
159 };
160
161 let value = u32::from(value).checked_mul(POWS[*encoded_count as usize]).ok_or_else(overflow_err)?;
162 *decoded_chunk = decoded_chunk.checked_add(value).ok_or_else(overflow_err)?;
163
164 *encoded_count += 1;
165 if (*encoded_count) == 5 {
166 decoded.extend_from_slice(&decoded_chunk.to_be_bytes());
168 *encoded_count = 0;
169 *decoded_chunk = 0;
170 }
171
172 Ok(())
173}
174
175#[must_use]
180#[allow(clippy::cast_possible_truncation, clippy::missing_panics_doc)]
181pub fn encode(input: &[u8], encode_y: bool) -> String {
182 let mut out = String::with_capacity((input.len() / 4) * 5);
183 for chunk in input.chunks(4) {
184 if chunk == [0u8; 4] {
185 out.push('z');
186 continue;
187 } else if encode_y && chunk == [b' '; 4] {
188 out.push('y');
189 continue;
190 }
191
192 let (chunk, encoded_count) = if chunk.len() < 4 {
193 let mut chunk = chunk.to_vec();
194 let len = 4 - chunk.len();
195 chunk.extend(std::iter::repeat_n(0u8, len));
196
197 (u32::from_be_bytes(chunk.try_into().expect("chunk length should be 4")), 5 - len)
198 } else {
199 (u32::from_be_bytes(chunk.try_into().expect("chunk length should be 4")), 5)
200 };
201
202 for pow in POWS.iter().take(encoded_count) {
204 let encoded = ((((chunk / pow) % 85) + 33) as u8) as char;
205 out.push(encoded);
206 }
207 }
208
209 out
210}