1use serde::{Deserialize, Serialize};
14
15pub const ESC: u8 = 0x1B;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
21#[serde(rename_all = "lowercase")]
22pub enum Language {
23 #[default]
24 Default,
25 Turkish,
26 Spanish,
27 Portuguese,
28}
29
30impl Language {
31 pub fn from_nli(nli: u8) -> Option<Self> {
34 match nli {
35 0 => Some(Self::Default),
36 1 => Some(Self::Turkish),
37 2 => Some(Self::Spanish),
38 3 => Some(Self::Portuguese),
39 _ => None,
40 }
41 }
42
43 pub fn nli(self) -> u8 {
44 match self {
45 Self::Default => 0,
46 Self::Turkish => 1,
47 Self::Spanish => 2,
48 Self::Portuguese => 3,
49 }
50 }
51
52 pub fn has_locking_table(self) -> bool {
54 !matches!(self, Self::Spanish)
55 }
56
57 fn locking(self) -> &'static [char; 128] {
58 match self {
59 Self::Turkish => &TURKISH,
60 Self::Portuguese => &PORTUGUESE,
61 Self::Default | Self::Spanish => &DEFAULT,
62 }
63 }
64
65 fn single(self) -> &'static [(u8, char)] {
66 match self {
67 Self::Default => DEFAULT_EXT,
68 Self::Turkish => TURKISH_EXT,
69 Self::Spanish => SPANISH_EXT,
70 Self::Portuguese => PORTUGUESE_EXT,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
77pub struct Tables {
78 pub locking: Language,
79 pub single: Language,
80}
81
82impl Tables {
83 pub const DEFAULT: Tables = Tables {
84 locking: Language::Default,
85 single: Language::Default,
86 };
87
88 pub fn new(locking: Language, single: Language) -> Self {
89 Tables { locking, single }
90 }
91
92 pub fn udh_ie_len(self) -> usize {
94 3 * (usize::from(self.locking != Language::Default)
95 + usize::from(self.single != Language::Default))
96 }
97}
98
99const DEFAULT: [char; 128] = [
102 '@', '£', '$', '¥', 'è', 'é', 'ù', 'ì', 'ò', 'Ç', '\n', 'Ø', 'ø', '\r', 'Å', 'å', 'Δ', '_', 'Φ', 'Γ', 'Λ', 'Ω', 'Π', 'Ψ', 'Σ', 'Θ', 'Ξ', '\u{A0}', 'Æ', 'æ', 'ß', 'É', ' ', '!', '"', '#', '¤', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '¡', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ä', 'Ö', 'Ñ', 'Ü', '§', '¿', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'ä', 'ö', 'ñ', 'ü', 'à', ];
111
112const TURKISH: [char; 128] = [
114 '@', '£', '$', '¥', '€', 'é', 'ù', 'ı', 'ò', 'Ç', '\n', 'Ğ', 'ğ', '\r', 'Å', 'å', 'Δ', '_', 'Φ', 'Γ', 'Λ', 'Ω', 'Π', 'Ψ', 'Σ', 'Θ', 'Ξ', '\u{A0}', 'Ş', 'ş', 'ß', 'É', ' ', '!', '"', '#', '¤', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', 'İ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ä', 'Ö', 'Ñ', 'Ü', '§', 'ç', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'ä', 'ö', 'ñ', 'ü', 'à', ];
123
124const PORTUGUESE: [char; 128] = [
126 '@', '£', '$', '¥', 'ê', 'é', 'ú', 'í', 'ó', 'ç', '\n', 'Ô', 'ô', '\r', 'Á', 'á', 'Δ', '_', 'ª', 'Ç', 'À', '∞', '^', '\\', '€', 'Ó', '|', '\u{A0}', 'Â', 'â', 'Ê', 'É', ' ', '!', '"', '#', 'º', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', 'Í', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ã', 'Õ', 'Ú', 'Ü', '§', '~', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'ã', 'õ', '`', 'ü', 'à', ];
135
136const DEFAULT_EXT: &[(u8, char)] = &[
138 (0x0A, '\u{C}'),
139 (0x14, '^'),
140 (0x28, '{'),
141 (0x29, '}'),
142 (0x2F, '\\'),
143 (0x3C, '['),
144 (0x3D, '~'),
145 (0x3E, ']'),
146 (0x40, '|'),
147 (0x65, '€'),
148];
149
150const TURKISH_EXT: &[(u8, char)] = &[
152 (0x0A, '\u{C}'),
153 (0x14, '^'),
154 (0x28, '{'),
155 (0x29, '}'),
156 (0x2F, '\\'),
157 (0x3C, '['),
158 (0x3D, '~'),
159 (0x3E, ']'),
160 (0x40, '|'),
161 (0x47, 'Ğ'),
162 (0x49, 'İ'),
163 (0x53, 'Ş'),
164 (0x63, 'ç'),
165 (0x65, '€'),
166 (0x67, 'ğ'),
167 (0x69, 'ı'),
168 (0x73, 'ş'),
169];
170
171const SPANISH_EXT: &[(u8, char)] = &[
173 (0x09, 'ç'),
174 (0x0A, '\u{C}'),
175 (0x14, '^'),
176 (0x28, '{'),
177 (0x29, '}'),
178 (0x2F, '\\'),
179 (0x3C, '['),
180 (0x3D, '~'),
181 (0x3E, ']'),
182 (0x40, '|'),
183 (0x41, 'Á'),
184 (0x49, 'Í'),
185 (0x4F, 'Ó'),
186 (0x55, 'Ú'),
187 (0x61, 'á'),
188 (0x65, '€'),
189 (0x69, 'í'),
190 (0x6F, 'ó'),
191 (0x75, 'ú'),
192];
193
194const PORTUGUESE_EXT: &[(u8, char)] = &[
196 (0x05, 'ê'),
197 (0x09, 'ç'),
198 (0x0A, '\u{C}'),
199 (0x0B, 'Ô'),
200 (0x0C, 'ô'),
201 (0x0E, 'Á'),
202 (0x0F, 'á'),
203 (0x12, 'Φ'),
204 (0x13, 'Γ'),
205 (0x14, '^'),
206 (0x15, 'Ω'),
207 (0x16, 'Π'),
208 (0x17, 'Ψ'),
209 (0x18, 'Σ'),
210 (0x19, 'Θ'),
211 (0x1F, 'Ê'),
212 (0x28, '{'),
213 (0x29, '}'),
214 (0x2F, '\\'),
215 (0x3C, '['),
216 (0x3D, '~'),
217 (0x3E, ']'),
218 (0x40, '|'),
219 (0x41, 'À'),
220 (0x49, 'Í'),
221 (0x4F, 'Ó'),
222 (0x55, 'Ú'),
223 (0x5B, 'Ã'),
224 (0x5C, 'Õ'),
225 (0x61, 'Â'),
226 (0x65, '€'),
227 (0x69, 'í'),
228 (0x6F, 'ó'),
229 (0x75, 'ú'),
230 (0x7B, 'ã'),
231 (0x7C, 'õ'),
232 (0x7F, 'â'),
233];
234
235pub fn encode_char(c: char, tables: Tables) -> Option<([u8; 2], usize)> {
238 if let Some(i) = tables
239 .locking
240 .locking()
241 .iter()
242 .position(|&t| t == c)
243 .filter(|&i| i != usize::from(ESC))
244 {
245 return Some(([i as u8, 0], 1));
246 }
247 tables
248 .single
249 .single()
250 .iter()
251 .find(|(_, t)| *t == c)
252 .map(|&(code, _)| ([ESC, code], 2))
253}
254
255pub fn encode(text: &str, tables: Tables) -> Result<Vec<u8>, char> {
257 let mut out = Vec::with_capacity(text.len());
258 for c in text.chars() {
259 let (s, n) = encode_char(c, tables).ok_or(c)?;
260 out.extend_from_slice(&s[..n]);
261 }
262 Ok(out)
263}
264
265pub fn septet_len(text: &str, tables: Tables) -> Option<usize> {
267 text.chars()
268 .map(|c| encode_char(c, tables).map(|(_, n)| n))
269 .sum()
270}
271
272pub fn is_encodable(text: &str, tables: Tables) -> bool {
273 septet_len(text, tables).is_some()
274}
275
276pub fn choose_tables(text: &str, locking: &[Language], single: &[Language]) -> Option<Tables> {
281 if is_encodable(text, Tables::DEFAULT) {
282 return Some(Tables::DEFAULT);
283 }
284 let with_default = |l: &[Language]| {
285 let mut v = vec![Language::Default];
286 v.extend(l.iter().copied().filter(|l| *l != Language::Default));
287 v
288 };
289 let (locking, single) = (with_default(locking), with_default(single));
290 let mut best: Option<(usize, Tables)> = None;
291 for &l in locking.iter().filter(|l| l.has_locking_table()) {
292 for &s in &single {
293 let t = Tables::new(l, s);
294 if let Some(n) = septet_len(text, t) {
295 let cost = n + (t.udh_ie_len() * 8).div_ceil(7);
296 if best.is_none_or(|(c, _)| cost < c) {
297 best = Some((cost, t));
298 }
299 }
300 }
301 }
302 best.map(|(_, t)| t)
303}
304
305pub fn decode(septets: &[u8], tables: Tables) -> String {
309 let locking = tables.locking.locking();
310 let single = tables.single.single();
311 let mut out = String::with_capacity(septets.len());
312 let mut it = septets.iter().map(|b| b & 0x7F);
313 while let Some(s) = it.next() {
314 if s != ESC {
315 out.push(locking[usize::from(s)]);
316 continue;
317 }
318 match it.next() {
319 None | Some(ESC) => out.push(' '),
320 Some(n) => out.push(
321 single
322 .iter()
323 .find(|(code, _)| *code == n)
324 .map_or(locking[usize::from(n)], |&(_, c)| c),
325 ),
326 }
327 }
328 out
329}
330
331pub fn pack(septets: &[u8], fill_bits: u8) -> Vec<u8> {
334 let fill = usize::from(fill_bits % 8);
335 let total_bits = fill + septets.len() * 7;
336 let mut out = vec![0u8; total_bits.div_ceil(8)];
337 for (i, &s) in septets.iter().enumerate() {
338 let bit = fill + i * 7;
339 let v = u16::from(s & 0x7F) << (bit % 8);
340 out[bit / 8] |= v as u8;
341 if v > 0xFF {
342 out[bit / 8 + 1] |= (v >> 8) as u8;
343 }
344 }
345 out
346}
347
348pub fn unpack(octets: &[u8], fill_bits: u8, count: usize) -> Vec<u8> {
351 let fill = usize::from(fill_bits % 8);
352 let avail = (octets.len() * 8).saturating_sub(fill) / 7;
353 (0..count.min(avail))
354 .map(|i| {
355 let bit = fill + i * 7;
356 let lo = u16::from(octets[bit / 8]);
357 let hi = octets.get(bit / 8 + 1).copied().map_or(0, u16::from);
358 (((hi << 8 | lo) >> (bit % 8)) & 0x7F) as u8
359 })
360 .collect()
361}
362
363pub fn septets_in(octets: usize, fill_bits: u8) -> usize {
365 (octets * 8).saturating_sub(usize::from(fill_bits % 8)) / 7
366}