dec_sixbit/
lib.rs

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/// Represents errors that can occur during encoding or decoding operations.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
23pub enum Error {
24    /// Occurs when encoding fails due to invalid character in input.
25    #[error("invalid character in input (must be ASCII 32-95)")]
26    InvalidCharacter,
27
28    /// Occurs when decoding fails due to inconsistent input bytes and length.
29    #[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"; // 4 chars should pack into 3 bytes
43
44        // Calculate expected values
45        let a = 65 - 32; // = 33 = 0b100001
46        let b = 66 - 32; // = 34 = 0b100010
47        let c = 67 - 32; // = 35 = 0b100011
48        let d = 68 - 32; // = 36 = 0b100100
49
50        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        // Correct expected bytes based on encode_core logic
61        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        // Encapsulated API test
68        #[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], // "A" packed: [132]
86            vec![0b10000110, 0b00100000], // "AB" packed: [134, 32]
87            vec![0b10000110, 0b00101000, 0b11000000], // "ABC" packed: [134, 40, 192]
88        ];
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            // Display SIXBIT values
95            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            // Display actual encoded bytes
106            println!("Got bytes:");
107            for (i, &b) in bytes.iter().enumerate() {
108                println!("  byte {} = {:08b} ({})", i + 1, b, b);
109            }
110
111            // Assertions
112            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        // Test character below range
148        assert!(matches!(
149            encode("\x1F"),
150            Err(Error::InvalidCharacter)
151        ));
152
153        // Test character above range
154        assert!(matches!(
155            encode("abc"),
156            Err(Error::InvalidCharacter)
157        ));
158
159        // Test non-ASCII character
160        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        // Serialize to JSON (readable format)
268        let serialized = serde_json::to_string(&sixbit).expect("Failed to serialize to JSON");
269        println!("Serialized JSON: {}", serialized);
270
271        // Deserialize back
272        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        // Serialize to binary using bincode
290        let serialized = my_options.serialize(&sixbit).expect("Failed to serialize with bincode");
291        println!("Serialized binary: {:?}", serialized);
292
293        // Deserialize from binary
294        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        // Serialize to JSON
307        let json = serde_json::to_string(&sixbit).expect("JSON serialization failed");
308
309        // Deserialize from JSON
310        let from_json: DecSixbit =
311            serde_json::from_str(&json).expect("JSON deserialization failed");
312        assert_eq!(sixbit, from_json);
313
314        // Serialize to binary
315        let binary = bincode::serialize(&sixbit).expect("Bincode serialization failed");
316
317        // Deserialize from binary
318        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            // Serialize to JSON
338            let json = serde_json::to_string(&sixbit).expect("JSON serialization failed");
339
340            // Deserialize from JSON
341            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            // Serialize to binary
346            let binary = bincode::serialize(&sixbit).expect("Bincode serialization failed");
347
348            // Deserialize from binary
349            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        // The last byte contains DecSixbit::TRAILING_SPACE_MARKER
368        assert_eq!(sixbit.as_bytes().len(), 7);
369    }
370}