teehistorian 0.12.0

Teehistorian parser for DDNet
Documentation
use crate::error::ErrorKind;
use nom::bytes::streaming::{take, take_while};
use nom::number::streaming::be_u8;
use nom::sequence::terminated;
use nom::Err::Failure;
use nom::IResult;
use uuid::Uuid;

pub fn twint(input: &[u8]) -> IResult<&[u8], i32, ErrorKind> {
    let mut result = 0;
    let mut len = 1;

    let mut next = be_u8(input)?;
    let mut src = next.1;
    let sign = ((src >> 6) & 1) as i32;

    result |= (src & 0b0011_1111) as i32;

    for i in 0..4 {
        if src & 0b1000_0000 == 0 {
            break;
        }
        next = be_u8(next.0)?;
        src = next.1;
        len += 1;
        if i == 3 && src & 0b1111_0000 != 0 {
            return Err(Failure(ErrorKind::NonZeroIntPadding));
        }
        result |= ((src & 0b0111_1111) as i32) << (6 + 7 * i);
    }

    if len > 1 && src == 0b0000_0000 {
        return Err(Failure(ErrorKind::OverlongIntEncoding));
    }

    result ^= -sign;

    Ok((next.0, result))
}

/// null terminated string
pub fn cstring(input: &[u8]) -> IResult<&[u8], &[u8], ErrorKind> {
    // TODO: return Error instead of panicing when encountering invalid utf-8
    terminated(take_while(|c| c != b'\0'), take(1usize))(input)
}

pub fn uuid(input: &[u8]) -> IResult<&[u8], Uuid, ErrorKind> {
    let (input, slice) = take(16usize)(input)?;
    // from_slice only returns an Err, if we don't give a slice of length 16,
    // and take(16) guarantees a length of 16
    Ok((input, Uuid::from_slice(slice).unwrap()))
}

#[cfg(test)]
mod test {
    use super::*;

    fn assert_twint(input: &[u8], remaining: &[u8], result: i32) {
        assert_eq!(twint(input), Ok((remaining, result)));
    }
    fn assert_twint_err(input: &[u8]) {
        assert!(twint(input).is_err());
    }
    #[test]
    fn twint_0() {
        assert_twint(b"\x00", b"", 0)
    }
    #[test]
    fn twint_1() {
        assert_twint(b"\x01", b"", 1)
    }
    #[test]
    fn twint_63() {
        assert_twint(b"\x3f", b"", 63)
    }
    #[test]
    fn twint_m1() {
        assert_twint(b"\x40", b"", -1)
    }
    #[test]
    fn twint_64() {
        assert_twint(b"\x80\x01", b"", 64)
    }
    #[test]
    fn twint_m65() {
        assert_twint(b"\xc0\x01", b"", -65)
    }
    #[test]
    fn twint_m64() {
        assert_twint(b"\x7f", b"", -64)
    }
    #[test]
    fn twint_min() {
        assert_twint(b"\xff\xff\xff\xff\x0f", b"", i32::min_value())
    }
    #[test]
    fn twint_max() {
        assert_twint(b"\xbf\xff\xff\xff\x0f", b"", i32::max_value())
    }
    #[test]
    fn twint_empty() {
        assert_twint_err(b"")
    }
    #[test]
    fn twint_extend_empty() {
        assert_twint_err(b"\x80")
    }

    #[test]
    fn uuid_teehistorian() {
        assert_eq!(
            Ok((
                &b""[..],
                Uuid::parse_str("699db17b-8efb-34ff-b1d8-da6f60c15dd1").unwrap()
            )),
            uuid(b"\x69\x9d\xb1\x7b\x8e\xfb\x34\xff\xb1\xd8\xda\x6f\x60\xc1\x5d\xd1"),
        );
    }

    fn assert_cstring(input: &[u8], remaining: &[u8], result: &[u8]) {
        assert_eq!(cstring(input), Ok((remaining, result)));
    }
    fn assert_cstring_err(input: &[u8]) {
        assert!(cstring(input).is_err());
    }
    #[test]
    fn cstr_empty() {
        assert_cstring(b"\0", b"", b"")
    }
    #[test]
    fn cstr_none() {
        assert_cstring_err(b"")
    }
    #[test]
    fn cstr_no_nul() {
        assert_cstring_err(b"abc")
    }
    #[test]
    fn cstr_rest1() {
        assert_cstring(b"abc\0def", b"def", b"abc")
    }
    #[test]
    fn cstr_rest2() {
        assert_cstring(b"abc\0", b"", b"abc")
    }
    #[test]
    fn cstr_rest3() {
        assert_cstring(b"abc\0\0", b"\0", b"abc")
    }
    #[test]
    fn cstr_rest4() {
        assert_cstring(b"\0\0", b"\0", b"")
    }
}