ktest-parser 0.1.1

Parse KTest binaries (.ktest)
Documentation
//! ktest-parser is a utility to parse `.ktest` binaries which is the output of KLEE,
//! into a Rust friendly struct instead.
//!
//! ## KTest File Format Description
//! The KTest binary is structured as follows,
//! 1. A header
//! 2. KLEE arguments
//! 3. Symbolic arguments
//! 4. KTest objects.
//!
//! The following sections describes the detailed structure. Each new section starts at
//! byte 0 here, but since they follow each other the arguments start at byte 8 where
//! the header left off. But it is easier to describe the structure this way.
//!
//! ### Header
//! The header describes the magic number which is either "KTEST" or "BOUT/n". Then followed
//! by a version of the file format.
//!
//! | BYTE | NAME    | DESCRIPTION                 | LENGTH  |
//! |------|---------|-----------------------------|---------|
//! | 0..5 | HDR     | File format (default: KTEST)| 4 bytes |
//! | 5..8 | VERSION | File format version         | 4 bytes |
//!
//! ## Arguments
//! The arguments section describes the number of arguments and then a repeated section of
//! arguments where each argument is first described by a size and then its content of size length.
//!
//! ### Information
//! | BYTE | NAME   | DESCRIPTION                 | LENGTH  |
//! |------|--------|-----------------------------|---------|
//! | 0..4 | NUMARGS| Number of arguments         | 4 bytes |
//!
//! ### Argument
//! This is repeated for (NUMARGS) times.
//!
//! | BYTE      | NAME   | DESCRIPTION      | LENGTH       |
//! |-----------|--------|------------------|--------------|
//! | 0..4      | SIZE   | Size of argument | 4 bytes      |
//! | 4..(SIZE) | ARG    | An argument      | (SIZE) bytes |
//!
//! ## Symbolic arguments
//! Describes symbolic arguments.
//!
//! | BYTE | NAME    | DESCRIPTION | LENGTH  |
//! |------|---------|-------------|---------|
//! | 0..4 | ARGVS   | none        | 4 bytes |
//! | 4..8 | ARGVLEN | none        | 4 bytes |
//!
//! ## Objects
//! Like the arguments section, the first item is the number of objects. Then followed by
//! a repeated section of objects where each object is described by a size and then its content
//! of size length.
//! ### Information
//! | BYTE | NAME      | DESCRIPTION       | LENGTH  |
//! |------|-----------|-------------------|---------|
//! | 0..4 | NUMOBJECTS| Number of objects | 4 bytes |
//!
//! ### Object
//! This is repeated for (NUMOBJECTS) times.
//!
//! | BYTE      | NAME   | DESCRIPTION    | LENGTH       |
//! |-----------|--------|----------------|--------------|
//! | 0..4      | SIZE   | Size of object | 4 bytes      |
//! | 4..(SIZE) | OBJECT | An object      | (SIZE) bytes |

extern crate nom;

use anyhow::{anyhow, Result};
use nom::{
    branch::alt,
    bytes::complete::{tag, take},
    combinator::map,
    multi::{count, many0},
    number::complete::be_u32,
    sequence::tuple,
    IResult,
};
use serde::{Deserialize, Serialize};

type KTestTuple = (u32, Vec<String>, u32, u32, u32, Vec<KTestObject>);

/// Contains information about the generated test vector on a symbolic object.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct KTestObject {
    /// The name given to the symbolic object when calling `klee_make_symbolic`
    pub name: String,
    /// The size of the generated test vector
    pub num_bytes: u32,
    /// The test vector data generated by KLEE
    pub bytes: Vec<u8>,
}

/// A representation of the KTest file format.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct KTest {
    /// KTest file format version
    pub version: u32,
    /// KLEE arguments
    pub args: Vec<String>,
    /// Symbolic arguments
    pub sym_argvs: u32,
    pub sym_argv_len: u32,
    /// The number of KTestObjects
    pub num_objects: u32,
    /// A list of the KTestObjects
    pub objects: Vec<KTestObject>,
}

/// Parses a .ktest file and returns a KTest. Will return an error if any
/// parts of the binary is illegal.
pub fn parse_ktest(input: &[u8]) -> Result<KTest> {
    let (_, (version, args, sym_argvs, sym_argv_len, num_objects, objects)) =
        match parse_ktest_binary(input) {
            Ok(res) => res,
            Err(e) => {
                return Err(anyhow!("Could not parse binary {:}", e));
            }
        };
    Ok(KTest {
        version,
        args,
        sym_argvs,
        sym_argv_len,
        num_objects,
        objects,
    })
}

fn parse_ktest_binary(input: &[u8]) -> IResult<&[u8], KTestTuple> {
    let (input, (_magic, version, args)) =
        tuple((magic_number, extract_be_u32, extract_arguments))(input)?;
    let (input, (sym_argvs, sym_argv_len)) = if version > 2 {
        extract_sym_args(input)?
    } else {
        (input, (0, 0))
    };
    let (input, objects) = extract_objects(input)?;
    Ok((
        input,
        (
            version,
            args,
            sym_argvs,
            sym_argv_len,
            objects.len() as u32,
            objects,
        ),
    ))
}

/// Parses the KTest magic number.
fn magic_number(input: &[u8]) -> IResult<&[u8], &[u8]> {
    alt((tag(b"KTEST"), tag(b"BOUT\n")))(input)
}

fn extract_be_u32(input: &[u8]) -> IResult<&[u8], u32> {
    be_u32(input)
}

// Parses the arguments in the KTest file
fn extract_arguments(input: &[u8]) -> IResult<&[u8], Vec<String>> {
    let (input, num) = extract_be_u32(input)?;
    count(extract_argument, num as usize)(input)
}

fn extract_argument(input: &[u8]) -> IResult<&[u8], String> {
    let (input, size) = extract_be_u32(input)?;
    map(take(size), |arg: &[u8]| {
        String::from_utf8(arg.to_owned()).unwrap()
    })(input)
}

fn extract_sym_args(input: &[u8]) -> IResult<&[u8], (u32, u32)> {
    tuple((extract_be_u32, extract_be_u32))(input)
}

fn extract_objects(input: &[u8]) -> IResult<&[u8], Vec<KTestObject>> {
    let (input, _num) = extract_be_u32(input)?;
    many0(extract_object)(input)
}

// Does not work yet
fn extract_object(input: &[u8]) -> IResult<&[u8], KTestObject> {
    let (input, size_name) = extract_be_u32(input)?;
    let (input, name) = map(take(size_name), |name: &[u8]| {
        String::from_utf8(name.to_owned()).unwrap()
    })(input)?;

    let (input, size_bytes) = extract_be_u32(input)?;
    let (input, bytes) = take(size_bytes)(input)?;

    Ok((
        input,
        KTestObject {
            name: name,
            num_bytes: size_bytes,
            bytes: bytes.to_vec(),
        },
    ))
}

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

    #[test]
    fn invalid_magic_number() {
        let magic = [0, 0, 0, 0, 0];
        let res = magic_number(&magic);
        assert_eq!(res.is_err(), true);
    }

    #[test]
    fn valid_magic_numbers() {
        let ktest = b"KTEST";
        let bout = b"BOUT\n";
        assert_eq!(magic_number(ktest).is_ok(), true);
        assert_eq!(magic_number(bout).is_ok(), true);
    }
}