1#![doc = include_str!("../README.md")]
2
3mod encode;
4mod decode;
5#[cfg(feature = "with-struct")]
6mod struct_api;
7
8pub use encode::{encode, encode_unchecked};
9pub use decode::{decode, decode_unchecked};
10#[cfg(feature = "with-struct")]
11pub use struct_api::DecSixbit;
12
13const MASK_TWO_BITS: u8 = 0b11;
14const MASK_FOUR_BITS: u8 = 0b1111;
15const MASK_SIX_BITS: u8 = 0b111111;
16const SHIFT_TWO_BITS: u8 = 2;
17const SHIFT_FOUR_BITS: u8 = 4;
18const SHIFT_SIX_BITS: u8 = 6;
19const ASCII_OFFSET: u8 = 32;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
23pub enum Error {
24 #[error("invalid character in input (must be ASCII 32-95)")]
26 InvalidCharacter,
27
28 #[error("input bytes and length are inconsistent")]
30 InvalidBytesLength,
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[cfg(feature = "with-struct")]
38 use super::DecSixbit;
39
40 #[test]
41 fn test_packed_storage() {
42 let input = "ABCD"; let a = 65 - 32; let b = 66 - 32; let c = 67 - 32; let d = 68 - 32; use std::io::Write;
51 let stderr = std::io::stderr();
52 let mut handle = stderr.lock();
53
54 writeln!(handle, "Input SIXBIT values:").unwrap();
55 writeln!(handle, " A: dec={} bin={:06b}", a, a).unwrap();
56 writeln!(handle, " B: dec={} bin={:06b}", b, b).unwrap();
57 writeln!(handle, " C: dec={} bin={:06b}", c, c).unwrap();
58 writeln!(handle, " D: dec={} bin={:06b}", d, d).unwrap();
59
60 let expected = vec![0b10000110, 0b00101000, 0b11100100];
62 writeln!(handle, "\nExpected packed bytes:").unwrap();
63 writeln!(handle, " byte 1 = {:08b} = (A<<2 | B>>4)", expected[0]).unwrap();
64 writeln!(handle, " byte 2 = {:08b} = (B<<4 | C>>2)", expected[1]).unwrap();
65 writeln!(handle, " byte 3 = {:08b} = (C<<6 | D)", expected[2]).unwrap();
66
67 #[cfg(feature = "with-struct")]
69 {
70 let sixbit = DecSixbit::new(input).unwrap();
71 writeln!(handle, "\nActual packed bytes: ").unwrap();
72 writeln!(handle, " byte 1 = {:08b}", sixbit.bytes[0]).unwrap();
73 writeln!(handle, " byte 2 = {:08b}", sixbit.bytes[1]).unwrap();
74 writeln!(handle, " byte 3 = {:08b}", sixbit.bytes[2]).unwrap();
75
76 assert_eq!(sixbit.bytes, expected);
77 assert_eq!(sixbit.len, 4);
78 }
79 }
80
81 #[test]
82 fn test_partial_packing() {
83 let inputs = ["A", "AB", "ABC"];
84 let expected_bytes = [
85 vec![0b10000100], vec![0b10000110, 0b00100000], vec![0b10000110, 0b00101000, 0b11000000], ];
89
90 for (input, expected) in inputs.iter().zip(expected_bytes.iter()) {
91 let (bytes, len) = encode(input).unwrap();
92 println!("\nTesting input: '{}' (length {})", input, input.len());
93
94 let values: Vec<u8> = input.bytes().map(|b| b - 32).collect();
96 print!("SIXBIT values: ");
97 for b in &values {
98 print!("{:02}={:06b} ", b, b);
99 }
100 println!("\nExpected bytes:");
101 for (i, &b) in expected.iter().enumerate() {
102 println!(" byte {} = {:08b} ({})", i + 1, b, b);
103 }
104
105 println!("Got bytes:");
107 for (i, &b) in bytes.iter().enumerate() {
108 println!(" byte {} = {:08b} ({})", i + 1, b, b);
109 }
110
111 assert_eq!(bytes, *expected, "Mismatch in encoded bytes for input '{}'", input);
113 assert_eq!(len, input.len(), "Mismatch in length for input '{}'", input);
114 }
115 }
116
117 #[test]
118 fn test_encoding_decoding() {
119 let inputs = [
120 "HELLO WORLD",
121 "TEST 123",
122 " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_",
123 ];
124
125 for input in inputs {
126 let (bytes, len) = encode(input).unwrap();
127 assert_eq!(len, input.len());
128
129 let decoded = decode(&bytes, len).unwrap();
130 assert_eq!(decoded, input);
131
132 let decoded_unchecked = decode_unchecked(&bytes, len);
133 assert_eq!(decoded_unchecked, input);
134
135 #[cfg(feature = "with-struct")]
136 {
137 let sixbit = DecSixbit::new(input).unwrap();
138 assert_eq!(sixbit.bytes, bytes);
139 assert_eq!(sixbit.len, len);
140 assert_eq!(sixbit.to_string(), decoded);
141 }
142 }
143 }
144
145 #[test]
146 fn test_invalid_characters() {
147 assert!(matches!(
149 encode("\x1F"),
150 Err(Error::InvalidCharacter)
151 ));
152
153 assert!(matches!(
155 encode("abc"),
156 Err(Error::InvalidCharacter)
157 ));
158
159 assert!(matches!(
161 encode("こんにちは"),
162 Err(Error::InvalidCharacter)
163 ));
164 }
165
166 #[test]
167 fn test_empty_string() {
168 let (bytes, len) = encode("").unwrap();
169 assert!(len == 0);
170 assert!(bytes.is_empty());
171
172 let decoded = decode(&bytes, len).unwrap();
173 assert_eq!(decoded, "");
174
175 let decoded_unchecked = decode_unchecked(&bytes, len);
176 assert_eq!(decoded_unchecked, "");
177
178 #[cfg(feature = "with-struct")]
179 {
180 let sixbit = DecSixbit::new("").unwrap();
181 assert!(sixbit.is_empty());
182 assert_eq!(sixbit.len, 0);
183 assert!(sixbit.as_bytes().is_empty());
184
185 let decoded = decode(&sixbit.bytes, sixbit.len).unwrap();
186 assert_eq!(decoded, "");
187
188 let decoded_unchecked = decode_unchecked(&sixbit.bytes, sixbit.len);
189 assert_eq!(decoded_unchecked, "");
190 }
191 }
192
193 #[cfg(feature = "with-struct")]
194 #[test]
195 fn test_default() {
196 {
197 let sixbit = DecSixbit::default();
198 assert!(sixbit.is_empty());
199 assert_eq!(sixbit.len, 0);
200 assert!(sixbit.as_bytes().is_empty());
201 }
202 }
203
204 #[cfg(feature = "with-struct")]
205 #[test]
206 fn test_as_ref() {
207 {
208 let input = "TEST";
209 let sixbit = DecSixbit::new(input).unwrap();
210 let bytes: &[u8] = sixbit.as_ref();
211 assert_eq!(bytes, sixbit.as_bytes());
212 }
213 }
214
215 #[cfg(feature = "with-struct")]
216 #[test]
217 fn test_try_from() {
218 {
219 let input = "TEST";
220 let sixbit = DecSixbit::try_from(input).unwrap();
221 assert_eq!(sixbit.to_string(), input);
222
223 assert!(DecSixbit::try_from("invalid❌").is_err());
224 }
225 }
226
227 #[cfg(feature = "with-struct")]
228 #[test]
229 fn test_from_str() {
230 {
231 let input = "TEST";
232 let sixbit: DecSixbit = input.parse().unwrap();
233 assert_eq!(sixbit.to_string(), input);
234
235 let result: Result<DecSixbit, _> = "invalid❌".parse();
236 assert!(result.is_err());
237 }
238 }
239
240 #[test]
241 fn test_decode_unchecked_integrity() {
242 let input = "OPTIMIZATION TEST";
243 let (bytes, len) = encode(input).unwrap();
244 let decoded = decode(&bytes, len).unwrap();
245 assert_eq!(decoded, input);
246
247 let decoded_unchecked = decode_unchecked(&bytes, len);
248 assert_eq!(decoded_unchecked, input);
249
250 #[cfg(feature = "with-struct")]
251 {
252 let sixbit = DecSixbit::new(input).unwrap();
253 let decoded = decode::decode(&sixbit.bytes, sixbit.len).unwrap();
254 assert_eq!(decoded, input);
255
256 let decoded_unchecked = decode_unchecked(&sixbit.bytes, sixbit.len);
257 assert_eq!(decoded_unchecked, input);
258 }
259 }
260
261 #[cfg(feature = "with-struct")]
262 #[test]
263 fn test_serde_serialization_readable() {
264 let input = "TEST SERIALIZATION";
265 let sixbit = DecSixbit::new(input).unwrap();
266
267 let serialized = serde_json::to_string(&sixbit).expect("Failed to serialize to JSON");
269 println!("Serialized JSON: {}", serialized);
270
271 let deserialized: DecSixbit =
273 serde_json::from_str(&serialized).expect("Failed to deserialize from JSON");
274
275 assert_eq!(sixbit, deserialized);
276 }
277
278 #[cfg(feature = "with-struct")]
279 #[test]
280 fn test_serde_deserialization_binary() {
281 use bincode::Options;
282 let my_options = bincode::DefaultOptions::new()
283 .with_fixint_encoding()
284 .allow_trailing_bytes();
285
286 let input = "TEST BINARY DESERIALIZATION";
287 let sixbit = DecSixbit::new(input).unwrap();
288
289 let serialized = my_options.serialize(&sixbit).expect("Failed to serialize with bincode");
291 println!("Serialized binary: {:?}", serialized);
292
293 let deserialized: DecSixbit =
295 my_options.deserialize(&serialized).expect("Failed to deserialize from bincode");
296
297 assert_eq!(sixbit, deserialized);
298 }
299
300 #[cfg(feature = "with-struct")]
301 #[test]
302 fn test_serde_readable_and_binary() {
303 let input = "COMPLEX SERIALIZATION TEST";
304 let sixbit = DecSixbit::new(input).unwrap();
305
306 let json = serde_json::to_string(&sixbit).expect("JSON serialization failed");
308
309 let from_json: DecSixbit =
311 serde_json::from_str(&json).expect("JSON deserialization failed");
312 assert_eq!(sixbit, from_json);
313
314 let binary = bincode::serialize(&sixbit).expect("Bincode serialization failed");
316
317 let from_binary: DecSixbit =
319 bincode::deserialize(&binary).expect("Bincode deserialization failed");
320 assert_eq!(sixbit, from_binary);
321 }
322
323 #[cfg(feature = "with-struct")]
324 #[test]
325 fn test_serde_roundtrip() {
326 let inputs = [
327 "",
328 "HELLO",
329 "WORLD!",
330 "SERDE SERIALIZATION TEST 123",
331 "SPECIAL CHARS: !\"#$%&'()*+,-./:;<=>?@[]^_[]",
332 ];
333
334 for input in &inputs {
335 let sixbit = DecSixbit::new(input).expect("Failed to create DecSixbit");
336
337 let json = serde_json::to_string(&sixbit).expect("JSON serialization failed");
339
340 let deserialized_json: DecSixbit =
342 serde_json::from_str(&json).expect("JSON deserialization failed");
343 assert_eq!(sixbit, deserialized_json, "JSON roundtrip failed for input '{}'", input);
344
345 let binary = bincode::serialize(&sixbit).expect("Bincode serialization failed");
347
348 let deserialized_binary: DecSixbit =
350 bincode::deserialize(&binary).expect("Bincode deserialization failed");
351 assert_eq!(sixbit, deserialized_binary, "Binary roundtrip failed for input '{}'", input);
352 }
353 }
354
355 #[cfg(feature = "with-struct")]
356 #[test]
357 fn test_trailing_spaces() {
358 let input = "TESTTEST";
359 let sixbit = DecSixbit::new(input).unwrap();
360 assert_eq!(sixbit.to_string(), "TESTTEST");
361 assert_eq!(sixbit.as_bytes().len(), 6);
362
363 let input = "TEST ";
364 let sixbit = DecSixbit::new(input).unwrap();
365 assert_eq!(sixbit.len(), 8);
366 assert_eq!(sixbit.to_string(), "TEST ");
367 assert_eq!(sixbit.as_bytes().len(), 7);
369 }
370}