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
//! Package containing the parser for GBX Files.
//! The datatypes used are defined in the [gbx](crate::gbx) module, with [GBX](crate::gbx::GBX) being the main one.

pub mod challenge;
pub mod replay;

use self::challenge::parse_challenge_header_xml;
use self::replay::parse_replay_xml;

use super::*;

use std::convert::TryInto;
use std::io;
use std::io::Read;
use std::{fs::File, num::ParseIntError};

const HEADER_START_TOKEN: &[u8] = "<header ".as_bytes();
const HEADER_END_TOKEN: &[u8] = "</header>".as_bytes();

const THUMBNAIL_START_TOKEN: &[u8] = &[0xFF, 0xD8, 0xFF];
const THUMBNAIL_END_TOKEN: &[u8] = &[0xFF, 0xD9];

#[derive(Debug)]
pub enum ParseError {
    MissingGBXMagic,
    FileTooShort,
    HeaderNotFound,
    ThumbnailNotFound,
    XMLParseError(xml::reader::Error),
    HeaderValueError(ParseIntError),
    HeaderTryIntoEnumError(String),
    IOError(io::Error),
    Unknown,
}

/// Util. function to find first match of a sub-slice in a slice
fn find_window(buf: &[u8], needle: &[u8]) -> Option<usize> {
    buf.windows(needle.len()).position(|w| w == needle)
}

/// Reads the contents from `filename` and parses them identically to [parse_from_buffer](parse_from_buffer).
///
/// Note, that the [GBXOrigin](GBXOrigin) of the returned [GBX](GBX).[GBXHeader](GBXHeader) will be `File{path:<filepath>}`.
pub fn parse_from_file(filename: &str) -> Result<GBX, ParseError> {
    let mut buffer = Vec::new();
    let mut f = File::open(filename).map_err(ParseError::IOError)?;
    f.read_to_end(&mut buffer).map_err(ParseError::IOError)?;
    let mut gbx = parse_from_buffer(&buffer)?;
    gbx.origin = GBXOrigin::File {
        path: String::from(filename),
    };
    Ok(gbx)
}

/// Parses the given slice of bytes as if it was a GBX file.
///
/// This function assumes the XML header included in the GBX file is valid UTF8, and will panic
/// otherwise.
/// As of now the actual map-data is not extracted.
///
/// If you want to parse a file directly see [parse_from_file](parse_from_file).
pub fn parse_from_buffer(buffer: &[u8]) -> Result<GBX, ParseError> {
    if buffer.len() < 3 {
        return Err(ParseError::FileTooShort);
    }

    if &buffer[0..3] != b"GBX" {
        return Err(ParseError::MissingGBXMagic);
    }

    let binary_header = GBXBinaryHeader {
        version: u16::from_le_bytes((&buffer[3..5]).try_into().unwrap()),
        class_id: u32::from_le_bytes((&buffer[9..13]).try_into().unwrap()),
    };

    let header_start = find_window(buffer, HEADER_START_TOKEN).ok_or(ParseError::HeaderNotFound);
    let header_end = find_window(buffer, HEADER_END_TOKEN)
        .ok_or(ParseError::HeaderNotFound)
        .map(|x| x + HEADER_END_TOKEN.len());

    let thumbnail_start =
        find_window(buffer, THUMBNAIL_START_TOKEN).ok_or(ParseError::ThumbnailNotFound);
    let thumbnail_end = find_window(buffer, THUMBNAIL_END_TOKEN)
        .ok_or(ParseError::ThumbnailNotFound)
        .map(|x| x + THUMBNAIL_END_TOKEN.len());

    let mut header_xml = Vec::new();
    let mut challenge_header = Err(ParseError::HeaderNotFound);
    let mut replay_header = Err(ParseError::HeaderNotFound);

    let hs = *header_start.as_ref().unwrap_or(&0);
    let he = *header_end.as_ref().unwrap_or(&0);
    if header_start.is_ok() && header_end.is_ok() {
        header_xml.extend_from_slice(&buffer[hs..he]);
        challenge_header = parse_challenge_header_xml(&buffer[hs..he]);
        replay_header = parse_replay_xml(&buffer[hs..he])
    }
    let header_xml = String::from_utf8(header_xml).unwrap();

    let thumbnail = if let (Ok(ts), Ok(te)) = (&thumbnail_start, &thumbnail_end) {
        let mut thumbnail_data = Vec::new();
        thumbnail_data.extend_from_slice(&buffer[*ts..*te]);
        Some(JPEGData(thumbnail_data))
    } else {
        None
    };

    Ok(GBX {
        origin: GBXOrigin::Buffer,
        filesize: buffer.len(),
        header_length: he - hs,
        header_start: hs,
        thumbnail_length: if let (Ok(te), Ok(ts)) = (&thumbnail_end, &thumbnail_start) {
            Some(*te - *ts)
        } else {
            None
        },
        thumbnail_start: thumbnail_start.ok(),
        thumbnail,
        challenge_header: challenge_header.ok(),
        replay_header: replay_header.ok(),
        header_xml,
        bin_header: binary_header,
    })
}