Skip to main content

upbflib/
lib.rs

1pub mod raw;
2pub mod read;
3pub mod write;
4
5#[repr(u8)]
6#[derive(Debug, Copy, Clone, PartialEq, Eq)]
7pub enum UPBFType {
8    MediumAlignedLittleEndian   = 0x0,
9    MediumAlignedBigEndian      = 0x1,
10    BigAlignedLittleEndian      = 0x2,
11    BigAlignedBigEndian         = 0x3,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct UPBFVersion(u8);
16
17impl TryFrom<u8> for UPBFType {
18    type Error = ();
19
20    fn try_from(value: u8) -> Result<Self, Self::Error> {
21        if value > 0x3 { return Err(()); }
22        unsafe { std::mem::transmute(value) }
23    }
24}
25
26impl Into<u8> for UPBFType {
27    fn into(self) -> u8 {
28        unsafe {  std::mem::transmute(self) }
29    }
30}
31
32impl UPBFVersion {
33    pub const V0: Self = Self(0x0);
34    pub const V1: Self = Self(0x1);
35    pub const LAST_SUPPORTED: Self = Self::V1;
36
37    pub const fn new(value: u8) -> Self {
38        Self(value)
39    }
40
41    pub const fn is_supported(self) -> bool {
42        self.0 == Self::LAST_SUPPORTED.0
43    }
44
45    pub const fn as_raw(self) -> u8 {
46        self.0
47    }
48}
49
50impl Into<u8> for UPBFVersion {
51    fn into(self) -> u8 {
52        self.0
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use read::{UPBFReader, UPBFReaderError, UPBFReaderDataFormatReadError, UPBFReaderHeaderReadError };
60    use write::{UPBFWriter, UPBFWriterDataAddError, UPBFWriterError, UPBFWriterWriteError };
61
62    // Helper: create a writer with some data formats and data blocks
63    fn create_test_writer(
64        build_name: &str,
65        build_version: &str,
66        data_specs: &[(&str, &str, &[u8])], // (data_name, format_name, data_bytes)
67    ) -> UPBFWriter {
68        let mut writer = UPBFWriter::new(build_name.to_string(), build_version.to_string());
69        for (data_name, format_name, data_bytes) in data_specs {
70            writer
71                .add_data(
72                    data_name.to_string(),
73                    &format_name.to_string(),
74                    data_bytes.to_vec().into_boxed_slice(),
75                )
76                .unwrap();
77        }
78        writer
79    }
80
81    #[test]
82    fn roundtrip_all_types() {
83        let specs = &[
84            ("data1", "formatA", &b"hello"[..]),
85            ("data2", "formatB", &b"world"[..]),
86            ("data3", "formatA", &b"again"[..]),
87        ];
88        let build_name = "@test_build";
89        let build_version = "1.2.3";
90
91        for ty in [
92            UPBFType::MediumAlignedLittleEndian,
93            UPBFType::MediumAlignedBigEndian,
94            UPBFType::BigAlignedLittleEndian,
95            UPBFType::BigAlignedBigEndian,
96        ] {
97            let mut writer = create_test_writer(build_name, build_version, specs);
98            let bytes = writer.write(ty, UPBFVersion::LAST_SUPPORTED).unwrap();
99            let reader = UPBFReader::new(&bytes).unwrap();
100            let read_result = reader.read().unwrap();
101
102            assert_eq!(read_result.file_type(), ty);
103            assert_eq!(read_result.build_name(), build_name);
104            assert_eq!(read_result.build_version(), build_version);
105
106            let formats = read_result.data_formats();
107            assert_eq!(formats.len(), 2);
108            let format_a = formats.iter().find(|f| f.name() == "formatA").unwrap();
109            let format_b = formats.iter().find(|f| f.name() == "formatB").unwrap();
110            let data = read_result.data();
111            assert_eq!(data.len(), 3);
112            let data1 = data.iter().find(|d| d.name() == "data1").unwrap();
113            let data2 = data.iter().find(|d| d.name() == "data2").unwrap();
114            let data3 = data.iter().find(|d| d.name() == "data3").unwrap();
115            assert_eq!(data1.data_id(), format_a.data_id());
116            assert_eq!(data3.data_id(), format_a.data_id());
117            assert_eq!(data2.data_id(), format_b.data_id());
118            assert_eq!(data1.data(), b"hello");
119            assert_eq!(data2.data(), b"world");
120            assert_eq!(data3.data(), b"again");
121        }
122    }
123
124    #[test]
125    fn data_format_reuse() {
126        let mut writer = UPBFWriter::new("test".to_string(), "1.0".to_string());
127        writer
128            .add_data("d1".to_string(), &"fmt".to_string(), b"data1".to_vec().into_boxed_slice())
129            .unwrap();
130        writer
131            .add_data("d2".to_string(), &"fmt".to_string(), b"data2".to_vec().into_boxed_slice())
132            .unwrap();
133
134        let formats = writer.data_formats();
135        assert_eq!(formats.len(), 1);
136        assert_eq!(formats[0].name(), "fmt");
137        assert_eq!(formats[0].refs(), 2);
138    }
139
140    #[test]
141    fn remove_data_decrements_refs() {
142        let mut writer = UPBFWriter::new("test".to_string(), "1.0".to_string());
143        writer
144            .add_data("d1".to_string(), &"fmt".to_string(), b"d1".to_vec().into_boxed_slice())
145            .unwrap();
146        writer
147            .add_data("d2".to_string(), &"fmt".to_string(), b"d2".to_vec().into_boxed_slice())
148            .unwrap();
149
150        assert_eq!(writer.data_formats().len(), 1);
151        assert_eq!(writer.data_formats()[0].refs(), 2);
152
153        assert!(writer.remove_data(&"d1".to_string()));
154        assert_eq!(writer.data().len(), 1);
155        assert_eq!(writer.data_formats().len(), 1);
156        assert_eq!(writer.data_formats()[0].refs(), 1);
157
158        assert!(writer.remove_data(&"d2".to_string()));
159        assert_eq!(writer.data().len(), 0);
160        assert_eq!(writer.data_formats().len(), 0);
161
162        writer
163            .add_data("d3".to_string(), &"fmt".to_string(), b"d3".to_vec().into_boxed_slice())
164            .unwrap();
165        assert_eq!(writer.data_formats().len(), 1);
166    }
167
168    #[test]
169    fn add_data_duplicate_name_error() {
170        let mut writer = UPBFWriter::new("test".to_string(), "1.0".to_string());
171        writer
172            .add_data("dup".to_string(), &"fmt".to_string(), b"first".to_vec().into_boxed_slice())
173            .unwrap();
174        let err = writer
175            .add_data("dup".to_string(), &"fmt".to_string(), b"second".to_vec().into_boxed_slice())
176            .unwrap_err();
177        match err {
178            UPBFWriterError::DataAdd(UPBFWriterDataAddError::DataAlreadyDefined) => (),
179            _ => panic!("expected DataAlreadyDefined"),
180        }
181    }
182
183    #[test]
184    fn add_or_overwrite_data() {
185        let mut writer = UPBFWriter::new("test".to_string(), "1.0".to_string());
186        writer
187            .add_or_overwrite_data(&"d1".to_string(), &"fmt".to_string(), b"first".to_vec().into_boxed_slice())
188            .unwrap();
189        assert_eq!(writer.data().len(), 1);
190        assert_eq!(writer.data()[0].data(), b"first");
191
192        writer
193            .add_or_overwrite_data(&"d1".to_string(), &"fmt2".to_string(), b"second".to_vec().into_boxed_slice())
194            .unwrap();
195        assert_eq!(writer.data().len(), 1);
196        assert_eq!(writer.data()[0].data(), b"second");
197        let fmt = writer.data_formats().iter().find(|f| f.data_id() == writer.data()[0].data_id()).unwrap();
198        assert_eq!(fmt.name(), "fmt2");
199    }
200
201    #[test]
202    fn remove_nonexistent_data_returns_false() {
203        let mut writer = UPBFWriter::new("test".to_string(), "1.0".to_string());
204        assert!(!writer.remove_data(&"nosuch".to_string()));
205    }
206
207    #[test]
208    fn read_invalid_magic() {
209        let bytes = b"BADMAGIC";
210        let err = UPBFReader::new(bytes).unwrap_err();
211        match err {
212            UPBFReaderError::Header(UPBFReaderHeaderReadError::InvalidMagic) => (),
213            _ => panic!("expected InvalidMagic"),
214        }
215    }
216
217    #[test]
218    fn read_unsupported_version() {
219        let mut bytes = b".UPBF\0".to_vec();
220        bytes.push(0x00); // type
221        bytes.push(0xFF); // unsupported version
222        bytes.resize(8, 0);
223        let reader = UPBFReader::new(&bytes).unwrap();
224        assert_eq!(reader.is_read_supported(), false);
225        let err = reader.read().unwrap_err();
226        match err {
227            UPBFReaderError::Header(UPBFReaderHeaderReadError::UnsupportedVersion) => (),
228            _ => panic!("expected UnsupportedVersion"),
229        }
230    }
231
232    #[test]
233    fn read_truncated_file() {
234        let bytes = b".UPBF\0";
235        let err = UPBFReader::new(bytes).unwrap_err();
236        match err {
237            UPBFReaderError::InvalidFileLength => (),
238            _ => panic!("expected InvalidFileLength"),
239        }
240    }
241
242    #[test]
243    fn read_truncated_name() {
244        let mut writer = UPBFWriter::new("very_long_build_name_that_exceeds_buffer".to_string(), "1.0".to_string());
245        let mut bytes = writer
246            .write(UPBFType::MediumAlignedLittleEndian, UPBFVersion::LAST_SUPPORTED)
247            .unwrap();
248        bytes.truncate(0x20);
249        let reader = UPBFReader::new(&bytes).unwrap();
250        let err = reader.read().unwrap_err();
251        match err {
252            UPBFReaderError::Header(UPBFReaderHeaderReadError::InvalidBuildNameLength) => (),
253            _ => panic!("expected InvalidLength"),
254        }
255    }
256
257    #[test]
258    fn data_format_lookup() {
259        let mut writer = create_test_writer(
260            "test",
261            "1.0",
262            &[("data1", "formatX", &b"abc"[..]), ("data2", "formatY", &b"def"[..])],
263        );
264        let bytes = writer.write(UPBFType::MediumAlignedLittleEndian, UPBFVersion::LAST_SUPPORTED).unwrap();
265        let reader = UPBFReader::new(&bytes).unwrap();
266        let read_result = reader.read().unwrap();
267
268        let data = read_result.data();
269        let data1 = data.iter().find(|d| d.name() == "data1").unwrap();
270        let fmt1 = data1.format(&read_result);
271        assert_eq!(fmt1.name(), "formatX");
272        let data2 = data.iter().find(|d| d.name() == "data2").unwrap();
273        let fmt2 = data2.format(&read_result);
274        assert_eq!(fmt2.name(), "formatY");
275    }
276
277    #[test]
278    fn empty_writer() {
279        let mut writer = UPBFWriter::new("empty".to_string(), "0.0".to_string());
280        let bytes = writer.write(UPBFType::MediumAlignedLittleEndian, UPBFVersion::LAST_SUPPORTED).unwrap();
281        let reader = UPBFReader::new(&bytes).unwrap();
282        let read_result = reader.read().unwrap();
283
284        assert_eq!(read_result.data_formats().len(), 0);
285        assert_eq!(read_result.data().len(), 0);
286        assert_eq!(read_result.build_name(), "empty");
287        assert_eq!(read_result.build_version(), "0.0");
288    }
289
290    #[test]
291    fn convert_from_read_result() {
292        let mut writer_orig = create_test_writer(
293            "convert",
294            "2.0",
295            &[("c1", "fmtA", &b"one"[..]), ("c2", "fmtB", &b"two"[..]), ("c3", "fmtA", &b"three"[..])],
296        );
297        let bytes = writer_orig.write(UPBFType::BigAlignedBigEndian, UPBFVersion::LAST_SUPPORTED).unwrap();
298        let reader = UPBFReader::new(&bytes).unwrap();
299        let read_result = reader.read().unwrap();
300
301        let writer_converted: UPBFWriter = UPBFWriter::try_from(&read_result).unwrap();
302        let mut writer_converted_mut = writer_converted;
303        let bytes2 = writer_converted_mut.write(UPBFType::BigAlignedBigEndian, UPBFVersion::LAST_SUPPORTED).unwrap();
304        // The two byte sequences should be identical (deterministic writer)
305        assert_eq!(bytes, bytes2);
306    }
307
308    #[test]
309    fn data_id_reuse_after_remove() {
310        let mut writer = UPBFWriter::new("test".to_string(), "1.0".to_string());
311        writer
312            .add_data("d1".to_string(), &"fmt".to_string(), b"".to_vec().into_boxed_slice())
313            .unwrap();
314        let first_format_id = writer.data_formats()[0].data_id();
315        writer.remove_data(&"d1".to_string());
316        writer
317            .add_data("d2".to_string(), &"fmt".to_string(), b"".to_vec().into_boxed_slice())
318            .unwrap();
319        assert_eq!(writer.data_formats()[0].data_id(), first_format_id);
320    }
321
322    #[test]
323    fn writer_errors_on_too_long_strings() {
324        let mut writer = UPBFWriter::new("a".repeat(u32::MAX as usize).to_string(), "1.0".to_string());
325        let err = writer
326            .write(UPBFType::MediumAlignedLittleEndian, UPBFVersion::LAST_SUPPORTED)
327            .unwrap_err();
328        match err {
329            UPBFWriterError::Write(UPBFWriterWriteError::InvalidBuildNameLength) => (),
330            _ => panic!("expected InvalidNameLength"),
331        }
332    }
333
334    #[test]
335    fn read_invalid_type_byte() {
336        let mut bytes = b".UPBF\0".to_vec();
337        bytes.push(0xFF); // invalid type
338        bytes.push(UPBFVersion::LAST_SUPPORTED.into());
339        bytes.resize(8, 0);
340        let err = UPBFReader::new(&bytes).unwrap_err();
341        match err {
342            UPBFReaderError::Header(UPBFReaderHeaderReadError::InvalidType) => (),
343            _ => panic!("expected InvalidType"),
344        }
345    }
346
347    #[test]
348    fn read_corrupted_format_next_offset() {
349        let mut writer = create_test_writer("corrupt", "1.0", &[("d1", "fmt", &b"x"[..])]);
350        let mut bytes = writer
351            .write(UPBFType::MediumAlignedLittleEndian, UPBFVersion::LAST_SUPPORTED)
352            .unwrap();
353        let format_offset = u32::from_le_bytes(bytes[0x8..0xC].try_into().unwrap()) as usize;
354        let next_offset = format_offset + 0x10000;
355        bytes[format_offset..format_offset + 4].copy_from_slice(&(next_offset as u32).to_le_bytes());
356        let reader = UPBFReader::new(&bytes).unwrap();
357        let err = reader.read().unwrap_err();
358        match err {
359            UPBFReaderError::DataFormat(UPBFReaderDataFormatReadError::InvalidOffset) => (),
360            _ => panic!("expected InvalidOffset"),
361        }
362    }
363}