-
Notifications
You must be signed in to change notification settings - Fork 1
/
header.rs
383 lines (331 loc) · 12.9 KB
/
header.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
use std::borrow::BorrowMut;
use std::io::{Read, Seek};
use byteorder::{ReadBytesExt, LE};
use log::{debug, info, warn};
use crate::{ManifestError, Result};
/// File header.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Header {
/// Magic bytes of the file.
///
/// They are stored as [`u32`] and not as a string, which also means that it reads `RMAN` in
/// reverse because of endianness.
pub magic: u32,
/// Major version of the manifest format.
///
/// So far, it's only ever been 2.
///
/// NOTE: The library logs if the version differs from 2 using the [log crate][log].
pub major: u8,
/// Minor version of the manifest format.
///
/// So far, it's only ever been 0.
///
/// NOTE: The library logs if the version differs from 0 using the [log crate][log].
pub minor: u8,
/// Manifest flags (no idea what any of them mean or do).
pub flags: u16,
/// Offset to the compressed flatbuffer data.
pub offset: u32,
/// Size of the parsed flatbuffer schema before decompression.
pub compressed_size: u32,
/// Manifest id.
pub manifest_id: u64,
/// Size of the parsed flatbuffer schema after decompression.
pub uncompressed_size: u32,
}
impl Header {
/// Main header parser method.
///
/// This method tries to parse the file header, and does some basic checks that it is indeed a
/// Riot Manifest file.
///
/// It checks the [magic bytes](Header::magic), [version](Header::major),
/// [offset](Header::offset) and [compressed size](Header::compressed_size).
///
/// # Errors
///
/// If converting file size from [`usize`] fails, the error
/// [`ConversionFailure`][crate::ManifestError::ConversionFailure] is returned.
///
/// If seeking to start (rewinding) fails, the error
/// [`SeekError`][crate::ManifestError::SeekError] is returned.
///
/// If reading from io stream fails, the error [`IoError`][crate::ManifestError::IoError] is
/// returned.
///
/// If magic bytes do not equal to `R`, `M`, `A` and `N`, the error
/// [`InvalidMagicBytes`][crate::ManifestError::InvalidMagicBytes] is returned.
///
/// If major version does not equal 2, and the feature
/// [`version_error`](index.html#feature-version_error) is enabled, the error
/// [`InvalidMajor`][crate::ManifestError::InvalidMajor] is returned.
///
/// If minor version does not equal 0, and the feature
/// [`version_error`](index.html#feature-version_error) is enabled, the error
/// [`InvalidMinor`][crate::ManifestError::InvalidMinor] is returned.
///
/// If [`offset`](Header::offset) is smaller or larger than the file, the error
/// [`InvalidOffset`][crate::ManifestError::InvalidOffset] is returned.
///
/// If [`compressed_size`](Header::compressed_size) is smaller or larger than the file, the
/// error [`CompressedSizeTooLarge`][crate::ManifestError::CompressedSizeTooLarge] is
/// returned.
pub fn from_reader<R: Read + Seek>(mut reader: R) -> Result<Self> {
debug!("Attempting to convert \"reader.bytes().count()\" into \"u32\".");
let size: u32 = reader.borrow_mut().bytes().count().try_into()?;
debug!("Successfully converted \"reader.bytes().count()\" into \"u32\".");
debug!("The file is {size} bytes in size");
if let Err(error) = reader.rewind() {
return Err(ManifestError::SeekError(error));
};
let magic = reader.read_u32::<LE>()?;
// N A M R (RMAN bacwards because I am reading this as an u32, instead
// of as an array of chars)
if magic != 0x4E_41_4D_52 {
return Err(ManifestError::InvalidMagicBytes(magic));
}
let major = reader.read_u8()?;
if major != 2 {
warn!("Invalid major version. Parsing the manfiset may not work.");
info!("If you want the crate to throw an error instead, you can enable the \"version_error\" feature");
#[cfg(feature = "version_error")]
return Err(ManifestError::InvalidMajor(major));
}
let minor = reader.read_u8()?;
if major == 2 && minor != 0 {
info!("Invalid minor version. Parsing the manfiset will probably still work.");
info!("If you want the crate to throw an error instead, you can enable the \"version_error\" feature");
#[cfg(feature = "version_error")]
return Err(ManifestError::InvalidMinor(minor));
}
let flags = reader.read_u16::<LE>()?;
let offset = reader.read_u32::<LE>()?;
if offset < 28 || offset >= size {
return Err(ManifestError::InvalidOffset(offset));
}
let compressed_size = reader.read_u32::<LE>()?;
if compressed_size > size - 28 {
return Err(ManifestError::CompressedSizeTooLarge(compressed_size));
}
if compressed_size + offset > size {
return Err(ManifestError::CompressedSizeTooLarge(
compressed_size + offset,
));
}
let manifest_id = reader.read_u64::<LE>()?;
let uncompressed_size = reader.read_u32::<LE>()?;
let file_header = Self {
magic,
major,
minor,
flags,
offset,
compressed_size,
manifest_id,
uncompressed_size,
};
Ok(file_header)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unreadable_literal)]
use super::*;
use std::io::Cursor;
mod helpers {
pub const VALID_HEADER: [u8; 32] = [
0x52, 0x4D, 0x41, 0x4E, 0x02, 0x00, 0x00, 0x02, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xD2, 0x17, 0xC3, 0xEF, 0xAB, 0x4A, 0x9C, 0x2B, 0x60, 0xA4, 0xD0, 0x01,
0x00, 0x00, 0x00, 0x00,
];
macro_rules! assert_error {
($buf: ident, $error: ident) => {
let mut cursor = Cursor::new($buf);
let Err(error) = crate::Header::from_reader(&mut cursor) else {
panic!("did not throw an error");
};
let crate::error::ManifestError::$error(..) = error else {
panic!("some other error was thrown");
};
};
}
pub(crate) use assert_error;
}
#[test]
fn should_parse_when_valid_header() {
let mut cursor = Cursor::new(helpers::VALID_HEADER);
if let Err(error) = Header::from_reader(&mut cursor) {
panic!("there was an error when parsing header, header: {error:?}");
};
}
#[test]
fn should_have_correct_values_when_valid_header() {
let mut cursor = Cursor::new(helpers::VALID_HEADER);
let header = Header::from_reader(&mut cursor).unwrap();
assert_eq!(header.magic, 0x4E414D52, "magic bytes did not match");
assert_eq!(header.major, 2, "major version did not match");
assert_eq!(header.minor, 0, "minor version did not match");
assert_eq!(header.flags, 512, "flags did not match");
assert_eq!(header.offset, 28, "offset did not match");
assert_eq!(header.compressed_size, 0, "compressed size did not match");
assert_eq!(
header.manifest_id, 3142468742320166866,
"manifest id did not match"
);
assert_eq!(
header.uncompressed_size, 30450784,
"unompressed size did not match"
);
}
#[test]
fn should_throw_correct_errors_when_eof() {
// EOF when reading magic bytes
let error = Header::from_reader(&mut Cursor::new(helpers::VALID_HEADER[..3].to_owned()))
.expect_err("did not throw an error on missing bytes");
match error {
crate::error::ManifestError::IoError(_) => (),
_ => panic!("invalid ManifestError error when eof"),
};
// EOF when reading major
let error = Header::from_reader(&mut Cursor::new(helpers::VALID_HEADER[..4].to_owned()))
.expect_err("did not throw an error on missing bytes");
match error {
crate::error::ManifestError::IoError(_) => (),
_ => panic!("invalid ManifestError error when eof"),
};
// EOF when reading minor
let error = Header::from_reader(&mut Cursor::new(helpers::VALID_HEADER[..5].to_owned()))
.expect_err("did not throw an error on missing bytes");
match error {
crate::error::ManifestError::IoError(_) => (),
_ => panic!("invalid ManifestError error when eof"),
};
// EOF when reading flags
let error = Header::from_reader(&mut Cursor::new(helpers::VALID_HEADER[..7].to_owned()))
.expect_err("did not throw an error on missing bytes");
match error {
crate::error::ManifestError::IoError(_) => (),
_ => panic!("invalid ManifestError error when eof"),
};
// EOF when reading offset
let error = Header::from_reader(&mut Cursor::new(helpers::VALID_HEADER[..11].to_owned()))
.expect_err("did not throw an error on missing bytes");
match error {
crate::error::ManifestError::IoError(_) => (),
_ => panic!("invalid ManifestError error when eof"),
};
// it should be impossible for reading to fail at this point, because
// offset must be greater than 28 and and less then file size, which
// in turn ensures that the file size is at least 28 bytes
}
#[test]
fn should_error_when_invalid_magic_bytes() {
let buf = [&[0x53], &helpers::VALID_HEADER[1..]].concat();
helpers::assert_error!(buf, InvalidMagicBytes);
}
#[test]
fn should_error_when_invalid_major() {
let buf = [
&helpers::VALID_HEADER[..4],
&[0x01],
&helpers::VALID_HEADER[5..],
]
.concat();
#[cfg(not(feature = "version_error"))]
{
let mut cursor = Cursor::new(buf);
assert!(Header::from_reader(&mut cursor).is_ok(), "error was thrown");
}
#[cfg(feature = "version_error")]
helpers::assert_error!(buf, InvalidMajor);
}
#[test]
fn should_error_when_invalid_minor() {
let buf = [
&helpers::VALID_HEADER[..5],
&[0x01],
&helpers::VALID_HEADER[6..],
]
.concat();
#[cfg(not(feature = "version_error"))]
{
let mut cursor = Cursor::new(buf);
assert!(Header::from_reader(&mut cursor).is_ok(), "error was thrown");
}
#[cfg(feature = "version_error")]
helpers::assert_error!(buf, InvalidMinor);
}
#[test]
fn should_error_when_offset_too_small() {
// too small
let buf = [
&helpers::VALID_HEADER[..8],
&0u32.to_le_bytes(),
&helpers::VALID_HEADER[12..],
]
.concat();
helpers::assert_error!(buf, InvalidOffset);
// slightly too small
let buf = [
&helpers::VALID_HEADER[..8],
&27u32.to_le_bytes(),
&helpers::VALID_HEADER[12..],
]
.concat();
helpers::assert_error!(buf, InvalidOffset);
}
#[test]
fn should_error_when_offset_too_large() {
// slightly too large
let size = u32::try_from(helpers::VALID_HEADER.len()).unwrap();
let buf = [
&helpers::VALID_HEADER[..8],
&size.to_le_bytes(),
&helpers::VALID_HEADER[12..],
]
.concat();
helpers::assert_error!(buf, InvalidOffset);
// too large
let buf = [
&helpers::VALID_HEADER[..8],
&u32::MAX.to_le_bytes(),
&helpers::VALID_HEADER[12..],
]
.concat();
helpers::assert_error!(buf, InvalidOffset);
}
#[test]
fn should_error_when_compressed_size_too_large() {
// slightly too large
let size = u32::try_from(helpers::VALID_HEADER.len()).unwrap();
let buf = [
&helpers::VALID_HEADER[..12],
&(size - 27).to_le_bytes(),
&helpers::VALID_HEADER[16..],
]
.concat();
helpers::assert_error!(buf, CompressedSizeTooLarge);
// too large
let buf = [
&helpers::VALID_HEADER[..12],
&u32::MAX.to_le_bytes(),
&helpers::VALID_HEADER[16..],
]
.concat();
helpers::assert_error!(buf, CompressedSizeTooLarge);
}
#[test]
fn should_error_when_compressed_size_and_offset_too_large() {
let offset = 28u32;
let size = u32::try_from(helpers::VALID_HEADER.len()).unwrap();
let buf = [
&helpers::VALID_HEADER[..8],
&offset.to_le_bytes(),
&(size - offset + 1).to_le_bytes(),
&helpers::VALID_HEADER[16..],
]
.concat();
helpers::assert_error!(buf, CompressedSizeTooLarge);
}
}