Skip to main content

gbx_header/gbx/
parser.rs

1//! Package containing the parser for GBX Files.
2//! The datatypes used are defined in the [gbx](crate::gbx) module, with [GBX](crate::gbx::GBX) being the main one.
3
4pub mod challenge;
5pub mod replay;
6
7use self::challenge::parse_challenge_header_xml;
8use self::replay::parse_replay_xml;
9
10use super::*;
11
12use std::convert::TryInto;
13use std::io;
14use std::io::Read;
15use std::{fs::File, num::ParseIntError};
16
17const HEADER_START_TOKEN: &[u8] = "<header ".as_bytes();
18const HEADER_END_TOKEN: &[u8] = "</header>".as_bytes();
19
20const THUMBNAIL_START_TOKEN: &[u8] = &[0xFF, 0xD8, 0xFF];
21const THUMBNAIL_END_TOKEN: &[u8] = &[0xFF, 0xD9];
22
23#[derive(Debug)]
24pub enum ParseError {
25    MissingGBXMagic,
26    FileTooShort,
27    HeaderNotFound,
28    ThumbnailNotFound,
29    XMLParseError(xml::reader::Error),
30    HeaderValueError(ParseIntError),
31    HeaderTryIntoEnumError(String),
32    IOError(io::Error),
33    Unknown,
34}
35
36/// Util. function to find first match of a sub-slice in a slice
37fn find_window(buf: &[u8], needle: &[u8]) -> Option<usize> {
38    buf.windows(needle.len()).position(|w| w == needle)
39}
40
41/// Reads the contents from `filename` and parses them identically to [parse_from_buffer](parse_from_buffer).
42///
43/// Note, that the [GBXOrigin](GBXOrigin) of the returned [GBX](GBX).[GBXHeader](GBXHeader) will be `File{path:<filepath>}`.
44pub fn parse_from_file(filename: &str) -> Result<GBX, ParseError> {
45    let mut buffer = Vec::new();
46    let mut f = File::open(filename).map_err(ParseError::IOError)?;
47    f.read_to_end(&mut buffer).map_err(ParseError::IOError)?;
48    let mut gbx = parse_from_buffer(&buffer)?;
49    gbx.origin = GBXOrigin::File {
50        path: String::from(filename),
51    };
52    Ok(gbx)
53}
54
55/// Parses the given slice of bytes as if it was a GBX file.
56///
57/// This function assumes the XML header included in the GBX file is valid UTF8, and will panic
58/// otherwise.
59/// As of now the actual map-data is not extracted.
60///
61/// If you want to parse a file directly see [parse_from_file](parse_from_file).
62pub fn parse_from_buffer(buffer: &[u8]) -> Result<GBX, ParseError> {
63    if buffer.len() < 3 {
64        return Err(ParseError::FileTooShort);
65    }
66
67    if &buffer[0..3] != b"GBX" {
68        return Err(ParseError::MissingGBXMagic);
69    }
70
71    let binary_header = GBXBinaryHeader {
72        version: u16::from_le_bytes((&buffer[3..5]).try_into().unwrap()),
73        class_id: u32::from_le_bytes((&buffer[9..13]).try_into().unwrap()),
74    };
75
76    let header_start = find_window(buffer, HEADER_START_TOKEN).ok_or(ParseError::HeaderNotFound);
77    let header_end = find_window(buffer, HEADER_END_TOKEN)
78        .ok_or(ParseError::HeaderNotFound)
79        .map(|x| x + HEADER_END_TOKEN.len());
80
81    let thumbnail_start =
82        find_window(buffer, THUMBNAIL_START_TOKEN).ok_or(ParseError::ThumbnailNotFound);
83    let thumbnail_end = find_window(buffer, THUMBNAIL_END_TOKEN)
84        .ok_or(ParseError::ThumbnailNotFound)
85        .map(|x| x + THUMBNAIL_END_TOKEN.len());
86
87    let mut header_xml = Vec::new();
88    let mut challenge_header = Err(ParseError::HeaderNotFound);
89    let mut replay_header = Err(ParseError::HeaderNotFound);
90
91    let hs = *header_start.as_ref().unwrap_or(&0);
92    let he = *header_end.as_ref().unwrap_or(&0);
93    if header_start.is_ok() && header_end.is_ok() {
94        header_xml.extend_from_slice(&buffer[hs..he]);
95        challenge_header = parse_challenge_header_xml(&buffer[hs..he]);
96        replay_header = parse_replay_xml(&buffer[hs..he])
97    }
98    let header_xml = String::from_utf8(header_xml).unwrap();
99
100    let thumbnail = if let (Ok(ts), Ok(te)) = (&thumbnail_start, &thumbnail_end) {
101        let mut thumbnail_data = Vec::new();
102        thumbnail_data.extend_from_slice(&buffer[*ts..*te]);
103        Some(JPEGData(thumbnail_data))
104    } else {
105        None
106    };
107
108    Ok(GBX {
109        origin: GBXOrigin::Buffer,
110        filesize: buffer.len(),
111        header_length: he - hs,
112        header_start: hs,
113        thumbnail_length: if let (Ok(te), Ok(ts)) = (&thumbnail_end, &thumbnail_start) {
114            Some(*te - *ts)
115        } else {
116            None
117        },
118        thumbnail_start: thumbnail_start.ok(),
119        thumbnail,
120        challenge_header: challenge_header.ok(),
121        replay_header: replay_header.ok(),
122        header_xml,
123        bin_header: binary_header,
124    })
125}