Skip to content

Commit

Permalink
Add new features 'trace' and 'debug' to print the debug state when pa…
Browse files Browse the repository at this point in the history
…rsing
  • Loading branch information
chifflier committed Mar 13, 2024
1 parent da83b86 commit 4a68bef
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 20 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ default = ["std"]
bigint = ["num-bigint"]
bits = ["bitvec"]
datetime = ["time"]
debug = ["colored"]
serialize = ["cookie-factory"]
std = []
trace = ["debug"]

[dependencies]
asn1-rs-derive = { version="0.5", path="./derive" }
asn1-rs-impl = { version="0.2", path="./impl" }
bitvec = { version="1.0", optional=true }
colored = { version="2.0", optional=true }
cookie-factory = { version="0.3.0", optional=true }
displaydoc = "0.2.2"
nom = { version="7.0", default_features=false, features=["std"] }
Expand Down
28 changes: 20 additions & 8 deletions src/asn1_types/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use alloc::borrow::Cow;
use alloc::string::String;

Check warning on line 4 in src/asn1_types/any.rs

View workflow job for this annotation

GitHub Actions / Check (nightly)

the item `String` is imported redundantly
use core::convert::{TryFrom, TryInto};

use self::debug::trace;

/// The `Any` object is not strictly an ASN.1 type, but holds a generic description of any object
/// that could be encoded.
///
Expand Down Expand Up @@ -320,21 +322,31 @@ impl<'a> Any<'a> {
}
}

pub(crate) fn parse_ber_any(input: &[u8]) -> ParseResult<Any> {
let (i, header) = Header::from_ber(input)?;
let (i, data) = BerParser::get_object_content(i, &header, MAX_RECURSION)?;
Ok((i, Any { header, data }))
}

pub(crate) fn parse_der_any(input: &[u8]) -> ParseResult<Any> {
let (i, header) = Header::from_der(input)?;
// X.690 section 10.1: The definite form of length encoding shall be used
header.length.assert_definite()?;
let (i, data) = DerParser::get_object_content(i, &header, MAX_RECURSION)?;
Ok((i, Any { header, data }))
}

impl<'a> FromBer<'a> for Any<'a> {
#[inline]
fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> {
let (i, header) = Header::from_ber(bytes)?;
let (i, data) = BerParser::get_object_content(i, &header, MAX_RECURSION)?;
Ok((i, Any { header, data }))
trace("Any", parse_ber_any, bytes)
}
}

