mkups 0.1.0

Toolkit for creating, applying, and inspecting .ups patches
Documentation
//! Functions for parsing UPS patches. A UPS patch starts with a [header](crate::data::Header),
//! followed by one or more [hunks](crate::data::Hunk), followed by a
//! [trailer](crate::data::Trailer).

use std::fmt::Write;
use nom::{IResult,
    bytes::complete::{take, take_until, tag},
    error::{context, ErrorKind, FromExternalError},
};
   
use crate::data::{Header, Hunk, Trailer};

pub type Error<'a> = nom::error::VerboseError<&'a [u8]>;
type Res<'a, T> = IResult<&'a [u8], T, Error<'a>>;

/// Parses the UPS header.
pub fn header(i: &[u8]) -> Res<Header> {
    let (i, _) = context("magic", tag("UPS1"))(i)?;
    let (i, src_len) = context("src_len", vlq)(i)?;
    let (i, dst_len) = context("dst_len", vlq)(i)?;
    Ok((i, Header {
        src_len,
        dst_len,
    }))
}

/// Parses an UPS hunk. You should call this function repeatedly, until only the trailer remains.
/// You can compare the slice length to [`Trailer::LENGTH`](crate::data::Trailer::LENGTH) to check
/// this.
pub fn hunk(i: &[u8]) -> Res<Hunk> {
    context("hunk", hunk1)(i)
}

fn hunk1(i: &[u8]) -> Res<Hunk> {
    let (i, skip) = context("skip", vlq)(i)?;
    let (i, xor) = context("data", take_until(&b"\x00"[..]))(i)?;
    let (i, _) = context("terminator", tag(&b"\x00"[..]))(i)?;
    Ok((i, Hunk {
        skip,
        xor,
    }))
}

/// Parses the UPS trailer.
pub fn trailer(i: &[u8]) -> Res<Trailer> {
    let (i, src_crc) = context("src_crc", crc)(i)?;
    let (i, dst_crc) = context("dst_crc", crc)(i)?;
    let (i, ups_crc) = context("ups_crc", crc)(i)?;
    Ok((i, Trailer {
        src_crc,
        dst_crc,
        ups_crc,
    }))
}

fn vlq(i: &[u8]) -> Res<usize> {
    let mut res = 0usize;
    let mut i = i;
    let mut bs: &[u8];
    let mut shift = 0;
    loop {
        (i, bs) = take(1u8)(i)?;
        let octet: usize = bs[0] as usize;
        if (bs[0] & 0x80) != 0 {
           res = res.checked_add((octet & 0x7f) << shift)
                .ok_or_else(|| fail(i, "VLQ overflow"))?;
            break;
        }
        res = res.checked_add((octet | 0x80) << shift)
            .ok_or_else(|| fail(i, "VLQ overflow"))?;
        shift += 7;
        if shift > 56 {
            return Err(fail(i, "VLQ shift overflow"));
        }
    }
    Ok((i, res))
}

fn crc(i: &[u8]) -> Res<u32> {
    let (i, crc) = take(4u8)(i)?;
    Ok((i, u32::from_le_bytes(crc.try_into().unwrap())))
}

fn fail<'a>(input: &'a [u8], msg: &'static str) -> nom::Err<Error<'a>> {
    nom::Err::Error(Error::from_external_error(input, ErrorKind::Fail, msg))
}

/// Formats a parsing error (badly).
pub fn format_error(err: Error) -> String {
    let mut result = String::new();
    for error in err.errors.iter() {
        let mut slice = error.0;
        if slice.len() > 16 {
            slice = &slice[0..16];
        }
        writeln!(result, "At {:?}", slice).unwrap();
        match error.1 {
            nom::error::VerboseErrorKind::Context(c) => writeln!(result, "  In {}", c),
            nom::error::VerboseErrorKind::Char(c) => writeln!(result, "  Expected '{}'", c),
            nom::error::VerboseErrorKind::Nom(n) => writeln!(result, "  Error: {:?}", n),
        }.unwrap();
    }
    result
}

#[test]
fn vlq_test() {
    assert_eq!(vlq(&[0x80]), Ok((&[][..], 0)));
    assert_eq!(vlq(&[0x81]), Ok((&[][..], 1)));
    assert_eq!(vlq(&[0x81, 0x48]), Ok((&[0x48][..], 1)));
    assert_eq!(vlq(&[0x10, 0x20, 0x30, 0x81, 0x48]), Ok((&[0x48][..], 0x4c5090)));
    
    assert_eq!(vlq(&[0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x48]).is_ok(), false);
}

#[test]
fn header_test() {
    assert_eq!(header(&b"UPS1\x81\x82"[..]), Ok((&[][..], Header {
        src_len: 1,
        dst_len: 2,
    })));
    assert_eq!(header(&b"HELO\x81\x82"[..]).is_ok(), false);
}

#[test]
fn hunk_test() {
    assert_eq!(hunk(&b"\x81\xaa\xbb\xcc\x00"[..]), Ok((&[][..], Hunk {
        skip: 1,
        xor: &[0xaa, 0xbb, 0xcc][..],
    })));
    assert_eq!(hunk(&b"\x81\x82\x83\x84"[..]).is_ok(), false);
    assert_eq!(hunk(&b"\x7f"[..]).is_ok(), false);
}

#[test]
fn trailer_test() {
    assert_eq!(trailer(&b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"[..]), Ok((&[][..], Trailer {
        src_crc: 0x04030201,
        dst_crc: 0x08070605,
        ups_crc: 0x0c0b0a09,
    })));
    assert_eq!(trailer(&b"\x81\x82\x83\x84\x81\x82\x83\x84"[..]).is_ok(), false);
}

#[test]
fn patch_test() {
    let i = &b"UPS1\xe0\xf0\x85FOO\x00\x8aBAR\x00abcdEFGHijkl"[..];
    let (i, header) = header(i).expect("parsing header failed");
    let (i, hunk1) = hunk(i).expect("parsing hunk 1 failed");
    let (i, hunk2) = hunk(i).expect("parsing hunk 2 failed");
    let (i, trailer) = trailer(i).expect("parsing trailer failed");
    assert_eq!(header, Header {
        src_len: 0x60,
        dst_len: 0x70,
    });
    assert_eq!(hunk1, Hunk {
        skip: 5,
        xor: &b"FOO"[..],
    });
    assert_eq!(hunk2, Hunk {
        skip: 10,
        xor: &b"BAR"[..],
    });
    assert_eq!(trailer, Trailer {
        src_crc: 1684234849,
        dst_crc: 1212630597,
        ups_crc: 1818978921
    });
    assert_eq!(i.len(), 0);
}