use nom::{
bytes::complete::tag,
character::complete::{char, one_of},
combinator::{map, map_res, opt, recognize},
error::Error,
multi::many1,
sequence::{delimited, preceded},
IResult,
};
use crate::intermediate::{
types::{GeneralizedTime, UTCTime},
ASN1Type, ASN1Value, GENERALIZED_TIME, TIME, UTC_TIME,
};
use super::{common::skip_ws_and_comments, constraint::constraint};
pub fn time_value(input: &str) -> IResult<&str, ASN1Value> {
map(skip_ws_and_comments(t_string), |t_string| {
ASN1Value::Time(t_string.to_owned())
})(input)
}
pub fn time(input: &str) -> IResult<&str, ASN1Type> {
map(
skip_ws_and_comments(preceded(tag(TIME), opt(constraint))),
|t| ASN1Type::Time(t.into()),
)(input)
}
pub fn generalized_time(input: &str) -> IResult<&str, ASN1Type> {
map(
skip_ws_and_comments(preceded(tag(GENERALIZED_TIME), opt(constraint))),
|cnst| {
ASN1Type::GeneralizedTime(GeneralizedTime {
constraints: cnst.unwrap_or_default(),
})
},
)(input)
}
pub fn utc_time(input: &str) -> IResult<&str, ASN1Type> {
map(
skip_ws_and_comments(preceded(tag(UTC_TIME), opt(constraint))),
|cnst| {
ASN1Type::UTCTime(UTCTime {
constraints: cnst.unwrap_or_default(),
})
},
)(input)
}
const NON_NUMERIC_TIME_CHARS: [char; 17] = [
'+', '-', ':', '.', ',', '/', 'C', 'D', 'H', 'M', 'R', 'P', 'S', 'T', 'W', 'Y', 'Z',
];
fn t_string(input: &str) -> IResult<&str, &str> {
delimited(
char('"'),
map_res(
recognize(many1(one_of("0123456789+-:.,/CDHMRPSTWYZ"))),
|tstring: &str| {
if tstring.contains(char::is_numeric)
&& tstring.contains(|c| NON_NUMERIC_TIME_CHARS.contains(&c))
{
Ok(tstring)
} else {
Err(nom::Err::Error(Error {
input,
code: nom::error::ErrorKind::Satisfy,
}))
}
},
),
char('"'),
)(input)
}