pub mod types;
use types::*;
#[macro_use]
extern crate nom;
use nom::{le_u16, le_u32};
use nom::Endianness::Little;
use std::time::Duration;
named!(null_terminated_string<String>, map_res!(terminated!(take_until!(&[0x00][..]), take!(1)), |buf: &[u8]| String::from_utf8(buf.to_vec())));
named!(get_byte<u8>, map!(take!(1), |bs| bs[0]));
named!(id1, tag!([0x1f]));
named!(id2, tag!([0x8b]));
named!(compression_method<CompressionMethod>, map!(take!(1), |b| CompressionMethod::from(b[0])));
named!(flags<Flags>, map!(take!(1), |b| Flags::from(b[0])));
named!(modified_time_as_secs_since_epoch<Duration>, map!(u32!(Little), |t| Duration::from_secs(t as u64)));
named!(extra_flags<ExtraFlags>, map!(take!(1), |b| ExtraFlags::from(b[0])));
named!(operating_system<OperatingSystem>, map!(take!(1), |b| OperatingSystem::from(b[0])));
named!(sub_field<SubField>, do_parse!(
id1: get_byte
>> id2: get_byte
>> data: length_data!(le_u16)
>>
(SubField { id1, id2, data })
));
named!(extra_field<ExtraField>, length_value!(le_u16, map!(many0!(sub_field), |sub_fields| ExtraField{ sub_fields })));
named!(original_filename<String>, call!(null_terminated_string));
named!(file_comment<String>, call!(null_terminated_string));
named!(header_crc16<u16>, call!(le_u16));
named!(footer_crc32<u32>, call!(le_u32));
named!(input_size<u32>, call!(le_u32));
named!(pub gzip_header<GzipHeader>, do_parse!(
id1
>> id2
>> compression_method: compression_method
>> flags: flags
>> modified_time_as_secs_since_epoch: modified_time_as_secs_since_epoch
>> extra_flags: extra_flags
>> operating_system: operating_system
>> extra_field: cond!(flags.fextra, call!(extra_field))
>> original_filename: cond!(flags.fname, call!(original_filename))
>> file_comment: cond!(flags.fcomment, call!(file_comment))
>> header_crc: cond!(flags.fhcrc, call!(header_crc16))
>>
(GzipHeader {
compression_method,
flags,
modified_time_as_secs_since_epoch,
extra_flags,
operating_system,
extra_field,
original_filename,
file_comment,
header_crc
})
));
named!(pub gzip_footer<GzipFooter>, do_parse!(
crc: footer_crc32
>> input_size: input_size
>> eof!()
>>
(GzipFooter { crc, input_size })
));
named!(pub gzip_file<GzipFile>, do_parse! (
header: gzip_header
>> gzip_file: map!(many_till!(call!(get_byte), call!(gzip_footer)), |tup: (Vec<u8>, GzipFooter)| {
GzipFile { header, footer: tup.1, compressed_blocks: tup.0.iter().map(|b| *b).collect() }
})
>>
(gzip_file)
));
#[cfg(test)]
mod tests {
extern crate nom;
extern crate byteorder;
use tests::nom::IResult::Done;
use super::*;
macro_rules! empty {
() => {
&b""[..];
}
}
macro_rules! test_null_terminated {
($func:ident) => {
let input = &b"This is null-terminated\0"[..];
let expected = String::from("This is null-terminated");
match $func(input) {
Done(_, actual) => assert_eq!(actual, expected),
unexpected => assert!(false, "Unable to parse null-terminated string, got back {:?}", unexpected),
}
}
}
macro_rules! test_u16 {
($func:ident) => {
use tests::byteorder::{ByteOrder, LittleEndian};
let mut buf: [u8; 2] = [0; 2];
for expected in 0x0000u16 .. 0xffffu16 {
LittleEndian::write_u16(&mut buf[0..2], expected);
assert_eq!($func(&buf[..]), Done(empty!(), expected));
}
}
}
macro_rules! test_u32 {
($func:ident) => {
use tests::byteorder::{ByteOrder, LittleEndian};
let samples: [u32; 6] = [0x00000000, 0xffffffff, 0xff00ff00, 0x00ff00ff, 0x01234567, 0x89abcdef];
let mut buf: [u8; 4] = [0; 4];
for expected in samples.iter() {
LittleEndian::write_u32(&mut buf[0..4], *expected);
assert_eq!($func(&buf[..]), Done(empty!(), *expected));
}
}
}
#[test]
fn test_id1() {
let input: &[u8] = &[0x1f][..];
assert_eq!(id1(input), Done(&b""[..], input));
}
#[test]
fn test_id2() {
let input: &[u8] = &[0x8b][..];
assert_eq!(id2(input), Done(&b""[..], input));
}
#[test]
fn test_compression_method() {
use CompressionMethod::*;
assert_eq!(compression_method(&[0x00][..]), Done(empty!(), Reserved0));
assert_eq!(compression_method(&[0x01][..]), Done(empty!(), Reserved1));
assert_eq!(compression_method(&[0x02][..]), Done(empty!(), Reserved2));
assert_eq!(compression_method(&[0x03][..]), Done(empty!(), Reserved3));
assert_eq!(compression_method(&[0x04][..]), Done(empty!(), Reserved4));
assert_eq!(compression_method(&[0x05][..]), Done(empty!(), Reserved5));
assert_eq!(compression_method(&[0x06][..]), Done(empty!(), Reserved6));
assert_eq!(compression_method(&[0x07][..]), Done(empty!(), Reserved7));
assert_eq!(compression_method(&[0x08][..]), Done(empty!(), Deflate));
for b in 0x09u8 .. 0xffu8 {
assert_eq!(compression_method(&[b][..]), Done(empty!(), Unknown));
}
}
#[test]
fn test_flags() {
for byte in 0b0000_0000 .. 0b0001_1111 {
let expected = Done(empty!(), Flags {
ftext: byte & 0b0000_0001 > 0,
fhcrc: byte & 0b0000_0010 > 0,
fextra: byte & 0b0000_0100 > 0,
fname: byte & 0b0000_1000 > 0,
fcomment: byte & 0b0001_0000 > 0,
});
assert_eq!(flags(&[byte][..]), expected);
}
}
#[test]
fn test_modified_time_as_secs_since_epoch() {
use tests::byteorder::{ByteOrder, LittleEndian};
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now();
let expected = Duration::from_secs(now.duration_since(UNIX_EPOCH).unwrap().as_secs()); let mut buffer: [u8; 4] = [0; 4];
LittleEndian::write_u32(&mut buffer[..], expected.as_secs() as u32);
match modified_time_as_secs_since_epoch(&buffer[..]) {
Done(remaining, actual) => {
assert_eq!(remaining, empty!());
assert_eq!(actual, expected);
}
_ => panic!("")
}
}
#[test]
fn test_extra_flags() {
assert_eq!(extra_flags(&[0x02u8][..]), Done(empty!(), ExtraFlags::MaximumCompression));
assert_eq!(extra_flags(&[0x04u8][..]), Done(empty!(), ExtraFlags::FastestAlgorithm));
for byte in 0x00u8 .. 0xffu8 {
let masked = byte & 0b1111_1001;
assert_eq!(extra_flags(&[masked][..]), Done(empty!(), ExtraFlags::Unknown));
}
}
#[test]
fn test_operating_system() {
use OperatingSystem::*;
assert_eq!(operating_system(&[0u8][..]), Done(empty!(), Fat));
assert_eq!(operating_system(&[1u8][..]), Done(empty!(), Amiga));
assert_eq!(operating_system(&[2u8][..]), Done(empty!(), Vms));
assert_eq!(operating_system(&[3u8][..]), Done(empty!(), Unix));
assert_eq!(operating_system(&[4u8][..]), Done(empty!(), VmCms));
assert_eq!(operating_system(&[5u8][..]), Done(empty!(), AtariTos));
assert_eq!(operating_system(&[6u8][..]), Done(empty!(), Hpfs));
assert_eq!(operating_system(&[7u8][..]), Done(empty!(), Macintosh));
assert_eq!(operating_system(&[8u8][..]), Done(empty!(), Zsystem));
assert_eq!(operating_system(&[9u8][..]), Done(empty!(), Cpm));
assert_eq!(operating_system(&[10u8][..]), Done(empty!(), Tops20));
assert_eq!(operating_system(&[11u8][..]), Done(empty!(), Ntfs));
assert_eq!(operating_system(&[12u8][..]), Done(empty!(), Qdos));
assert_eq!(operating_system(&[13u8][..]), Done(empty!(), AcornRiscos));
for b in 14u8 .. 0xffu8 {
assert_eq!(operating_system(&[b][..]), Done(empty!(), Unknown));
}
}
#[test]
fn test_sub_field() {
use tests::byteorder::{ByteOrder, LittleEndian};
let mut field: [u8; 8] = [0; 8];
for (pos, val) in "cp cpio".bytes().enumerate() {
field[pos] = val;
}
LittleEndian::write_u16(&mut field[2..4], 4);
assert_eq!(sub_field(&field[..]), Done(empty!(), SubField {
id1: 'c' as u8,
id2: 'p' as u8,
data: &b"cpio"[..],
}));
}
#[test]
fn test_extra_field() {
use tests::byteorder::{ByteOrder, LittleEndian};
let mut xfield: [u8; 42] = [0; 42];
for (pos, val) in " cp cpio.Ac acorn.KN keynote assertion".bytes().enumerate() {
xfield[pos] = val;
}
LittleEndian::write_u16(&mut xfield[0..2], 40);
LittleEndian::write_u16(&mut xfield[4..6], 5);
LittleEndian::write_u16(&mut xfield[13..15], 6);
LittleEndian::write_u16(&mut xfield[23..25], 17);
match extra_field(&xfield[..]) {
Done(_, actual) => {
assert!(actual.sub_fields.contains(&SubField {
id1: 'c' as u8,
id2: 'p' as u8,
data: &b"cpio."[..],
}));
assert!(actual.sub_fields.contains(&SubField {
id1: 'A' as u8,
id2: 'c' as u8,
data: &b"acorn."[..],
}));
assert!(actual.sub_fields.contains(&SubField {
id1: 'K' as u8,
id2: 'N' as u8,
data: &b"keynote assertion"[..],
}));
},
unexpected => assert!(false, "Unable to parse extra field, got back {:?}", unexpected),
}
}
#[test]
fn test_get_byte() {
for expected in 0x00u8 .. 0xffu8 {
assert_eq!(get_byte(&[expected][..]), Done(empty!(), expected));
}
}
#[test]
fn test_null_terminated_string() {
test_null_terminated!(null_terminated_string);
}
#[test]
fn test_original_filename() {
test_null_terminated!(original_filename);
}
#[test]
fn test_file_comment() {
test_null_terminated!(file_comment);
}
#[test]
fn test_header_crc16() {
test_u16!(header_crc16);
}
#[test]
fn test_footer_crc32() {
test_u32!(footer_crc32);
}
#[test]
fn test_input_size() {
test_u32!(input_size);
}
}