impl<'a> FromDer<'a> for Any<'a> {
#[inline]
fn from_der(bytes: &'a [u8]) -> ParseResult<Self> {
let (i, header) = Header::from_der(bytes)?;
// X.690 section 10.1: The definite form of length encoding shall be used
header.length.assert_definite()?;
let (i, data) = DerParser::get_object_content(i, &header, MAX_RECURSION)?;
Ok((i, Any { header, data }))
trace("Any", parse_der_any, bytes)
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/asn1_types/sequence/sequence_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use core::convert::TryFrom;
use core::iter::FromIterator;
use core::ops::{Deref, DerefMut};

use self::debug::trace;

/// The `SEQUENCE OF` object is an ordered list of homogeneous types.
///
/// This type implements `Deref<Target = [T]>` and `DerefMut<Target = [T]>`, so all methods
Expand Down Expand Up @@ -131,7 +133,8 @@ where
E: From<Error>,
{
fn from_der(bytes: &'a [u8]) -> ParseResult<Self, E> {
let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
let (rem, any) =
trace(core::any::type_name::<Self>(), parse_der_any, bytes).map_err(Err::convert)?;
any.header
.assert_tag(Self::TAG)
.map_err(|e| nom::Err::Error(e.into()))?;

Check failure on line 140 in src/asn1_types/sequence/sequence_of.rs

View workflow job for this annotation

GitHub Actions / Check (nightly)

unnecessary qualification
Expand Down
5 changes: 4 additions & 1 deletion src/asn1_types/sequence/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::*;
use alloc::vec::Vec;
use core::convert::TryFrom;

use self::debug::trace;

// // XXX this compiles but requires bound TryFrom :/
// impl<'a, 'b, T> TryFrom<&'b Any<'a>> for Vec<T>
// where
Expand Down Expand Up @@ -94,7 +96,8 @@ where
E: From<Error>,
{
fn from_der(bytes: &'a [u8]) -> ParseResult<Self, E> {
let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
let (rem, any) =
trace(core::any::type_name::<Self>(), parse_der_any, bytes).map_err(Err::convert)?;
any.header
.assert_tag(Self::TAG)
.map_err(|e| nom::Err::Error(e.into()))?;

Check failure on line 103 in src/asn1_types/sequence/vec.rs

View workflow job for this annotation

GitHub Actions / Check (nightly)

unnecessary qualification
Expand Down
5 changes: 4 additions & 1 deletion src/asn1_types/set/btreeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::*;
use alloc::collections::BTreeSet;
use core::convert::TryFrom;

use self::debug::trace;

impl<T> Tagged for BTreeSet<T> {
const TAG: Tag = Tag::Set;
}
Expand Down Expand Up @@ -44,7 +46,8 @@ where
E: From<Error>,
{
fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
let (rem, any) =
trace(core::any::type_name::<Self>(), Any::from_der, bytes).map_err(Err::convert)?;
any.tag()
.assert_eq(Self::TAG)
.map_err(|e| nom::Err::Error(e.into()))?;

Check failure on line 53 in src/asn1_types/set/btreeset.rs

View workflow job for this annotation

GitHub Actions / Check (nightly)

unnecessary qualification
Expand Down
5 changes: 4 additions & 1 deletion src/asn1_types/set/hashset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::collections::HashSet;
use std::convert::TryFrom;
use std::hash::Hash;

use self::debug::trace;

impl<T> Tagged for HashSet<T> {
const TAG: Tag = Tag::Set;
}
Expand Down Expand Up @@ -46,7 +48,8 @@ where
E: From<Error>,
{
fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
let (rem, any) =
trace(core::any::type_name::<Self>(), Any::from_der, bytes).map_err(Err::convert)?;
any.tag()
.assert_eq(Self::TAG)
.map_err(|e| nom::Err::Error(e.into()))?;
Expand Down
5 changes: 4 additions & 1 deletion src/asn1_types/set/set_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use core::convert::TryFrom;
use core::iter::FromIterator;
use core::ops::{Deref, DerefMut};

use self::debug::trace;

/// The `SET OF` object is an unordered list of homogeneous types.
///
/// This type implements `Deref<Target = [T]>` and `DerefMut<Target = [T]>`, so all methods
Expand Down Expand Up @@ -131,7 +133,8 @@ where
E: From<Error>,
{
fn from_der(bytes: &'a [u8]) -> ParseResult<Self, E> {
let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
let (rem, any) =
trace(core::any::type_name::<Self>(), Any::from_der, bytes).map_err(Err::convert)?;
any.header
.assert_tag(Self::TAG)
.map_err(|e| nom::Err::Error(e.into()))?;
Expand Down
178 changes: 178 additions & 0 deletions src/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use crate::ParseResult;

#[macro_export]
macro_rules! debug_eprintln {
($msg: expr, $( $args:expr ),* ) => {
#[cfg(feature = "debug")]
{
let s = $msg.to_string().green();
eprintln!("{} {}", s, format!($($args),*));
}
};
}

#[macro_export]
macro_rules! trace_eprintln {
($msg: expr, $( $args:expr ),* ) => {
#[cfg(feature = "trace")]
{
let s = $msg.to_string().green();
eprintln!("{} {}", s, format!($($args),*));
}
};
}

#[cfg(feature = "debug")]
fn eprintln_hex_dump(bytes: &[u8], max_len: usize) {
use core::cmp::min;
use nom::HexDisplay;

let m = min(bytes.len(), max_len);
eprint!("{}", &bytes[..m].to_hex(16));
if bytes.len() > max_len {
eprintln!("... <continued>");
}
}

#[cfg(not(feature = "debug"))]
#[inline]
pub fn trace<'a, T, E, F>(_msg: &str, f: F, input: &'a [u8]) -> ParseResult<'a, T, E>
where
F: Fn(&'a [u8]) -> ParseResult<'a, T, E>,
{
f(input)
}

#[cfg(feature = "debug")]
pub fn trace<'a, T, E, F>(msg: &str, f: F, input: &'a [u8]) -> ParseResult<'a, T, E>
where
F: Fn(&'a [u8]) -> ParseResult<'a, T, E>,
E: core::fmt::Debug,
{
use colored::Colorize;

trace_eprintln!(
msg,
"⤷ input (len={}, type={})",
input.len(),
core::any::type_name::<T>()
);
let res = f(input);
match &res {
Ok((_rem, _)) => {
trace_eprintln!(
msg,
"⤶ Parsed {} bytes, {} remaining",
input.len() - _rem.len(),
_rem.len()
);
}
Err(e) => {
debug_eprintln!(msg, "↯ Parsing failed: {}", e.to_string().red());
eprintln_hex_dump(input, 16);
}
}
res
}

#[cfg(test)]
mod tests {
use crate::{Any, FromDer};
use hex_literal::hex;

#[cfg(feature = "debug")]
#[test]
fn debug_from_der_any() {
assert!(Any::from_der(&hex!("01 01 ff")).is_ok());
}

#[cfg(feature = "debug")]
#[test]
fn debug_from_der_failures() {
use crate::Sequence;

// parsing any failed
eprintln!("--");
assert!(u16::from_der(&hex!("ff 00")).is_err());
// Indefinite length
eprintln!("--");
assert!(u16::from_der(&hex!("30 80 00 00")).is_err());
// DER constraints failed
eprintln!("--");
assert!(bool::from_der(&hex!("01 01 7f")).is_err());
// Incomplete sequence
eprintln!("--");
let _ = Sequence::from_der(&hex!("30 81 04 00 00"));
}

#[cfg(feature = "debug")]
#[test]
fn debug_from_der_sequence() {
// parsing OK, recursive
let input = &hex!("30 05 02 03 01 00 01");
let (rem, result) = <Vec<u32>>::from_der(input).expect("parsing failed");
assert_eq!(&result, &[65537]);
assert_eq!(rem, &[]);
}

#[cfg(feature = "debug")]
#[test]
fn debug_from_der_sequence_of() {
use crate::SequenceOf;
// parsing failure (wrong type)
let input = &hex!("30 03 01 01 00");
eprintln!("--");
let _ = <SequenceOf<u32>>::from_der(input).expect_err("parsing should fail");
eprintln!("--");
let _ = <Vec<u32>>::from_der(input).expect_err("parsing should fail");
}

#[cfg(feature = "debug")]
#[test]
fn debug_from_der_set_of() {
use crate::SetOf;
use alloc::collections::BTreeSet;

// parsing failure (wrong type)
let input = &hex!("31 03 01 01 00");
eprintln!("--");
let _ = <SetOf<u32>>::from_der(input).expect_err("parsing should fail");
eprintln!("--");
let _ = <BTreeSet<u32>>::from_der(input).expect_err("parsing should fail");
}

/// Check that it is possible to implement an error without fmt::Debug
#[cfg(not(feature = "debug"))]
#[test]
fn from_der_error_not_impl_debug() {
use crate::{CheckDerConstraints, DerAutoDerive};
use core::convert::TryFrom;

struct MyError;

struct A;

impl CheckDerConstraints for A {
fn check_constraints(_any: &Any) -> crate::Result<()> {
Ok(())
}
}
impl<'a> TryFrom<Any<'a>> for A {
type Error = MyError;

fn try_from(_value: Any<'a>) -> Result<Self, Self::Error> {
Ok(A)
}
}
impl DerAutoDerive for A {}

impl From<crate::Error> for MyError {
fn from(_value: crate::Error) -> Self {
Self
}
}

let res = A::from_der(&hex!("02 01 00"));
assert!(res.is_ok());
}
}
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub enum Error {
/// Invalid Date or Time
InvalidDateTime,

/// DER Failed constraint
/// DER Failed constraint: {0:?}
DerConstraintFailed(DerConstraint),

/// Requesting borrowed data from a temporary object
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ mod asn1_types;
mod ber;
mod class;
mod datetime;
mod debug;
mod derive;
mod error;
mod header;
Expand Down
27 changes: 21 additions & 6 deletions src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::error::*;
use crate::debug::trace;
use crate::{debug_eprintln, error::*, parse_der_any};
use crate::{Any, Class, Explicit, Implicit, Tag, TaggedParser};
#[cfg(feature = "debug")]
use colored::Colorize;
use core::convert::{TryFrom, TryInto};
#[cfg(feature = "std")]
use std::io::Write;
Expand Down Expand Up @@ -181,11 +184,23 @@ where
E: From<Error>,
{
fn from_der(bytes: &'a [u8]) -> ParseResult<T, E> {
// Note: Any::from_der checks than length is definite
let (i, any) = Any::from_der(bytes).map_err(nom::Err::convert)?;
<T as CheckDerConstraints>::check_constraints(&any)
.map_err(|e| nom::Err::Error(e.into()))?;
let result = any.try_into().map_err(nom::Err::Error)?;
let (i, any) =
trace(core::any::type_name::<T>(), parse_der_any, bytes).map_err(nom::Err::convert)?;
<T as CheckDerConstraints>::check_constraints(&any).map_err(|e| {
debug_eprintln!(
std::any::type_name::<T>(),
"≠ Checking DER constraints failed:\n {}",
e.to_string().red()
);
nom::Err::Error(e.into())
})?;
let result = any
.try_into()
.map_err(|e| {
debug_eprintln!(core::any::type_name::<T>(), "≠ Conversion from Any failed");
e
})
.map_err(nom::Err::Error)?;
Ok((i, result))
}
}
Expand Down

0 comments on commit 4a68bef

Please sign in to comment.