|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | //! Parser for cryptolib test fixtures. | 
|  | //! | 
|  | //! This file consumes `.c` files and performs a very basic parse on them | 
|  | //! to extract the test vector type and any annotations on it. | 
|  | //! | 
|  | //! This library expects the `.c` file to contain a struct definition | 
|  | //! conforming to the following reduced grammar, modulo whitespace. | 
|  | //! | 
|  | //! ```text | 
|  | //! // cryptotest:struct | 
|  | //! // Comments... | 
|  | //! typedef struct $name? { | 
|  | //!   // Comments... | 
|  | //!   $type $field; | 
|  | //!   // More fields... | 
|  | //! } $name; | 
|  | //! ``` | 
|  | //! | 
|  | //! Here, `// Comments...` is any number of single-line comments, `$field` is any | 
|  | //! valid C identifier, and `$type` is one of the following: | 
|  | //! - `bool` | 
|  | //! - `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t` | 
|  | //! - `int8_t`, `int16_t`, `int32_t`, `int64_t` | 
|  | //! - `size_t` | 
|  | //! - `const char *` (always a NUL-terminated string). | 
|  | //! | 
|  | //! In the future, this list will include enumerations defined in the cryptolib's ABI. | 
|  | //! | 
|  | //! Additionally, the following compound types are recognized: | 
|  | //! - `$Int field[N];`, where `$Int` is a non-`const char *` type above and N is an | 
|  | //!   integer literal. | 
|  | //! - `const $Int *field;`, for a variable-length array of integers. | 
|  | //! - `const char *const *field;`, for a variable-length array of strings. | 
|  | //! - `const $Int (*field)[N];`, for a variable-length array of fixed arrays of ints. | 
|  | //! | 
|  | //! Comments that do not begin with `// cryptotest:` are ignored; the rest are parsed | 
|  | //! as annotations. | 
|  |  | 
|  | use regex::Regex; | 
|  |  | 
|  | /// A test vector struct. | 
|  | /// | 
|  | /// See the [module docs](self) for information on the grammar. | 
|  | #[derive(Clone, Debug, PartialEq, Eq)] | 
|  | pub struct Struct { | 
|  | /// The name of the struct (as specified by the `typedef`). | 
|  | pub name: String, | 
|  | /// The struct's fields. | 
|  | pub fields: Vec<Field>, | 
|  | /// Any `// cryptotest:` annotations at the top of the struct. | 
|  | pub annots: Vec<Annotation>, | 
|  | } | 
|  |  | 
|  | /// A field of a [`Struct`]. | 
|  | #[derive(Clone, Debug, PartialEq, Eq)] | 
|  | pub struct Field { | 
|  | /// The name of the field. | 
|  | pub name: String, | 
|  | /// The type of the field. | 
|  | pub ty: FieldType, | 
|  | /// Any `// cryptotest:` annotations on this field. | 
|  | pub annots: Vec<Annotation>, | 
|  | } | 
|  |  | 
|  | /// A [`Field`]'s type. | 
|  | #[derive(Clone, Debug, PartialEq, Eq)] | 
|  | pub enum FieldType { | 
|  | /// An array field, represented by a raw `const` pointer. The length of | 
|  | /// the array must be specified by another field of the struct | 
|  | /// via `// cryptotest:len`. | 
|  | Array(Scalar), | 
|  | /// A scalar field, consisting of a single value. | 
|  | Scalar(Scalar), | 
|  | } | 
|  |  | 
|  | /// An integer type recognized by cryptotest. | 
|  | /// | 
|  | /// Not only does this include the `stdint.h` types, but it also includes cryptolib | 
|  | /// enum types known a priori to the parser. | 
|  | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | 
|  | pub enum Int { | 
|  | Bool, | 
|  | U8, | 
|  | U16, | 
|  | U32, | 
|  | U64, | 
|  | I8, | 
|  | I16, | 
|  | I32, | 
|  | I64, | 
|  | Size, | 
|  | } | 
|  |  | 
|  | impl Int { | 
|  | /// Parses an `Int` from its C name. | 
|  | pub fn from_name(name: &str) -> Result<Self, Error> { | 
|  | match name { | 
|  | "bool" => Ok(Int::Bool), | 
|  | "uint8_t" => Ok(Int::U8), | 
|  | "uint16_t" => Ok(Int::U16), | 
|  | "uint32_t" => Ok(Int::U32), | 
|  | "uint64_t" => Ok(Int::U64), | 
|  | "int8_t" => Ok(Int::I8), | 
|  | "int16_t" => Ok(Int::I16), | 
|  | "int32_t" => Ok(Int::I32), | 
|  | "int64_t" => Ok(Int::I64), | 
|  | "size_t" => Ok(Int::Size), | 
|  | _ => Err(Error::UnknownIntType(name)), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// A scalar type, i.e., types that do not have external size information. | 
|  | #[derive(Clone, Debug, PartialEq, Eq)] | 
|  | pub enum Scalar { | 
|  | /// An [`Int`] type. | 
|  | Int(Int), | 
|  | /// A vector type, i.e. a fixed-length array, of some kind of integer. | 
|  | Vec(Int, usize), | 
|  | /// A C-style (i.e., NUL-terminated) string, represented as a `const char*` | 
|  | /// pointer. | 
|  | CStr, | 
|  | } | 
|  |  | 
|  | /// An annotation recognized by the parser for specifying how cryptotest should | 
|  | /// interpret fields. | 
|  | #[derive(Clone, Debug, PartialEq, Eq)] | 
|  | pub enum Annotation { | 
|  | /// The `cryptotest:struct` directive at the top of a struct that is used to | 
|  | /// find where the struct starts. | 
|  | Struct, | 
|  | /// The `cryptotest:len` directive that must be present on each array | 
|  | /// field, which specifies which field specifies its length. The length present | 
|  | /// at runtime may be specified in units of single bits, bytes, or 32-bit words. | 
|  | Len(String, LenUnit), | 
|  | } | 
|  |  | 
|  | /// A length unit used by [`Annotation::Len`]. | 
|  | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | 
|  | pub enum LenUnit { | 
|  | Bits, | 
|  | Bytes, | 
|  | Words, | 
|  | } | 
|  |  | 
|  | // The "grammar" above is designed to be so simple we can parse it with a pile of | 
|  | // regular expressions. | 
|  | lazy_static::lazy_static! { | 
|  | static ref STRUCT_START: Regex = | 
|  | Regex::new(r"^typedef\s+struct\s+([a-zA-Z_][0-9a-zA-Z_]*)?\s*\{").unwrap(); | 
|  | static ref STRUCT_END: Regex = | 
|  | Regex::new(r"^}\s*([a-zA-Z_][0-9a-zA-Z_]*)\s*;").unwrap(); | 
|  | static ref INT_FIELD: Regex = | 
|  | Regex::new(r"^([a-zA-Z_][0-9a-zA-Z_]*)\s+([a-zA-Z_][0-9a-zA-Z_]*)\s*;").unwrap(); | 
|  | static ref STRING_FIELD: Regex = | 
|  | Regex::new(r"^const\s+char\s*\*\s*([a-zA-Z_][0-9a-zA-Z_]*)\s*;").unwrap(); | 
|  | static ref VECTOR_FIELD: Regex = | 
|  | Regex::new(r"^([a-zA-Z_][0-9a-zA-Z_]*)\s+([a-zA-Z_][0-9a-zA-Z_]*)\s*\[\s*(\w+)\s*\]\s*;").unwrap(); | 
|  | static ref INT_ARRAY_FIELD: Regex = | 
|  | Regex::new(r"^const\s+([a-zA-Z_][0-9a-zA-Z_]*)\s*\*\s*([a-zA-Z_][0-9a-zA-Z_]*)\s*;").unwrap(); | 
|  | static ref STRING_ARRAY_FIELD: Regex = | 
|  | Regex::new(r"^const\s+char\s*\*\s*const\s*\*\s*([a-zA-Z_][0-9a-zA-Z_]*)\s*;").unwrap(); | 
|  | static ref VECTOR_ARRAY_FIELD: Regex = | 
|  | Regex::new(r"^const\s+([a-zA-Z_][0-9a-zA-Z_]*)\s*\(\s*\*\s*([a-zA-Z_][0-9a-zA-Z_]*)\s*\)\s*\[\s*(\w+)\s*\]\s*;").unwrap(); | 
|  | } | 
|  |  | 
|  | const STRUCT_COMMENT: &str = "// cryptotest:struct\n"; | 
|  |  | 
|  | /// An error produced by [`Struct::parse()`]. | 
|  | #[derive(Debug, thiserror::Error, PartialEq, Eq)] | 
|  | pub enum Error<'c> { | 
|  | #[error("missing `// cryptotest:struct` comment")] | 
|  | NoStructFound, | 
|  | #[error("missing `typedef struct {{` prologue")] | 
|  | MissingStructPrologue, | 
|  | #[error("missing `}} name;` epilogue")] | 
|  | MissingStructEpilogue, | 
|  | #[error("unknown integer type: `{0}`")] | 
|  | UnknownIntType(&'c str), | 
|  | #[error(transparent)] | 
|  | BadInt(#[from] std::num::ParseIntError), | 
|  | #[error("expected a field but found something else")] | 
|  | ExpectedField, | 
|  | #[error("unknown array length unit: `{0}`")] | 
|  | UnknownLenUnit(&'c str), | 
|  | #[error("unknown annotation: `{0}`")] | 
|  | UnknownAnnotation(&'c str), | 
|  | } | 
|  |  | 
|  | impl Struct { | 
|  | /// Parses a `Struct` from the given C file. This will only parse the first | 
|  | /// `cryptolib:struct` encountered. | 
|  | pub fn parse(mut c_file: &str) -> Result<Struct, Error> { | 
|  | // First, find the struct. | 
|  | let struct_start = c_file.find(STRUCT_COMMENT).ok_or(Error::NoStructFound)?; | 
|  | c_file = &c_file[struct_start..]; | 
|  | let mut annots = Vec::new(); | 
|  | for comment in munch_comments(&mut c_file) { | 
|  | if let Some(annot) = parse_annotation(comment)? { | 
|  | annots.push(annot); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Next, find and tear off the struct header. | 
|  | let header = STRUCT_START | 
|  | .find(c_file) | 
|  | .ok_or(Error::MissingStructPrologue)?; | 
|  | c_file = &c_file[header.end()..]; | 
|  |  | 
|  | // Now, parse as many fields as we can. | 
|  | let mut fields = Vec::new(); | 
|  | loop { | 
|  | c_file = c_file.trim_start(); | 
|  | let mut annots = Vec::new(); | 
|  | for comment in munch_comments(&mut c_file) { | 
|  | if let Some(annot) = parse_annotation(comment)? { | 
|  | annots.push(annot); | 
|  | } | 
|  | } | 
|  |  | 
|  | if let Some(field) = INT_FIELD.captures(c_file) { | 
|  | c_file = &c_file[field.get(0).unwrap().end()..]; | 
|  | let int = field.get(1).unwrap().as_str(); | 
|  | let name = field.get(2).unwrap().as_str(); | 
|  | fields.push(Field { | 
|  | name: name.to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::from_name(int)?)), | 
|  | annots, | 
|  | }); | 
|  | } else if let Some(field) = STRING_FIELD.captures(c_file) { | 
|  | c_file = &c_file[field.get(0).unwrap().end()..]; | 
|  | let name = field.get(1).unwrap().as_str(); | 
|  | fields.push(Field { | 
|  | name: name.to_string(), | 
|  | ty: FieldType::Scalar(Scalar::CStr), | 
|  | annots, | 
|  | }); | 
|  | } else if let Some(field) = VECTOR_FIELD.captures(c_file) { | 
|  | c_file = &c_file[field.get(0).unwrap().end()..]; | 
|  | let int = field.get(1).unwrap().as_str(); | 
|  | let name = field.get(2).unwrap().as_str(); | 
|  | let count = field.get(3).unwrap().as_str(); | 
|  | fields.push(Field { | 
|  | name: name.to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Vec(Int::from_name(int)?, count.parse()?)), | 
|  | annots, | 
|  | }); | 
|  | } else if let Some(field) = INT_ARRAY_FIELD.captures(c_file) { | 
|  | c_file = &c_file[field.get(0).unwrap().end()..]; | 
|  | let int = field.get(1).unwrap().as_str(); | 
|  | let name = field.get(2).unwrap().as_str(); | 
|  | fields.push(Field { | 
|  | name: name.to_string(), | 
|  | ty: FieldType::Array(Scalar::Int(Int::from_name(int)?)), | 
|  | annots, | 
|  | }); | 
|  | } else if let Some(field) = STRING_ARRAY_FIELD.captures(c_file) { | 
|  | c_file = &c_file[field.get(0).unwrap().end()..]; | 
|  | let name = field.get(1).unwrap().as_str(); | 
|  | fields.push(Field { | 
|  | name: name.to_string(), | 
|  | ty: FieldType::Array(Scalar::CStr), | 
|  | annots, | 
|  | }); | 
|  | } else if let Some(field) = VECTOR_ARRAY_FIELD.captures(c_file) { | 
|  | c_file = &c_file[field.get(0).unwrap().end()..]; | 
|  | let int = field.get(1).unwrap().as_str(); | 
|  | let name = field.get(2).unwrap().as_str(); | 
|  | let count = field.get(3).unwrap().as_str(); | 
|  | fields.push(Field { | 
|  | name: name.to_string(), | 
|  | ty: FieldType::Array(Scalar::Vec(Int::from_name(int)?, count.parse()?)), | 
|  | annots, | 
|  | }); | 
|  | } else if c_file.starts_with('}') { | 
|  | break; | 
|  | } else { | 
|  | return Err(Error::ExpectedField); | 
|  | } | 
|  | } | 
|  |  | 
|  | let end = STRUCT_END | 
|  | .captures(c_file) | 
|  | .ok_or(Error::MissingStructEpilogue)?; | 
|  |  | 
|  | Ok(Struct { | 
|  | name: end.get(1).unwrap().as_str().to_string(), | 
|  | fields, | 
|  | annots, | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Strips any leading `//` comments from the start of `c_file`, and returns them, | 
|  | /// including the `//` prefix. | 
|  | fn munch_comments<'a>(c_file: &mut &'a str) -> Vec<&'a str> { | 
|  | let mut comments = Vec::new(); | 
|  | loop { | 
|  | *c_file = c_file.trim_start(); | 
|  | if !c_file.starts_with("//") { | 
|  | return comments; | 
|  | } | 
|  | let comment_end = c_file.find('\n').unwrap_or(c_file.len()); | 
|  | let (comment, rest) = c_file.split_at(comment_end); | 
|  | comments.push(comment); | 
|  | *c_file = rest; | 
|  | } | 
|  | } | 
|  |  | 
|  | fn parse_annotation(comment: &str) -> Result<Option<Annotation>, Error> { | 
|  | let comment = match comment.strip_prefix("// cryptotest:") { | 
|  | Some(comment) => comment, | 
|  | None => return Ok(None), | 
|  | }; | 
|  |  | 
|  | match comment.split(' ').collect::<Vec<_>>().as_slice() { | 
|  | ["struct"] => Ok(Some(Annotation::Struct)), | 
|  | ["len", field, units] => { | 
|  | let units = match *units { | 
|  | "bits" => LenUnit::Bits, | 
|  | "bytes" => LenUnit::Bytes, | 
|  | "words" => LenUnit::Words, | 
|  | _ => return Err(Error::UnknownLenUnit(units)), | 
|  | }; | 
|  | Ok(Some(Annotation::Len(field.to_string(), units))) | 
|  | } | 
|  | _ => Err(Error::UnknownAnnotation(comment)), | 
|  | } | 
|  | } | 
|  |  | 
|  | #[cfg(test)] | 
|  | mod tests { | 
|  | use super::*; | 
|  |  | 
|  | #[test] | 
|  | fn empty() { | 
|  | assert!(Struct::parse("").is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_prologue() { | 
|  | assert!(Struct::parse("// cryptotest:struct").is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_typedef() { | 
|  | assert!(Struct::parse("// cryptotest:struct\nstruct").is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn empty_struct() { | 
|  | assert_eq!( | 
|  | Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct {} foo_t; | 
|  | " | 
|  | ), | 
|  | Ok(Struct { | 
|  | name: "foo_t".to_string(), | 
|  | fields: vec![], | 
|  | annots: vec![Annotation::Struct], | 
|  | }) | 
|  | ) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_name() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct {}; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn int_field() { | 
|  | assert_eq!( | 
|  | Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct foo { | 
|  | // My cool field! | 
|  | uint32_t x; | 
|  | } foo_t; | 
|  | " | 
|  | ), | 
|  | Ok(Struct { | 
|  | name: "foo_t".to_string(), | 
|  | fields: vec![Field { | 
|  | name: "x".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::U32)), | 
|  | annots: vec![], | 
|  | }], | 
|  | annots: vec![Annotation::Struct], | 
|  | }) | 
|  | ) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_field_name() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | uint32_t; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn char_field() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | char is_not_allowed; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn bad_annotation() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | // cryptotest:omelette | 
|  | uint32_t something; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn all_int_fields() { | 
|  | assert_eq!( | 
|  | Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct foo { | 
|  | bool b; | 
|  | uint8_t u8; | 
|  | uint16_t u16; | 
|  | uint32_t u32; | 
|  | uint64_t u64; | 
|  | int8_t i8; | 
|  | int16_t i16; | 
|  | int32_t i32; | 
|  | int64_t i64; | 
|  | size_t sz; | 
|  | } foo_t; | 
|  | " | 
|  | ), | 
|  | Ok(Struct { | 
|  | name: "foo_t".to_string(), | 
|  | fields: vec![ | 
|  | Field { | 
|  | name: "b".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::Bool)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "u8".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::U8)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "u16".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::U16)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "u32".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::U32)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "u64".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::U64)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "i8".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::I8)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "i16".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::I16)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "i32".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::I32)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "i64".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::I64)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "sz".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::Size)), | 
|  | annots: vec![], | 
|  | }, | 
|  | ], | 
|  | annots: vec![Annotation::Struct], | 
|  | }) | 
|  | ) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn vec_field() { | 
|  | assert_eq!( | 
|  | Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct foo { | 
|  | uint32_t key[25]; | 
|  | uint8_t bytes[1]; | 
|  | bool flag; | 
|  | int8_t more_bytes[9001]; | 
|  | } foo_t; | 
|  | " | 
|  | ), | 
|  | Ok(Struct { | 
|  | name: "foo_t".to_string(), | 
|  | fields: vec![ | 
|  | Field { | 
|  | name: "key".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Vec(Int::U32, 25)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "bytes".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Vec(Int::U8, 1)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "flag".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::Bool)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "more_bytes".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Vec(Int::I8, 9001)), | 
|  | annots: vec![], | 
|  | }, | 
|  | ], | 
|  | annots: vec![Annotation::Struct], | 
|  | }) | 
|  | ) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_vec_field_name() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | uint32_t[4]; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn transposed_vec_field_name() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | uint32_t[4] foo; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn non_literal_vec_len() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | uint32_t foo[kLen]; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn cstr_field() { | 
|  | assert_eq!( | 
|  | Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct foo { | 
|  | bool flag; | 
|  | const char *plaintext; | 
|  | } foo_t; | 
|  | " | 
|  | ), | 
|  | Ok(Struct { | 
|  | name: "foo_t".to_string(), | 
|  | fields: vec![ | 
|  | Field { | 
|  | name: "flag".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::Bool)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "plaintext".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::CStr), | 
|  | annots: vec![], | 
|  | }, | 
|  | ], | 
|  | annots: vec![Annotation::Struct], | 
|  | }) | 
|  | ) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_cstr_const() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | char *mut_str; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_cstr_star() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | const char mut_str; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn cstr_right_const() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | char *const mut_str; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn int_array_field() { | 
|  | assert_eq!( | 
|  | Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct foo { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count words | 
|  | const uint32_t *ciphertext; | 
|  | } foo_t; | 
|  | " | 
|  | ), | 
|  | Ok(Struct { | 
|  | name: "foo_t".to_string(), | 
|  | fields: vec![ | 
|  | Field { | 
|  | name: "word_count".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::Size)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "ciphertext".to_string(), | 
|  | ty: FieldType::Array(Scalar::Int(Int::U32)), | 
|  | annots: vec![Annotation::Len("word_count".to_string(), LenUnit::Words)], | 
|  | }, | 
|  | ], | 
|  | annots: vec![Annotation::Struct], | 
|  | }) | 
|  | ) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_int_array_const() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count words | 
|  | uint32_t *mut; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn mystery_len() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count mystery | 
|  | uint32_t *mut; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn vec_array_field() { | 
|  | assert_eq!( | 
|  | Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct foo { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | const uint32_t (*keys)[32]; | 
|  | } foo_t; | 
|  | " | 
|  | ), | 
|  | Ok(Struct { | 
|  | name: "foo_t".to_string(), | 
|  | fields: vec![ | 
|  | Field { | 
|  | name: "word_count".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::Size)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "keys".to_string(), | 
|  | ty: FieldType::Array(Scalar::Vec(Int::U32, 32)), | 
|  | annots: vec![Annotation::Len("word_count".to_string(), LenUnit::Bytes)], | 
|  | }, | 
|  | ], | 
|  | annots: vec![Annotation::Struct], | 
|  | }) | 
|  | ) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_vec_array_const() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | uint32_t (*keys)[32]; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_vec_array_star() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | uint32_t (keys)[32]; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_vec_array_parens() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | uint32_t *keys[32]; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn cstr_array_field() { | 
|  | assert_eq!( | 
|  | Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct foo { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | const char* const* sonnets; | 
|  | } foo_t; | 
|  | " | 
|  | ), | 
|  | Ok(Struct { | 
|  | name: "foo_t".to_string(), | 
|  | fields: vec![ | 
|  | Field { | 
|  | name: "word_count".to_string(), | 
|  | ty: FieldType::Scalar(Scalar::Int(Int::Size)), | 
|  | annots: vec![], | 
|  | }, | 
|  | Field { | 
|  | name: "sonnets".to_string(), | 
|  | ty: FieldType::Array(Scalar::CStr), | 
|  | annots: vec![Annotation::Len("word_count".to_string(), LenUnit::Bytes)], | 
|  | }, | 
|  | ], | 
|  | annots: vec![Annotation::Struct], | 
|  | }) | 
|  | ) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_cstr_array_inner_const() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | char* const* sonnets; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_cstr_array_outer_const() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | const char** sonnets; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_cstr_array_outer_star() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | const char* const sonnets; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  |  | 
|  | #[test] | 
|  | fn missing_cstr_array_inner_star() { | 
|  | assert!(Struct::parse( | 
|  | " | 
|  | // cryptotest:struct | 
|  | typedef struct { | 
|  | size_t word_count; | 
|  | // cryptotest:len word_count bytes | 
|  | const char const* sonnets; | 
|  | }; | 
|  | " | 
|  | ) | 
|  | .is_err()) | 
|  | } | 
|  | } |