diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/asn1 | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rust/src/asn1')
-rw-r--r-- | rust/src/asn1/mod.rs | 393 | ||||
-rw-r--r-- | rust/src/asn1/parse_rules.rs | 326 |
2 files changed, 719 insertions, 0 deletions
diff --git a/rust/src/asn1/mod.rs b/rust/src/asn1/mod.rs new file mode 100644 index 0000000..4b77b0c --- /dev/null +++ b/rust/src/asn1/mod.rs @@ -0,0 +1,393 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! ASN.1 parser module. + +use der_parser::ber::{parse_ber_recursive, BerObject, BerObjectContent, Tag}; +use nom7::Err; +use std::convert::TryFrom; + +mod parse_rules; +use parse_rules::DetectAsn1Data; + +/// Container for parsed Asn1 objects +#[derive(Debug)] +pub struct Asn1<'a>(Vec<BerObject<'a>>); + +/// Errors possible during decoding of Asn1 +#[derive(Debug)] +enum Asn1DecodeError { + InvalidKeywordParameter, + MaxFrames, + BerError(Err<der_parser::error::BerError>), +} + +/// Enumeration of Asn1 checks +#[derive(Debug, PartialEq)] +enum Asn1Check { + OversizeLength, + BitstringOverflow, + DoubleOverflow, + MaxDepth, +} + +impl<'a> Asn1<'a> { + /// Checks each BerObject contained in self with the provided detection + /// data, returns the first successful match if one occurs + fn check(&self, ad: &DetectAsn1Data) -> Option<Asn1Check> { + for obj in &self.0 { + let res = Asn1::check_object_recursive(obj, ad, ad.max_frames as usize); + if res.is_some() { + return res; + } + } + + None + } + + fn check_object_recursive( + obj: &BerObject, ad: &DetectAsn1Data, max_depth: usize, + ) -> Option<Asn1Check> { + // Check stack depth + if max_depth == 0 { + return Some(Asn1Check::MaxDepth); + } + + // Check current object + let res = Asn1::check_object(obj, ad); + if res.is_some() { + return res; + } + + // Check sub-nodes + for node in obj.ref_iter() { + let res = Asn1::check_object_recursive(node, ad, max_depth - 1); + if res.is_some() { + return res; + } + } + + None + } + + /// Checks a BerObject and subnodes against the Asn1 checks + fn check_object(obj: &BerObject, ad: &DetectAsn1Data) -> Option<Asn1Check> { + // get length + // Note that if length is indefinite (BER), this will return None + let len = obj.header.length().definite().ok()?; + // oversize_length will check if a node has a length greater than + // the user supplied length + if let Some(oversize_length) = ad.oversize_length { + if len > oversize_length as usize + || obj.content.as_slice().unwrap_or(&[]).len() > oversize_length as usize + { + return Some(Asn1Check::OversizeLength); + } + } + + // bitstring_overflow check a malformed option where the number of bits + // to ignore is greater than the length decoded (in bits) + if ad.bitstring_overflow + && (obj.header.is_universal() + && obj.header.tag() == Tag::BitString + && obj.header.is_primitive()) + { + if let BerObjectContent::BitString(bits, _v) = &obj.content { + if len > 0 + && *bits as usize > len.saturating_mul(8) + { + return Some(Asn1Check::BitstringOverflow); + } + } + } + + // double_overflow checks a known issue that affects the MSASN1 library + // when decoding double/real types. If the encoding is ASCII, + // and the buffer is greater than 256, the array is overflown + if ad.double_overflow + && (obj.header.is_universal() + && obj.header.tag() == Tag::RealType + && obj.header.is_primitive()) + { + if let Ok(data) = obj.content.as_slice() { + if len > 0 + && !data.is_empty() + && data[0] & 0xC0 == 0 + && (len > 256 || data.len() > 256) + { + return Some(Asn1Check::DoubleOverflow); + } + } + } + + None + } + + fn from_slice(input: &'a [u8], ad: &DetectAsn1Data) -> Result<Asn1<'a>, Asn1DecodeError> { + let mut results = Vec::new(); + let mut rest = input; + + // while there's data to process + while !rest.is_empty() { + let max_depth = ad.max_frames as usize; + + if results.len() >= max_depth { + return Err(Asn1DecodeError::MaxFrames); + } + + let res = parse_ber_recursive(rest, max_depth); + + match res { + Ok((new_rest, obj)) => { + results.push(obj); + + rest = new_rest; + } + // If there's an error, bail + Err(_) => { + // silent error as this could fail + // on non-asn1 or fragmented packets + break; + } + } + } + + Ok(Asn1(results)) + } +} + +/// Decodes Asn1 objects from an input + length while applying the offset +/// defined in the asn1 keyword options +fn asn1_decode<'a>( + buffer: &'a [u8], buffer_offset: u32, ad: &DetectAsn1Data, +) -> Result<Asn1<'a>, Asn1DecodeError> { + // Get offset + let offset = if let Some(absolute_offset) = ad.absolute_offset { + absolute_offset + } else if let Some(relative_offset) = ad.relative_offset { + // relative offset in regards to the last content match + + // buffer_offset (u32) + relative_offset (i32) => offset (u16) + u16::try_from({ + if relative_offset > 0 { + buffer_offset + .checked_add(u32::try_from(relative_offset)?) + .ok_or(Asn1DecodeError::InvalidKeywordParameter)? + } else { + buffer_offset + .checked_sub(u32::try_from(-relative_offset)?) + .ok_or(Asn1DecodeError::InvalidKeywordParameter)? + } + }) + .or(Err(Asn1DecodeError::InvalidKeywordParameter))? + } else { + 0 + }; + + // Make sure we won't read past the end or front of the buffer + if offset as usize >= buffer.len() { + return Err(Asn1DecodeError::InvalidKeywordParameter); + } + + // Get slice from buffer at offset + let slice = &buffer[offset as usize..]; + + Asn1::from_slice(slice, ad) +} + +/// Attempt to parse a Asn1 object from input, and return a pointer +/// to the parsed object if successful, null on failure +/// +/// # Safety +/// +/// input must be a valid buffer of at least input_len bytes +/// pointer must be freed using `rs_asn1_free` +#[no_mangle] +pub unsafe extern "C" fn rs_asn1_decode( + input: *const u8, input_len: u16, buffer_offset: u32, ad_ptr: *const DetectAsn1Data, +) -> *mut Asn1<'static> { + if input.is_null() || input_len == 0 || ad_ptr.is_null() { + return std::ptr::null_mut(); + } + + let slice = build_slice!(input, input_len as usize); + + let ad = &*ad_ptr ; + + let res = asn1_decode(slice, buffer_offset, ad); + + match res { + Ok(asn1) => Box::into_raw(Box::new(asn1)), + Err(_e) => std::ptr::null_mut(), + } +} + +/// Free a Asn1 object allocated by Rust +/// +/// # Safety +/// +/// ptr must be a valid object obtained using `rs_asn1_decode` +#[no_mangle] +pub unsafe extern "C" fn rs_asn1_free(ptr: *mut Asn1) { + if ptr.is_null() { + return; + } + drop(Box::from_raw(ptr)); +} + +/// This function implements the detection of the following options: +/// - oversize_length +/// - bitstring_overflow +/// - double_overflow +/// +/// # Safety +/// +/// ptr must be a valid object obtained using `rs_asn1_decode` +/// ad_ptr must be a valid object obtained using `rs_detect_asn1_parse` +/// +/// Returns 1 if any of the options match, 0 if not +#[no_mangle] +pub unsafe extern "C" fn rs_asn1_checks(ptr: *const Asn1, ad_ptr: *const DetectAsn1Data) -> u8 { + if ptr.is_null() || ad_ptr.is_null() { + return 0; + } + + let asn1 = &*ptr; + let ad = &*ad_ptr; + + match asn1.check(ad) { + Some(_check) => 1, + None => 0, + } +} + +impl From<std::num::TryFromIntError> for Asn1DecodeError { + fn from(_e: std::num::TryFromIntError) -> Asn1DecodeError { + Asn1DecodeError::InvalidKeywordParameter + } +} + +impl From<Err<der_parser::error::BerError>> for Asn1DecodeError { + fn from(e: Err<der_parser::error::BerError>) -> Asn1DecodeError { + Asn1DecodeError::BerError(e) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + // Example from the specification X.690-0207 Appendix A.3 + static ASN1_A3: &[u8] = b"\x60\x81\x85\x61\x10\x1A\x04John\x1A\x01 \ + P\x1A\x05Smith\xA0\x0A\x1A\x08Director \ + \x42\x01\x33\xA1\x0A\x43\x0819710917 \ + \xA2\x12\x61\x10\x1A\x04Mary\x1A\x01T\x1A\x05 \ + Smith\xA3\x42\x31\x1F\x61\x11\x1A\x05Ralph\x1A\x01 \ + T\x1A\x05Smith\xA0\x0A\x43\x0819571111 \ + \x31\x1F\x61\x11\x1A\x05Susan\x1A\x01B\x1A\x05 \ + Jones\xA0\x0A\x43\x0819590717"; + + /// Ensure that the checks work when they should + #[test_case("oversize_length 132 absolute_offset 0", ASN1_A3, DetectAsn1Data { + oversize_length: Some(132), + absolute_offset: Some(0), + ..Default::default() + }, Some(Asn1Check::OversizeLength); "Test oversize_length rule (match)" )] + #[test_case("oversize_length 133 absolute_offset 0", ASN1_A3, DetectAsn1Data { + oversize_length: Some(133), + absolute_offset: Some(0), + ..Default::default() + }, None; "Test oversize_length rule (non-match)" )] + #[test_case("bitstring_overflow, absolute_offset 0", + /* tagnum bitstring, primitive, and as universal tag, + length = 1 octet, but the next octet specify to ignore the last 256 bits */ + b"\x03\x01\xFF", + DetectAsn1Data { + bitstring_overflow: true, + absolute_offset: Some(0), + ..Default::default() + }, Some(Asn1Check::BitstringOverflow); "Test bitstring_overflow rule (match)" )] + #[test_case("bitstring_overflow, absolute_offset 0", + /* tagnum bitstring, primitive, and as universal tag, + length = 1 octet, but the next octet specify to ignore the last 7 bits */ + b"\x03\x01\x07", + DetectAsn1Data { + bitstring_overflow: true, + absolute_offset: Some(0), + ..Default::default() + }, None; "Test bitstring_overflow rule (non-match)" )] + #[test_case("double_overflow, absolute_offset 0", + { + static TEST_BUF: [u8; 261] = { + let mut b = [0x05; 261]; + /* universal class, primitive type, tag_num = 9 (Data type Real) */ + b[0] = 0x09; + /* length, definite form, 2 octets */ + b[1] = 0x82; + /* length is the sum of the following octets (257): */ + b[2] = 0x01; + b[3] = 0x01; + + b + }; + + &TEST_BUF + }, + DetectAsn1Data { + double_overflow: true, + absolute_offset: Some(0), + ..Default::default() + }, Some(Asn1Check::DoubleOverflow); "Test double_overflow rule (match)" )] + #[test_case("double_overflow, absolute_offset 0", + { + static TEST_BUF: [u8; 261] = { + let mut b = [0x05; 261]; + /* universal class, primitive type, tag_num = 9 (Data type Real) */ + b[0] = 0x09; + /* length, definite form, 2 octets */ + b[1] = 0x82; + /* length is the sum of the following octets (256): */ + b[2] = 0x01; + b[3] = 0x00; + + b + }; + + &TEST_BUF + }, + DetectAsn1Data { + double_overflow: true, + absolute_offset: Some(0), + ..Default::default() + }, None; "Test double_overflow rule (non-match)" )] + fn test_checks( + rule: &str, asn1_buf: &'static [u8], expected_data: DetectAsn1Data, + expected_check: Option<Asn1Check>, + ) { + // Parse rule + let (_rest, ad) = parse_rules::asn1_parse_rule(rule).unwrap(); + assert_eq!(expected_data, ad); + + // Decode + let asn1 = Asn1::from_slice(asn1_buf, &ad).unwrap(); + + // Run checks + let result = asn1.check(&ad); + assert_eq!(expected_check, result); + } +} diff --git a/rust/src/asn1/parse_rules.rs b/rust/src/asn1/parse_rules.rs new file mode 100644 index 0000000..540734c --- /dev/null +++ b/rust/src/asn1/parse_rules.rs @@ -0,0 +1,326 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom7::branch::alt; +use nom7::bytes::complete::tag; +use nom7::character::complete::{digit1, multispace0, multispace1}; +use nom7::combinator::{map_res, opt, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::sequence::{separated_pair, tuple}; +use nom7::{Err, IResult}; +use std::ffi::CStr; +use std::os::raw::c_char; + +const ASN1_DEFAULT_MAX_FRAMES: u16 = 30; + +/// Parse the asn1 keyword and return a pointer to a `DetectAsn1Data` +/// containing the parsed options, returns null on failure +/// +/// # Safety +/// +/// pointer must be free'd using `rs_detect_asn1_free` +#[no_mangle] +pub unsafe extern "C" fn rs_detect_asn1_parse(input: *const c_char) -> *mut DetectAsn1Data { + if input.is_null() { + return std::ptr::null_mut(); + } + + let arg = match CStr::from_ptr(input).to_str() { + Ok(arg) => arg, + _ => { + return std::ptr::null_mut(); + } + }; + + match asn1_parse_rule(arg) { + Ok((_rest, data)) => { + let mut data = data; + + // Get configuration value + if let Some(max_frames) = crate::conf::conf_get("asn1-max-frames") { + if let Ok(v) = max_frames.parse::<u16>() { + data.max_frames = v; + } else { + SCLogError!("Could not parse asn1-max-frames: {}", max_frames); + return std::ptr::null_mut(); + }; + } + + Box::into_raw(Box::new(data)) + } + Err(e) => { + SCLogError!("Malformed asn1 argument: {}", e.to_string()); + std::ptr::null_mut() + } + } +} + +/// Free a `DetectAsn1Data` object allocated by Rust +/// +/// # Safety +/// +/// ptr must be a valid object obtained using `rs_detect_asn1_parse` +#[no_mangle] +pub unsafe extern "C" fn rs_detect_asn1_free(ptr: *mut DetectAsn1Data) { + if ptr.is_null() { + return; + } + drop(Box::from_raw(ptr)); +} + +/// Struct to hold parsed asn1 keyword options +#[derive(Debug, PartialEq, Eq)] +pub struct DetectAsn1Data { + pub bitstring_overflow: bool, + pub double_overflow: bool, + pub oversize_length: Option<u32>, + pub absolute_offset: Option<u16>, + pub relative_offset: Option<i32>, + pub max_frames: u16, +} + +impl Default for DetectAsn1Data { + fn default() -> DetectAsn1Data { + DetectAsn1Data { + bitstring_overflow: false, + double_overflow: false, + oversize_length: None, + absolute_offset: None, + relative_offset: None, + max_frames: ASN1_DEFAULT_MAX_FRAMES, + } + } +} + +fn parse_u32_number(input: &str) -> IResult<&str, u32> { + map_res(digit1, |digits: &str| digits.parse::<u32>())(input) +} + +fn parse_u16_number(input: &str) -> IResult<&str, u16> { + map_res(digit1, |digits: &str| digits.parse::<u16>())(input) +} + +fn parse_i32_number(input: &str) -> IResult<&str, i32> { + let (rest, negate) = opt(tag("-"))(input)?; + let (rest, d) = map_res(digit1, |s: &str| s.parse::<i32>())(rest)?; + let n = if negate.is_some() { -1 } else { 1 }; + Ok((rest, d * n)) +} + +/// Parse asn1 keyword options +pub(super) fn asn1_parse_rule(input: &str) -> IResult<&str, DetectAsn1Data> { + // If nothing to parse, return + if input.is_empty() { + return Err(Err::Error(make_error( + input, + ErrorKind::Eof, + ))); + } + + // Rule parsing functions + fn bitstring_overflow(i: &str) -> IResult<&str, &str> { + tag("bitstring_overflow")(i) + } + + fn double_overflow(i: &str) -> IResult<&str, &str> { + tag("double_overflow")(i) + } + + fn oversize_length(i: &str) -> IResult<&str, (&str, u32)> { + separated_pair(tag("oversize_length"), multispace1, parse_u32_number)(i) + } + + fn absolute_offset(i: &str) -> IResult<&str, (&str, u16)> { + separated_pair(tag("absolute_offset"), multispace1, parse_u16_number)(i) + } + + fn relative_offset(i: &str) -> IResult<&str, (&str, i32)> { + separated_pair( + tag("relative_offset"), + multispace1, + verify(parse_i32_number, |v| { + *v >= -i32::from(std::u16::MAX) && *v <= i32::from(std::u16::MAX) + }), + )(i) + } + + let mut data = DetectAsn1Data::default(); + + let mut rest = input; + + // Parse the input and set data + while !rest.is_empty() { + let ( + new_rest, + ( + _, + bitstring_overflow, + double_overflow, + oversize_length, + absolute_offset, + relative_offset, + _, + ), + ) = tuple(( + opt(multispace0), + opt(bitstring_overflow), + opt(double_overflow), + opt(oversize_length), + opt(absolute_offset), + opt(relative_offset), + opt(alt((multispace1, tag(",")))), + ))(rest)?; + + if bitstring_overflow.is_some() { + data.bitstring_overflow = true; + } else if double_overflow.is_some() { + data.double_overflow = true; + } else if let Some((_, v)) = oversize_length { + data.oversize_length = Some(v); + } else if let Some((_, v)) = absolute_offset { + data.absolute_offset = Some(v); + } else if let Some((_, v)) = relative_offset { + data.relative_offset = Some(v); + } else { + return Err(Err::Error(make_error( + rest, + ErrorKind::Verify, + ))); + } + + rest = new_rest; + } + + Ok((rest, data)) +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + // Test oversize_length + #[test_case("oversize_length 1024", + DetectAsn1Data { oversize_length: Some(1024), ..Default::default()}; + "check that we parse oversize_length correctly")] + #[test_case("oversize_length 0", + DetectAsn1Data { oversize_length: Some(0), ..Default::default()}; + "check lower bound on oversize_length")] + #[test_case("oversize_length -1", + DetectAsn1Data::default() => panics r#"Error { input: "oversize_length -1", code: Verify }"#; + "check under lower bound on oversize_length")] + #[test_case("oversize_length 4294967295", + DetectAsn1Data { oversize_length: Some(4294967295), ..Default::default()}; + "check upper bound on oversize_length")] + #[test_case("oversize_length 4294967296", + DetectAsn1Data::default() => panics r#"Error { input: "oversize_length 4294967296", code: Verify }"#; + "check over upper bound on oversize_length")] + #[test_case("oversize_length", + DetectAsn1Data::default() => panics r#"Error { input: "oversize_length", code: Verify }"#; + "check that we fail if the needed arg oversize_length is not given")] + // Test absolute_offset + #[test_case("absolute_offset 1024", + DetectAsn1Data { absolute_offset: Some(1024), ..Default::default()}; + "check that we parse absolute_offset correctly")] + #[test_case("absolute_offset 0", + DetectAsn1Data { absolute_offset: Some(0), ..Default::default()}; + "check lower bound on absolute_offset")] + #[test_case("absolute_offset -1", + DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset -1", code: Verify }"#; + "check under lower bound on absolute_offset")] + #[test_case("absolute_offset 65535", + DetectAsn1Data { absolute_offset: Some(65535), ..Default::default()}; + "check upper bound on absolute_offset")] + #[test_case("absolute_offset 65536", + DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset 65536", code: Verify }"#; + "check over upper bound on absolute_offset")] + #[test_case("absolute_offset", + DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset", code: Verify }"#; + "check that we fail if the needed arg absolute_offset is not given")] + // Test relative_offset + #[test_case("relative_offset 1024", + DetectAsn1Data { relative_offset: Some(1024), ..Default::default()}; + "check that we parse relative_offset correctly")] + #[test_case("relative_offset -65535", + DetectAsn1Data { relative_offset: Some(-65535), ..Default::default()}; + "check lower bound on relative_offset")] + #[test_case("relative_offset -65536", + DetectAsn1Data::default() => panics r#"Error { input: "relative_offset -65536", code: Verify }"#; + "check under lower bound on relative_offset")] + #[test_case("relative_offset 65535", + DetectAsn1Data { relative_offset: Some(65535), ..Default::default()}; + "check upper bound on relative_offset")] + #[test_case("relative_offset 65536", + DetectAsn1Data::default() => panics r#"Error { input: "relative_offset 65536", code: Verify }"#; + "check over upper bound on relative_offset")] + #[test_case("relative_offset", + DetectAsn1Data::default() => panics r#"Error { input: "relative_offset", code: Verify }"#; + "check that we fail if the needed arg relative_offset is not given")] + // Test bitstring_overflow + #[test_case("bitstring_overflow", + DetectAsn1Data { bitstring_overflow: true, ..Default::default()}; + "check that we parse bitstring_overflow correctly")] + // Test double_overflow + #[test_case("double_overflow", + DetectAsn1Data { double_overflow: true, ..Default::default()}; + "check that we parse double_overflow correctly")] + // Test combination of params + #[test_case("oversize_length 1024, relative_offset 10", + DetectAsn1Data { oversize_length: Some(1024), relative_offset: Some(10), + ..Default::default()}; + "check for combinations of keywords (comma seperated)")] + #[test_case("oversize_length 1024 absolute_offset 10", + DetectAsn1Data { oversize_length: Some(1024), absolute_offset: Some(10), + ..Default::default()}; + "check for combinations of keywords (space seperated)")] + #[test_case("oversize_length 1024 absolute_offset 10, bitstring_overflow", + DetectAsn1Data { bitstring_overflow: true, oversize_length: Some(1024), + absolute_offset: Some(10), ..Default::default()}; + "check for combinations of keywords (space/comma seperated)")] + #[test_case( + "double_overflow, oversize_length 1024 absolute_offset 10,\n bitstring_overflow", + DetectAsn1Data { double_overflow: true, bitstring_overflow: true, + oversize_length: Some(1024), absolute_offset: Some(10), + ..Default::default()}; + "1. check for combinations of keywords (space/comma/newline seperated)")] + #[test_case( + "\n\t double_overflow, oversize_length 1024 relative_offset 10,\n bitstring_overflow", + DetectAsn1Data { double_overflow: true, bitstring_overflow: true, + oversize_length: Some(1024), relative_offset: Some(10), + ..Default::default()}; + "2. check for combinations of keywords (space/comma/newline seperated)")] + // Test empty + #[test_case("", + DetectAsn1Data::default() => panics r#"Error { input: "", code: Eof }"#; + "test that we break with a empty string")] + // Test invalid rules + #[test_case("oversize_length 1024, some_other_param 360", + DetectAsn1Data::default() => panics r#"Error { input: " some_other_param 360", code: Verify }"#; + "test that we break on invalid options")] + #[test_case("oversize_length 1024,,", + DetectAsn1Data::default() => panics r#"Error { input: ",", code: Verify }"#; + "test that we break on invalid format (missing option)")] + #[test_case("bitstring_overflowabsolute_offset", + DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset", code: Verify }"#; + "test that we break on invalid format (missing separator)")] + fn test_asn1_parse_rule(input: &str, expected: DetectAsn1Data) { + let (rest, res) = asn1_parse_rule(input).unwrap(); + + assert_eq!(0, rest.len()); + assert_eq!(expected, res); + } +} |