Skip to main content

prototext_core/helpers/
mod.rs

1// SPDX-FileCopyrightText: 2025 - 2026 Frederic Ruget <fred@atlant.is> <fred@s3ns.io> (GitHub: @douzebis)
2// SPDX-FileCopyrightText: 2025 - 2026 Thales Cloud Sécurisé
3//
4// SPDX-License-Identifier: MIT
5
6mod codecs;
7mod varint;
8mod wire;
9
10pub use codecs::*;
11pub use varint::*;
12pub use wire::*;
13
14// ── Unit tests ────────────────────────────────────────────────────────────────
15
16#[cfg(test)]
17mod tests {
18    use super::*;
19
20    // ── varint round-trips ────────────────────────────────────────────────────
21
22    #[test]
23    fn varint_zero() {
24        let buf = [0x00u8];
25        let r = parse_varint(&buf, 0);
26        assert_eq!(r.varint, Some(0));
27        assert_eq!(r.varint_ohb, None);
28        assert_eq!(r.next_pos, 1);
29    }
30
31    #[test]
32    fn varint_one_byte() {
33        let buf = [0x01u8];
34        let r = parse_varint(&buf, 0);
35        assert_eq!(r.varint, Some(1));
36        assert_eq!(r.next_pos, 1);
37    }
38
39    #[test]
40    fn varint_150() {
41        // 150 = 0x96 0x01
42        let buf = [0x96u8, 0x01];
43        let r = parse_varint(&buf, 0);
44        assert_eq!(r.varint, Some(150));
45        assert_eq!(r.next_pos, 2);
46        assert_eq!(r.varint_ohb, None);
47    }
48
49    #[test]
50    fn varint_max_u64() {
51        // max u64: 10 bytes of 0xFF followed by 0x01
52        let buf = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01];
53        let r = parse_varint(&buf, 0);
54        assert_eq!(r.varint, Some(u64::MAX));
55        assert_eq!(r.next_pos, 10);
56    }
57
58    #[test]
59    fn varint_truncated() {
60        // Continuation byte with no terminator
61        let buf = [0x80u8, 0x80];
62        let r = parse_varint(&buf, 0);
63        assert!(r.varint_gar.is_some());
64        assert!(r.varint.is_none());
65    }
66
67    #[test]
68    fn varint_empty_at_end() {
69        let buf = [0x01u8];
70        let r = parse_varint(&buf, 1); // start == buflen
71        assert!(r.varint_gar.is_some());
72        assert_eq!(r.varint_gar.unwrap(), Vec::<u8>::new());
73    }
74
75    #[test]
76    fn varint_overhang_one() {
77        // 0x00 encoded non-canonically as 0x80 0x00
78        let buf = [0x80u8, 0x00];
79        let r = parse_varint(&buf, 0);
80        assert_eq!(r.varint, Some(0));
81        assert_eq!(r.varint_ohb, Some(1));
82    }
83
84    #[test]
85    fn varint_overhang_two() {
86        // 0x00 encoded as 0x80 0x80 0x00  (2 overhang bytes)
87        let buf = [0x80u8, 0x80, 0x00];
88        let r = parse_varint(&buf, 0);
89        assert_eq!(r.varint, Some(0));
90        assert_eq!(r.varint_ohb, Some(2));
91    }
92
93    #[test]
94    fn varint_encode_with_overhang() {
95        let bytes = encode_varint_bytes(0, Some(1));
96        assert_eq!(bytes, vec![0x80, 0x00]);
97
98        let bytes2 = encode_varint_bytes(0, Some(2));
99        assert_eq!(bytes2, vec![0x80, 0x80, 0x00]);
100
101        let bytes3 = encode_varint_bytes(150, None);
102        assert_eq!(bytes3, vec![0x96, 0x01]);
103    }
104
105    #[test]
106    fn varint_encode_roundtrip() {
107        for val in [0u64, 1, 127, 128, 300, 16383, 16384, u64::MAX] {
108            let encoded = encode_varint_bytes(val, None);
109            let r = parse_varint(&encoded, 0);
110            assert_eq!(r.varint, Some(val), "roundtrip failed for {val}");
111            assert_eq!(r.next_pos, encoded.len());
112        }
113    }
114
115    // ── wiretag ──────────────────────────────────────────────────────────────
116
117    #[test]
118    fn wiretag_field1_varint() {
119        // tag for field 1, wire type 0: (1 << 3) | 0 = 0x08
120        let buf = [0x08u8];
121        let r = parse_wiretag(&buf, 0);
122        assert_eq!(r.wtype, Some(0));
123        assert_eq!(r.wfield, Some(1));
124        assert_eq!(r.wfield_ohb, None);
125        assert_eq!(r.wfield_oor, None);
126    }
127
128    #[test]
129    fn wiretag_invalid_wire_type() {
130        // wire type 6 is invalid
131        let buf = [0x06u8, 0x00, 0x01];
132        let r = parse_wiretag(&buf, 0);
133        assert!(r.wtag_gar.is_some());
134        assert!(r.wtype.is_none());
135    }
136
137    #[test]
138    fn wiretag_field_number_zero_is_oor() {
139        // field number 0: wire type 0, field 0 → (0 << 3) | 0 = 0x00
140        // but parse_wiretag asserts start < buflen, so use a buffer with content
141        let buf = [0x00u8]; // tag byte = 0 → wire_type=0, field=0
142        let r = parse_wiretag(&buf, 0);
143        assert_eq!(r.wfield, Some(0));
144        assert_eq!(r.wfield_oor, Some(true));
145    }
146
147    #[test]
148    fn wiretag_overhung() {
149        // Field 1, wire type 0 encoded non-canonically: (0x08) as 0x88 0x00
150        let buf = [0x88u8, 0x00];
151        let r = parse_wiretag(&buf, 0);
152        assert_eq!(r.wtype, Some(0));
153        assert_eq!(r.wfield, Some(1));
154        assert_eq!(r.wfield_ohb, Some(1));
155    }
156
157    // ── numeric codecs ────────────────────────────────────────────────────────
158
159    #[test]
160    fn int32_negative() {
161        // -1 as int32 is stored as 0xFFFFFFFF in a varint
162        assert_eq!(decode_int32(0xFFFFFFFF), -1i32);
163    }
164
165    #[test]
166    fn int64_negative() {
167        assert_eq!(decode_int64(u64::MAX), -1i64);
168    }
169
170    #[test]
171    fn sint32_roundtrip() {
172        for v in [-1i32, 0, 1, -2, 2, i32::MIN, i32::MAX] {
173            let encoded = if v >= 0 {
174                ((v as u32) << 1) as u64
175            } else {
176                ((!v as u32) * 2 + 1) as u64
177            };
178            assert_eq!(decode_sint32(encoded), v, "sint32 roundtrip for {v}");
179        }
180    }
181
182    #[test]
183    fn sint64_roundtrip() {
184        for v in [-1i64, 0, 1, -2, 2, i64::MIN, i64::MAX] {
185            let encoded = if v >= 0 {
186                (v as u64) << 1
187            } else {
188                ((!v as u64) << 1) | 1
189            };
190            assert_eq!(decode_sint64(encoded), v, "sint64 roundtrip for {v}");
191        }
192    }
193
194    #[test]
195    fn fixed32_little_endian() {
196        let data = [0x01u8, 0x00, 0x00, 0x00];
197        assert_eq!(decode_fixed32(&data), 1u32);
198        let data2 = [0xFFu8, 0xFF, 0xFF, 0xFF];
199        assert_eq!(decode_fixed32(&data2), u32::MAX);
200    }
201
202    #[test]
203    fn fixed64_little_endian() {
204        let data = [0x01u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
205        assert_eq!(decode_fixed64(&data), 1u64);
206    }
207
208    #[test]
209    fn double_roundtrip() {
210        let val = std::f64::consts::PI;
211        let data = val.to_le_bytes();
212        assert_eq!(decode_double(&data), val);
213    }
214
215    #[test]
216    fn float_roundtrip() {
217        let val = 1.5f32;
218        let data = val.to_le_bytes();
219        assert_eq!(decode_float(&data), val);
220    }
221
222    #[test]
223    fn write_varint_field_roundtrip() {
224        let mut buf = Vec::new();
225        write_varint_field(1, 300, &mut buf);
226        // tag: (1<<3)|0 = 0x08; value 300 = 0xAC 0x02
227        assert_eq!(buf, vec![0x08, 0xAC, 0x02]);
228    }
229
230    #[test]
231    fn write_len_field_roundtrip() {
232        let mut buf = Vec::new();
233        write_len_field(2, b"hi", &mut buf);
234        // tag: (2<<3)|2 = 0x12; length: 0x02; data: 0x68 0x69
235        assert_eq!(buf, vec![0x12, 0x02, 0x68, 0x69]);
236    }
237}