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 fn create_test_writer(
64 build_name: &str,
65 build_version: &str,
66 data_specs: &[(&str, &str, &[u8])], ) -> 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); bytes.push(0xFF); 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 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); 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}