From f803af25a9749958f7d772fe0cf6897d184018e1 Mon Sep 17 00:00:00 2001 From: Szabolcs Berecz Date: Mon, 14 May 2018 10:55:09 +0200 Subject: [PATCH] Use nom for parsing. Fixes #3 --- Cargo.toml | 4 ++ src/header.rs | 11 +++- src/lib.rs | 3 ++ src/parsers.rs | 131 +++++++++++++++++++++++++++++++++++++++++++++++ tests/invalid.rs | 14 +++++ 5 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 src/parsers.rs create mode 100644 tests/invalid.rs diff --git a/Cargo.toml b/Cargo.toml index 9dcaa0b..91df5ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,7 @@ readme = "README.md" [dependencies] failure = "0.1.1" byteorder = "1.2.1" + +[dependencies.nom] +version = "~3.2.1" +features = ["verbose-errors"] diff --git a/src/header.rs b/src/header.rs index 92b2f65..f33ba32 100644 --- a/src/header.rs +++ b/src/header.rs @@ -2,6 +2,8 @@ extern crate byteorder; use self::byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use failure::Error; +use nom::IResult; +use parsers; use std::io::Cursor; /// Algorithm used for hashing the data. @@ -42,14 +44,14 @@ pub enum FileType { } /// SLEEP Protocol version. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum ProtocolVersion { /// The version specified as per the paper released in 2017-09. V0, } /// Structural representation of 32 byte SLEEP headers. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct Header { /// Type of file. pub file_type: FileType, @@ -76,6 +78,11 @@ impl Header { } } + /// Parses a 32 byte buffer slice into a valid Header. + pub fn from_bytes(buf: &[u8]) -> IResult<&[u8], Header> { + parsers::header(buf) + } + /// Parse a 32 byte buffer slice into a valid Header. pub fn from_vec(buffer: &[u8]) -> Result { ensure!(buffer.len() == 32, "buffer should be 32 bytes"); diff --git a/src/lib.rs b/src/lib.rs index 7f75cdf..b08c1c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,11 @@ #[macro_use] extern crate failure; +#[macro_use] +extern crate nom; mod header; +mod parsers; pub use header::*; diff --git a/src/parsers.rs b/src/parsers.rs new file mode 100644 index 0000000..e2d334a --- /dev/null +++ b/src/parsers.rs @@ -0,0 +1,131 @@ +use header::*; +use nom::{be_u16, be_u8, rest}; +use std::str; + +const HEADER_LENGTH: usize = 32; +const VERIFY_TRAILING_ZEROS: bool = true; + +named!( + file_type, + switch!(be_u8, + 0 => value!(FileType::BitField) | + 1 => value!(FileType::Signatures) | + 2 => value!(FileType::Tree) + ) +); + +named!( + protocol_version, + switch!(be_u8, + 0 => value!(ProtocolVersion::V0) + ) +); + +named!( + pub header
, + flat_map!( + take!(HEADER_LENGTH), + do_parse!( + tag!(b"\x05\x02\x57") >> + file_type: file_type >> + protocol_version: protocol_version >> + entry_size: be_u16 >> + + algorithm_len: be_u8 >> + algorithm: switch!(map_res!(take!(algorithm_len), str::from_utf8), + "BLAKE2b" => value!(HashType::BLAKE2b) | + "Ed25519" => value!(HashType::Ed25519) | + "" => value!(HashType::None) + ) >> + + verify!(rest, |bytes: &[u8]| { + let header_consumed = bytes.len() + algorithm_len as usize + 8 == HEADER_LENGTH; + let trailing_zeros = !VERIFY_TRAILING_ZEROS || bytes.iter().all(|&b| b == 0u8); + header_consumed && trailing_zeros + }) >> + + (Header { + file_type, + protocol_version, + entry_size, + hash_type: algorithm, + }) + ) + ) +); + +#[cfg(test)] +mod test { + use super::*; + + use nom::IResult::Done; + + #[test] + fn parse_file_type() { + assert_eq!( + file_type(b"\x00"), + Done(&[][..], FileType::BitField) + ); + assert_eq!( + file_type(b"\x01"), + Done(&[][..], FileType::Signatures) + ); + assert_eq!( + file_type(b"\x02"), + Done(&[][..], FileType::Tree) + ); + assert!(file_type(b"\xff").is_err()); + } + + #[test] + fn parse_header() { + fn mk_header(prefix: &[u8]) -> [u8; 32] { + let mut h = [0u8; 32]; + h[0..prefix.len()].clone_from_slice(prefix); + h + } + + assert_eq!( + header(&mk_header( + b"\x05\x02W\x01\x00\x00\x28\x07BLAKE2b" + )), + Done( + &[][..], + Header { + file_type: FileType::Signatures, + protocol_version: ProtocolVersion::V0, + entry_size: 40, + hash_type: HashType::BLAKE2b + } + ) + ); + assert_eq!( + header(&mk_header( + b"\x05\x02W\x01\x00\x00\x28\x07BLAKE2b" + )).unwrap() + .1 + .hash_type, + HashType::BLAKE2b + ); + assert_eq!( + header(&mk_header( + b"\x05\x02W\x01\x00\x00\x28\x07Ed25519" + )).unwrap() + .1 + .hash_type, + HashType::Ed25519 + ); + assert_eq!( + header(&mk_header(b"\x05\x02W\x01\x00\x00\x28\x00")) + .unwrap() + .1 + .hash_type, + HashType::None + ); + assert!(header(&mk_header(b"\x05\x02W\x01\x00\x00\x28\x01B")).is_err()); + assert!(header(&mk_header(b"\x05\x02W\x01\x00\x00\x28\x01B")).is_err()); + + let h = b"\x05\x02W\x01\x00\x00\x28\x19BLAKE2bXXXXXXXXXXXXXXXXXX"; + assert!(header(h).is_incomplete()); + } +} diff --git a/tests/invalid.rs b/tests/invalid.rs new file mode 100644 index 0000000..0914e64 --- /dev/null +++ b/tests/invalid.rs @@ -0,0 +1,14 @@ +extern crate sleep_parser; + +use sleep_parser::*; + +#[test] +fn issue_3() { + // https://github.com/datrs/sleep-parser/issues/3 + + let data = b"\x05\x02W\x01\x00\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xfb\x03p\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xbb9\xb0\xf5\xf5"; + assert!(Header::from_bytes(data).is_incomplete()); + + let data = b"\x05\x02W\x01\x00\x00\x00\x12\x12\x12\x00\x00S\xc3\xcf\x8a2\xcc\xd1\xce9\xc4K\x9343\x00602\xb5\x07"; + assert!(Header::from_bytes(data).is_err()); +}