#![cfg(feature = "std")]
#![cfg(feature = "cbor")]
#![cfg(not(feature = "lsp"))]
use super::*;
use crate::{
ast::*,
token,
visitor::{self, *},
};
use core::convert::TryInto;
use std::{
borrow::Cow,
collections::HashMap,
convert::TryFrom,
fmt::{self, Write},
};
use super::cbor_value::{decode_cbor, Value};
use chrono::{TimeZone, Utc};
use serde_json;
#[cfg(feature = "additional-controls")]
use crate::validator::control::{
abnf_from_complex_controller, cat_operation, plus_operation, validate_abnf,
};
pub type Result<T> = std::result::Result<(), Error<T>>;
#[derive(Debug)]
pub enum Error<T: std::fmt::Debug> {
Validation(Vec<ValidationError>),
CBORParsing(ciborium::de::Error<T>),
JSONParsing(serde_json::Error),
CDDLParsing(String),
UTF8Parsing(std::str::Utf8Error),
Base16Decoding(base16::DecodeError),
Base64Decoding(data_encoding::DecodeError),
}
impl<T: std::fmt::Debug> fmt::Display for Error<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Validation(errors) => {
let mut error_str = String::new();
for e in errors.iter() {
let _ = writeln!(error_str, "{}", e);
}
write!(f, "{}", error_str)
}
Error::CBORParsing(error) => write!(f, "error parsing cbor: {}", error),
Error::JSONParsing(error) => write!(f, "error parsing json string: {}", error),
Error::CDDLParsing(error) => write!(f, "error parsing CDDL: {}", error),
Error::UTF8Parsing(error) => write!(f, "error parsing utf8: {}", error),
Error::Base16Decoding(error) => write!(f, "error decoding base16: {}", error),
Error::Base64Decoding(error) => write!(f, "error decoding base64: {}", error),
}
}
}
impl<T: std::fmt::Debug + 'static> std::error::Error for Error<T> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::CBORParsing(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct ValidationError {
pub reason: String,
pub cddl_location: String,
pub cbor_location: String,
pub is_multi_type_choice: bool,
pub is_multi_group_choice: bool,
pub is_group_to_choice_enum: bool,
pub type_group_name_entry: Option<String>,
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut error_str = String::from("error validating");
if self.is_multi_group_choice {
error_str.push_str(" group choice");
}
if self.is_multi_type_choice {
error_str.push_str(" type choice");
}
if self.is_group_to_choice_enum {
error_str.push_str(" type choice in group to choice enumeration");
}
if let Some(entry) = &self.type_group_name_entry {
let _ = write!(error_str, " group entry associated with rule \"{}\"", entry);
}
write!(
f,
"{} at cbor location {}: {}",
error_str, self.cbor_location, self.reason
)
}
}
impl std::error::Error for ValidationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl<T: std::fmt::Debug> Error<T> {
fn from_validator(cv: &CBORValidator, reason: String) -> Self {
Error::Validation(vec![ValidationError {
cddl_location: cv.state.cddl_location.clone(),
cbor_location: cv.state.data_location.clone(),
reason,
is_multi_type_choice: cv.state.is_multi_type_choice,
is_group_to_choice_enum: cv.state.is_group_to_choice_enum,
type_group_name_entry: cv.state.type_group_name_entry.map(|e| e.to_string()),
is_multi_group_choice: cv.state.is_multi_group_choice,
}])
}
}
#[derive(Clone)]
pub struct CBORValidator<'a> {
pub state: super::ValidationState<'a>,
cbor: Value,
errors: Vec<ValidationError>,
object_value: Option<Value>,
cut_value: Option<Type1<'a>>,
validated_keys: Option<Vec<Value>>,
values_to_validate: Option<Vec<Value>>,
validating_value: bool,
array_errors: Option<HashMap<usize, Vec<ValidationError>>>,
range_upper: Option<usize>,
}
impl<'a> CBORValidator<'a> {
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "additional-controls")]
pub fn new(cddl: &'a CDDL<'a>, cbor: Value, enabled_features: Option<&'a [&'a str]>) -> Self {
CBORValidator {
state: super::ValidationState::new(cddl, enabled_features),
cbor,
errors: Vec::default(),
object_value: None,
cut_value: None,
validated_keys: None,
values_to_validate: None,
validating_value: false,
array_errors: None,
range_upper: None,
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "additional-controls"))]
pub fn new(cddl: &'a CDDL<'a>, cbor: Value) -> Self {
CBORValidator {
state: super::ValidationState::new(cddl),
cbor,
errors: Vec::default(),
object_value: None,
cut_value: None,
validated_keys: None,
values_to_validate: None,
validating_value: false,
array_errors: None,
range_upper: None,
}
}
#[cfg(target_arch = "wasm32")]
#[cfg(feature = "additional-controls")]
pub fn new(cddl: &'a CDDL<'a>, cbor: Value, enabled_features: Option<Box<[JsValue]>>) -> Self {
CBORValidator {
state: super::ValidationState::new(cddl, enabled_features),
cbor,
errors: Vec::default(),
object_value: None,
cut_value: None,
validated_keys: None,
values_to_validate: None,
validating_value: false,
array_errors: None,
range_upper: None,
}
}
#[cfg(target_arch = "wasm32")]
#[cfg(not(feature = "additional-controls"))]
pub fn new(cddl: &'a CDDL<'a>, cbor: Value) -> Self {
CBORValidator {
state: super::ValidationState::new(cddl),
cbor,
errors: Vec::default(),
object_value: None,
cut_value: None,
validated_keys: None,
values_to_validate: None,
validating_value: false,
array_errors: None,
range_upper: None,
}
}
pub fn extract_cbor(self) -> Value {
self.cbor
}
fn new_with_recursion_state(&self, cbor: Value) -> CBORValidator<'a> {
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(self.state.cddl, cbor, self.state.enabled_features.clone());
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(self.state.cddl, cbor, self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, cbor);
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.visited_rules = self.state.visited_rules.clone();
cv
}
fn validate_array_items<T: std::fmt::Debug + 'static>(
&mut self,
token: &ArrayItemToken,
) -> visitor::Result<Error<T>>
where
cbor::Error<T>: From<cbor::Error<std::io::Error>>,
{
if let Value::Array(a) = &self.cbor {
if self.state.is_member_key {
return Ok(());
}
match validate_array_occurrence(
self.state.occurrence.as_ref(),
self.state.entry_counts.as_ref().map(|ec| &ec[..]),
a,
) {
Ok((iter_items, allow_empty_array)) => {
if iter_items {
for (idx, v) in a.iter().enumerate() {
if let Some(indices) = &self.state.valid_array_items {
if self.state.is_multi_type_choice && indices.contains(&idx) {
continue;
}
}
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = self.new_with_recursion_state(v.clone());
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = self.new_with_recursion_state(v.clone());
#[cfg(not(feature = "additional-controls"))]
let mut cv = self.new_with_recursion_state(v.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.ctrl = self.state.ctrl;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
let _ = write!(
cv.state.data_location,
"{}/{}",
self.state.data_location, idx
);
match token {
ArrayItemToken::Value(value) => cv.visit_value(value)?,
ArrayItemToken::Range(lower, upper, is_inclusive) => {
cv.visit_range(lower, upper, *is_inclusive)?
}
ArrayItemToken::Group(group) if v.is_array() => {
cv.visit_group(group)?;
}
ArrayItemToken::Group(group) => cv.visit_group(group)?,
ArrayItemToken::Identifier(ident) => cv.visit_identifier(ident)?,
ArrayItemToken::TaggedData(tagged_data) => cv.visit_type2(tagged_data)?,
}
if self.state.is_multi_type_choice && cv.errors.is_empty() {
if let Some(indices) = &mut self.state.valid_array_items {
indices.push(idx);
} else {
match self.state.occurrence {
#[cfg(feature = "ast-span")]
Some(Occur::OneOrMore { .. }) | Some(Occur::Exact { .. }) => {
self.add_error(format!(
"expected array element at index {}, but array only has {} elements",
idx,
a.len()
));
}
#[cfg(not(feature = "ast-span"))]
Some(Occur::OneOrMore {}) | Some(Occur::Exact { .. }) => {
self.add_error(format!(
"expected array element at index {}, but array only has {} elements",
idx,
a.len()
));
}
_ => {} }
return Ok(());
}
continue;
}
if let Some(errors) = &mut self.array_errors {
if let Some(error) = errors.get_mut(&idx) {
error.append(&mut cv.errors);
} else {
errors.insert(idx, cv.errors);
}
} else {
let mut errors = HashMap::new();
errors.insert(idx, cv.errors);
self.array_errors = Some(errors)
}
}
} else {
let idx = if !self.state.is_multi_type_choice {
self.state.group_entry_idx.take()
} else {
self.state.group_entry_idx
};
if let Some(idx) = idx {
if let Some(v) = a.get(idx) {
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = self.new_with_recursion_state(v.clone());
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = self.new_with_recursion_state(v.clone());
#[cfg(not(feature = "additional-controls"))]
let mut cv = self.new_with_recursion_state(v.clone());
cv.state.ctrl = self.state.ctrl;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
let _ = write!(
cv.state.data_location,
"{}/{}",
self.state.data_location, idx
);
match token {
ArrayItemToken::Value(value) => cv.visit_value(value)?,
ArrayItemToken::Range(lower, upper, is_inclusive) => {
cv.visit_range(lower, upper, *is_inclusive)?
}
ArrayItemToken::Group(group) if v.is_array() => {
cv.visit_group(group)?;
}
ArrayItemToken::Group(group) => cv.visit_group(group)?,
ArrayItemToken::Identifier(ident) => cv.visit_identifier(ident)?,
ArrayItemToken::TaggedData(tagged_data) => cv.visit_type2(tagged_data)?,
}
self.errors.append(&mut cv.errors);
} else if !allow_empty_array {
self.add_error(token.error_msg(Some(idx)));
}
} else if !self.state.is_multi_type_choice {
self.add_error(format!("{}, got {:?}", token.error_msg(None), self.cbor));
}
}
}
Err(errors) => {
for e in errors.into_iter() {
self.add_error(e);
}
}
}
}
Ok(())
}
fn resolve_bound_to_uint(&self, bound: &Type2<'a>) -> std::result::Result<usize, String> {
match bound {
Type2::UintValue { value, .. } => Ok(*value),
Type2::Typename { ident, .. } => {
for rule in self.state.cddl.rules.iter() {
if let Rule::Type { rule, .. } = rule {
if rule.name.ident == ident.ident {
if rule.value.type_choices.len() == 1 {
let type_choice = &rule.value.type_choices[0];
if let Type2::UintValue { value, .. } = &type_choice.type1.type2 {
return Ok(*value);
}
}
return Err(format!(
"Type name '{}' does not resolve to a simple uint value",
ident.ident
));
}
}
}
Err(format!(
"Type name '{}' not found in CDDL rules",
ident.ident
))
}
_ => Err(format!("Expected uint value or type name, got {}", bound)),
}
}
}
impl<'a, T: std::fmt::Debug + 'static> Validator<'a, '_, cbor::Error<T>> for CBORValidator<'a>
where
cbor::Error<T>: From<cbor::Error<std::io::Error>>,
{
fn validate(&mut self) -> std::result::Result<(), cbor::Error<T>> {
for r in self.state.cddl.rules.iter() {
if let Rule::Type { rule, .. } = r {
if rule.generic_params.is_none() {
self.state.is_root = true;
self.visit_type_rule(rule)?;
self.state.is_root = false;
break;
}
}
}
if !self.errors.is_empty() {
return Err(Error::Validation(self.errors.clone()));
}
Ok(())
}
fn add_error(&mut self, reason: String) {
self.errors.push(ValidationError {
reason,
cddl_location: self.state.cddl_location.clone(),
cbor_location: self.state.data_location.clone(),
is_multi_type_choice: self.state.is_multi_type_choice,
is_multi_group_choice: self.state.is_multi_group_choice,
is_group_to_choice_enum: self.state.is_group_to_choice_enum,
type_group_name_entry: self.state.type_group_name_entry.map(|e| e.to_string()),
});
}
}
impl<'a, T: std::fmt::Debug + 'static> Visitor<'a, '_, Error<T>> for CBORValidator<'a>
where
cbor::Error<T>: From<cbor::Error<std::io::Error>>,
{
fn visit_type_rule(&mut self, tr: &TypeRule<'a>) -> visitor::Result<Error<T>> {
if let Some(gp) = &tr.generic_params {
if let Some(gr) = self
.state
.generic_rules
.iter_mut()
.find(|r| r.name == tr.name.ident)
{
gr.params = gp.params.iter().map(|p| p.param.ident).collect();
} else {
self.state.generic_rules.push(GenericRule {
name: tr.name.ident,
params: gp.params.iter().map(|p| p.param.ident).collect(),
args: Vec::new(),
});
}
}
let type_choice_alternates = type_choice_alternates_from_ident(self.state.cddl, &tr.name);
if !type_choice_alternates.is_empty() {
self.state.is_multi_type_choice = true;
if self.cbor.is_array() {
self.state.is_multi_type_choice_type_rule_validating_array = true;
}
let error_count = self.errors.len();
let cur_errors = self.errors.len();
self.visit_type(&tr.value)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
return Ok(());
}
for t in type_choice_alternates {
let cur_errors = self.errors.len();
self.visit_type(t)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
return Ok(());
}
}
return Ok(());
}
if tr.value.type_choices.len() > 1 && self.cbor.is_array() {
self.state.is_multi_type_choice_type_rule_validating_array = true;
}
self.visit_type(&tr.value)
}
fn visit_group_rule(&mut self, gr: &GroupRule<'a>) -> visitor::Result<Error<T>> {
if let Some(gp) = &gr.generic_params {
if let Some(gr) = self
.state
.generic_rules
.iter_mut()
.find(|r| r.name == gr.name.ident)
{
gr.params = gp.params.iter().map(|p| p.param.ident).collect();
} else {
self.state.generic_rules.push(GenericRule {
name: gr.name.ident,
params: gp.params.iter().map(|p| p.param.ident).collect(),
args: Vec::new(),
});
}
}
let group_choice_alternates = group_choice_alternates_from_ident(self.state.cddl, &gr.name);
if !group_choice_alternates.is_empty() {
self.state.is_multi_group_choice = true;
}
let error_count = self.errors.len();
for ge in group_choice_alternates {
let cur_errors = self.errors.len();
self.visit_group_entry(ge)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
return Ok(());
}
}
self.visit_group_entry(&gr.entry)
}
fn visit_type(&mut self, t: &Type<'a>) -> visitor::Result<Error<T>> {
if let Value::Array(outer_array) = &self.cbor {
if let Some(idx) = self.state.group_entry_idx {
if let Some(item) = outer_array.get(idx) {
if item.is_array() {
for tc in t.type_choices.iter() {
if let Type2::Array { .. } = &tc.type1.type2 {
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
item.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv =
CBORValidator::new(self.state.cddl, item.clone(), self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, item.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
let _ = write!(
cv.state.data_location,
"{}/{}",
self.state.data_location, idx
);
cv.visit_type_choice(tc)?;
self.errors.append(&mut cv.errors);
return Ok(());
}
}
}
}
}
}
if t.type_choices.len() > 1 {
self.state.is_multi_type_choice = true;
}
let initial_error_count = self.errors.len();
let mut choice_validation_succeeded = false;
for type_choice in t.type_choices.iter() {
if matches!(self.cbor, Value::Array(_))
&& !self.state.is_multi_type_choice_type_rule_validating_array
{
let error_count = self.errors.len();
self.visit_type_choice(type_choice)?;
#[cfg(feature = "additional-controls")]
if self.errors.len() == error_count
&& !self.state.has_feature_errors
&& self.state.disabled_features.is_none()
{
let type_choice_error_count = self.errors.len() - initial_error_count;
if type_choice_error_count > 0 {
for _ in 0..type_choice_error_count {
self.errors.pop();
}
}
choice_validation_succeeded = true;
}
#[cfg(not(feature = "additional-controls"))]
if self.errors.len() == error_count {
let type_choice_error_count = self.errors.len() - initial_error_count;
if type_choice_error_count > 0 {
for _ in 0..type_choice_error_count {
self.errors.pop();
}
}
choice_validation_succeeded = true;
}
continue;
}
let mut choice_validator = self.clone();
choice_validator.errors.clear();
choice_validator.visit_type_choice(type_choice)?;
if choice_validator.errors.is_empty() {
#[cfg(feature = "additional-controls")]
if !choice_validator.state.has_feature_errors
|| choice_validator.state.disabled_features.is_some()
{
let type_choice_error_count = self.errors.len() - initial_error_count;
if type_choice_error_count > 0 {
for _ in 0..type_choice_error_count {
self.errors.pop();
}
}
return Ok(());
}
#[cfg(not(feature = "additional-controls"))]
{
let type_choice_error_count = self.errors.len() - initial_error_count;
if type_choice_error_count > 0 {
for _ in 0..type_choice_error_count {
self.errors.pop();
}
}
return Ok(());
}
} else {
self.errors.extend(choice_validator.errors);
}
}
if choice_validation_succeeded {
return Ok(());
}
Ok(())
}
fn visit_group(&mut self, g: &Group<'a>) -> visitor::Result<Error<T>> {
if g.group_choices.len() > 1 {
self.state.is_multi_group_choice = true;
}
if self.state.is_ctrl_map_equality {
if let Some(t) = &self.state.ctrl {
if let Value::Map(m) = &self.cbor {
let entry_counts = entry_counts_from_group(self.state.cddl, g);
let len = m.len();
if let ControlOperator::EQ | ControlOperator::NE = t {
if !validate_entry_count(&entry_counts, len) {
for ec in entry_counts.iter() {
if let Some(occur) = &ec.entry_occurrence {
self.add_error(format!(
"expected array with length per occurrence {}",
occur,
));
} else {
self.add_error(format!(
"expected array with length {}, got {}",
ec.count, len
));
}
}
return Ok(());
}
}
}
}
}
self.state.is_ctrl_map_equality = false;
let initial_error_count = self.errors.len();
for group_choice in g.group_choices.iter() {
let error_count = self.errors.len();
self.visit_group_choice(group_choice)?;
if self.errors.len() == error_count {
let group_choice_error_count = self.errors.len() - initial_error_count;
if group_choice_error_count > 0 {
for _ in 0..group_choice_error_count {
self.errors.pop();
}
}
return Ok(());
}
}
Ok(())
}
fn visit_group_choice(&mut self, gc: &GroupChoice<'a>) -> visitor::Result<Error<T>> {
if self.state.is_group_to_choice_enum {
let initial_error_count = self.errors.len();
for tc in type_choices_from_group_choice(self.state.cddl, gc).iter() {
let error_count = self.errors.len();
self.visit_type_choice(tc)?;
if self.errors.len() == error_count {
let type_choice_error_count = self.errors.len() - initial_error_count;
if type_choice_error_count > 0 {
for _ in 0..type_choice_error_count {
self.errors.pop();
}
}
return Ok(());
}
}
return Ok(());
}
for (idx, ge) in gc.group_entries.iter().enumerate() {
self.state.group_entry_idx = Some(idx);
self.visit_group_entry(&ge.0)?;
}
Ok(())
}
fn visit_range(
&mut self,
lower: &Type2,
upper: &Type2,
is_inclusive: bool,
) -> visitor::Result<Error<T>> {
if let Value::Array(_) = &self.cbor {
return self.validate_array_items(&ArrayItemToken::Range(lower, upper, is_inclusive));
}
let l_result = self.resolve_bound_to_uint(lower);
let u_result = self.resolve_bound_to_uint(upper);
match (l_result, u_result) {
(Ok(l), Ok(u)) => {
match &self.cbor {
Value::Bytes(b) => {
let len = b.len();
if is_inclusive {
if len < l || len > u {
self.add_error(format!(
"expected uint to be in range {} <= value <= {}, got Bytes({:?})",
l, u, b
));
}
} else if len < l || len >= u {
self.add_error(format!(
"expected uint to be in range {} <= value < {}, got Bytes({:?})",
l, u, b
));
}
}
Value::Text(s) => match self.state.ctrl {
Some(ControlOperator::SIZE) => {
let len = s.len();
let s = s.clone();
if is_inclusive {
if s.len() < l || s.len() > u {
self.add_error(format!(
"expected \"{}\" string length to be in the range {} <= value <= {}, got {}",
s, l, u, len
));
}
return Ok(());
} else if s.len() < l || s.len() >= u {
self.add_error(format!(
"expected \"{}\" string length to be in the range {} <= value < {}, got {}",
s, l, u, len
));
return Ok(());
}
}
_ => {
self.add_error("string value cannot be validated against a range without the .size control operator".to_string());
return Ok(());
}
},
Value::Integer(i) => {
if is_inclusive {
if i128::from(*i) < l as i128 || i128::from(*i) > u as i128 {
self.add_error(format!(
"expected integer to be in range {} <= value <= {}, got {:?}",
l, u, i
));
}
} else if i128::from(*i) < l as i128 || i128::from(*i) >= u as i128 {
self.add_error(format!(
"expected integer to be in range {} <= value < {}, got {:?}",
l, u, i
));
}
}
_ => {
self.add_error(format!(
"expected value to be in range {} {} value {} {}, got {:?}",
l,
if is_inclusive { "<=" } else { "<" },
if is_inclusive { "<=" } else { "<" },
u,
self.cbor
));
}
}
}
(Ok(_), Err(u_err)) => {
self.add_error(format!(
"invalid cddl range. upper value must be a uint type. got {}. Error: {}",
upper, u_err
));
}
(Err(l_err), Ok(_)) => {
self.add_error(format!(
"invalid cddl range. lower value must be a uint type. got {}. Error: {}",
lower, l_err
));
}
(Err(l_err), Err(u_err)) => {
let err_msg = format!("{} and {}", l_err, u_err);
self.add_error(format!(
"invalid cddl range. upper and lower values must be uint types. got {} and {}. Error: {}",
lower, upper, err_msg
));
}
}
Ok(())
}
fn visit_control_operator(
&mut self,
target: &Type2<'a>,
ctrl: ControlOperator,
controller: &Type2<'a>,
) -> visitor::Result<Error<T>> {
if let Type2::Typename {
ident: target_ident,
..
} = target
{
if let Type2::Typename {
ident: controller_ident,
..
} = controller
{
if let Some(name) = self.state.eval_generic_rule {
if let Some(gr) = self
.state
.generic_rules
.iter()
.find(|&gr| gr.name == name)
.cloned()
{
for (idx, gp) in gr.params.iter().enumerate() {
if let Some(arg) = gr.args.get(idx) {
if *gp == target_ident.ident {
let t2 = Type2::from(arg.clone());
if *gp == controller_ident.ident {
return self.visit_control_operator(&t2, ctrl, &t2);
}
return self.visit_control_operator(&arg.type2, ctrl, controller);
}
}
}
}
}
}
if let Some(name) = self.state.eval_generic_rule {
if let Some(gr) = self
.state
.generic_rules
.iter()
.find(|&gr| gr.name == name)
.cloned()
{
for (idx, gp) in gr.params.iter().enumerate() {
if let Some(arg) = gr.args.get(idx) {
if *gp == target_ident.ident {
let t2 = Type2::from(arg.clone());
return self.visit_control_operator(&t2, ctrl, controller);
}
}
}
}
}
if is_ident_string_data_type(self.state.cddl, target_ident)
&& !matches!(self.cbor, Value::Text(_))
{
self.add_error(format!("expected type tstr, got {:?}", self.cbor));
return Ok(());
} else if is_ident_byte_string_data_type(self.state.cddl, target_ident)
&& !matches!(self.cbor, Value::Bytes(_))
{
self.add_error(format!("expected type bstr, got {:?}", self.cbor));
return Ok(());
} else if is_ident_uint_data_type(self.state.cddl, target_ident) {
if !matches!(self.cbor, Value::Integer(_)) {
self.add_error(format!("expected type uint, got {:?}", self.cbor));
return Ok(());
} else if let Value::Integer(i) = &self.cbor {
if i128::from(*i) < 0 {
self.add_error(format!("expected type uint, got {:?}", self.cbor));
return Ok(());
}
}
#[cfg(feature = "freezer")]
if ctrl != ControlOperator::BITFIELD {
return Ok(());
}
#[cfg(not(feature = "freezer"))]
return Ok(());
} else if is_ident_integer_data_type(self.state.cddl, target_ident)
&& !matches!(self.cbor, Value::Integer(_))
{
self.add_error(format!("expected type int, got {:?}", self.cbor));
return Ok(());
} else if is_ident_float_data_type(self.state.cddl, target_ident)
&& !matches!(self.cbor, Value::Float(_))
{
self.add_error(format!("expected type float, got {:?}", self.cbor));
return Ok(());
} else if is_ident_bool_data_type(self.state.cddl, target_ident)
&& !matches!(self.cbor, Value::Bool(_))
{
self.add_error(format!("expected type bool, got {:?}", self.cbor));
return Ok(());
} else if is_ident_null_data_type(self.state.cddl, target_ident)
&& !matches!(self.cbor, Value::Null)
{
self.add_error(format!("expected type null, got {:?}", self.cbor));
return Ok(());
}
}
match ctrl {
ControlOperator::EQ => {
match target {
Type2::Typename { ident, .. } => {
if is_ident_string_data_type(self.state.cddl, ident)
|| is_ident_numeric_data_type(self.state.cddl, ident)
{
return self.visit_type2(controller);
}
}
Type2::Array { group, .. } => {
if let Value::Array(_) = &self.cbor {
self.state.entry_counts = Some(entry_counts_from_group(self.state.cddl, group));
self.visit_type2(controller)?;
self.state.entry_counts = None;
return Ok(());
}
}
Type2::Map { .. } => {
if let Value::Map(_) = &self.cbor {
self.state.ctrl = Some(ctrl);
self.state.is_ctrl_map_equality = true;
self.visit_type2(controller)?;
self.state.ctrl = None;
self.state.is_ctrl_map_equality = false;
return Ok(());
}
}
_ => self.add_error(format!(
"target for .eq operator must be a string, numerical, array or map data type, got {}",
target
)),
}
Ok(())
}
ControlOperator::NE => {
match target {
Type2::Typename { ident, .. } => {
if is_ident_string_data_type(self.state.cddl, ident)
|| is_ident_numeric_data_type(self.state.cddl, ident)
{
self.state.ctrl = Some(ctrl);
self.visit_type2(controller)?;
self.state.ctrl = None;
return Ok(());
}
}
Type2::Array { .. } => {
if let Value::Array(_) = &self.cbor {
self.state.ctrl = Some(ctrl);
self.visit_type2(controller)?;
self.state.ctrl = None;
return Ok(());
}
}
Type2::Map { .. } => {
if let Value::Map(_) = &self.cbor {
self.state.ctrl = Some(ctrl);
self.state.is_ctrl_map_equality = true;
self.visit_type2(controller)?;
self.state.ctrl = None;
self.state.is_ctrl_map_equality = false;
return Ok(());
}
}
_ => self.add_error(format!(
"target for .ne operator must be a string, numerical, array or map data type, got {}",
target
)),
}
Ok(())
}
ControlOperator::LT | ControlOperator::GT | ControlOperator::GE | ControlOperator::LE => {
match target {
Type2::Typename { ident, .. } if is_ident_numeric_data_type(self.state.cddl, ident) => {
self.state.ctrl = Some(ctrl);
self.visit_type2(controller)?;
self.state.ctrl = None;
Ok(())
}
_ => {
self.add_error(format!(
"target for .lt, .gt, .ge or .le operator must be a numerical data type, got {}",
target
));
Ok(())
}
}
}
ControlOperator::SIZE => match target {
Type2::Typename { ident, .. }
if is_ident_string_data_type(self.state.cddl, ident)
|| is_ident_uint_data_type(self.state.cddl, ident)
|| is_ident_byte_string_data_type(self.state.cddl, ident) =>
{
self.state.ctrl = Some(ctrl);
self.visit_type2(controller)?;
self.state.ctrl = None;
Ok(())
}
_ => {
self.add_error(format!(
"target for .size must a string or uint data type, got {}",
target
));
Ok(())
}
},
ControlOperator::AND => {
self.state.ctrl = Some(ctrl);
self.visit_type2(target)?;
self.visit_type2(controller)?;
self.state.ctrl = None;
Ok(())
}
ControlOperator::WITHIN => {
self.state.ctrl = Some(ctrl);
let error_count = self.errors.len();
self.visit_type2(target)?;
let no_errors = self.errors.len() == error_count;
self.visit_type2(controller)?;
if no_errors && self.errors.len() > error_count {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
self.add_error(format!(
"expected type {} .within type {}, got {:?}",
target, controller, self.cbor,
));
}
self.state.ctrl = None;
Ok(())
}
ControlOperator::DEFAULT => {
self.state.ctrl = Some(ctrl);
let error_count = self.errors.len();
self.visit_type2(target)?;
if self.errors.len() != error_count {
#[cfg(feature = "ast-span")]
if let Some(Occur::Optional { .. }) = self.state.occurrence.take() {
self.add_error(format!(
"expected default value {}, got {:?}",
controller, self.cbor
));
}
#[cfg(not(feature = "ast-span"))]
if let Some(Occur::Optional {}) = self.state.occurrence.take() {
self.add_error(format!(
"expected default value {}, got {:?}",
controller, self.cbor
));
}
}
self.state.ctrl = None;
Ok(())
}
ControlOperator::REGEXP => {
self.state.ctrl = Some(ctrl);
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match self.cbor {
Value::Text(_) | Value::Array(_) => self.visit_type2(controller)?,
_ => self.add_error(format!(
".regexp control can only be matched against CBOR string, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".regexp control can only be matched against string data type, got {}",
target
)),
}
self.state.ctrl = None;
Ok(())
}
ControlOperator::PCRE => {
self.state.ctrl = Some(ctrl);
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match self.cbor {
Value::Text(_) | Value::Array(_) => self.visit_type2(controller)?,
_ => self.add_error(format!(
".pcre control can only be matched against CBOR string, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".pcre control can only be matched against string data type, got {}",
target
)),
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "freezer")]
ControlOperator::IREGEXP => {
self.state.ctrl = Some(ctrl);
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match self.cbor {
Value::Text(_) | Value::Array(_) => self.visit_type2(controller)?,
_ => self.add_error(format!(
".iregexp control can only be matched against CBOR string, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".iregexp control can only be matched against string data type, got {}",
target
)),
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "freezer")]
ControlOperator::BITFIELD => {
self.state.ctrl = Some(ctrl);
match target {
Type2::Typename { ident, .. } if is_ident_uint_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Integer(i) if i128::from(*i) >= 0i128 => {
let int_val = i128::from(*i) as u128;
match extract_bitfield_widths(controller) {
Some(total_bits) if total_bits > 0 && total_bits <= 128 => {
let max_val = if total_bits >= 128 {
u128::MAX
} else {
(1u128 << total_bits) - 1
};
if int_val > max_val {
self.add_error(format!(
"value {} exceeds .bitfield capacity of {} bits (max {})",
int_val, total_bits, max_val
));
}
}
Some(_) => {
self
.add_error(".bitfield total bit width must be between 1 and 128".to_string());
}
None => {
self.add_error(
".bitfield controller must be an array of uint values representing bit widths"
.to_string(),
);
}
}
}
_ => self.add_error(format!(
".bitfield control can only be matched against a non-negative integer, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".bitfield control can only be matched against uint data type, got {}",
target
)),
}
self.state.ctrl = None;
Ok(())
}
ControlOperator::CBOR | ControlOperator::CBORSEQ => {
self.state.ctrl = Some(ctrl);
match target {
Type2::Typename { ident, .. }
if is_ident_byte_string_data_type(self.state.cddl, ident) =>
{
match &self.cbor {
Value::Bytes(b) => {
let inner_value = decode_cbor(b);
match inner_value {
Ok(value) => {
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
value,
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv =
CBORValidator::new(self.state.cddl, value, self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, value);
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.data_location.push_str(&self.state.data_location);
cv.visit_type2(controller)?;
if !cv.errors.is_empty() {
self.errors.append(&mut cv.errors);
}
}
Err(e) => {
self.add_error(format!("error decoding embedded CBOR: {}", e));
}
}
}
Value::Array(arr) => {
for (idx, item) in arr.iter().enumerate() {
if let Value::Bytes(b) = item {
let inner_value = decode_cbor(b);
match inner_value {
Ok(value) => {
let current_location = self.state.data_location.clone();
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
value,
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv =
CBORValidator::new(self.state.cddl, value, self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, value);
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
let _ = write!(
cv.state.data_location,
"{}/{}",
self.state.data_location, idx
);
cv.visit_type2(controller)?;
if !cv.errors.is_empty() {
self.errors.append(&mut cv.errors);
}
self.state.data_location = current_location;
}
Err(e) => {
let error_msg =
format!("error decoding embedded CBOR at index {}: {}", idx, e);
self.errors.push(ValidationError {
reason: error_msg,
cddl_location: self.state.cddl_location.clone(),
cbor_location: self.state.data_location.clone(),
is_multi_type_choice: self.state.is_multi_type_choice,
is_multi_group_choice: self.state.is_multi_group_choice,
is_group_to_choice_enum: self.state.is_group_to_choice_enum,
type_group_name_entry: self
.state
.type_group_name_entry
.map(|e| e.to_string()),
});
}
}
} else {
let error_msg = format!(
"array item at index {} must be a byte string for .cbor control, got {:?}",
idx, item
);
self.errors.push(ValidationError {
reason: error_msg,
cddl_location: self.state.cddl_location.clone(),
cbor_location: self.state.data_location.clone(),
is_multi_type_choice: self.state.is_multi_type_choice,
is_multi_group_choice: self.state.is_multi_group_choice,
is_group_to_choice_enum: self.state.is_group_to_choice_enum,
type_group_name_entry: self
.state
.type_group_name_entry
.map(|e| e.to_string()),
});
}
}
}
_ => {
self.add_error(format!(
".cbor control can only be matched against a CBOR byte string or array of byte strings, got {:?}",
self.cbor
));
}
}
}
_ => self.add_error(format!(
".cbor control can only be matched against a byte string data type, got {}",
target
)),
}
self.state.ctrl = None;
Ok(())
}
ControlOperator::BITS => {
self.state.ctrl = Some(ctrl);
match target {
Type2::Typename { ident, .. }
if is_ident_byte_string_data_type(self.state.cddl, ident)
|| is_ident_uint_data_type(self.state.cddl, ident) =>
{
match &self.cbor {
Value::Bytes(_) | Value::Array(_) => self.visit_type2(controller)?,
Value::Integer(i) if i128::from(*i) >= 0i128 => self.visit_type2(controller)?,
_ => self.add_error(format!(
"{} control can only be matched against a CBOR byte string or uint, got {:?}",
ctrl, self.cbor,
)),
}
}
_ => self.add_error(format!(
".bits control can only be matched against a byte string data type, got {}",
target
)),
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::CAT => {
self.state.ctrl = Some(ctrl);
match cat_operation(self.state.cddl, target, controller, false) {
Ok(values) => {
let error_count = self.errors.len();
for v in values.iter() {
let cur_errors = self.errors.len();
self.visit_type2(v)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
break;
}
}
}
Err(e) => self.add_error(e),
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::DET => {
self.state.ctrl = Some(ctrl);
match cat_operation(self.state.cddl, target, controller, true) {
Ok(values) => {
let error_count = self.errors.len();
for v in values.iter() {
let cur_errors = self.errors.len();
self.visit_type2(v)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
break;
}
}
}
Err(e) => self.add_error(e),
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::PLUS => {
self.state.ctrl = Some(ctrl);
match plus_operation(self.state.cddl, target, controller) {
Ok(values) => {
let error_count = self.errors.len();
for v in values.iter() {
let cur_errors = self.errors.len();
self.visit_type2(v)?;
self.visit_type2(v)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
break;
}
}
}
Err(e) => self.add_error(e),
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::ABNF => {
self.state.ctrl = Some(ctrl);
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match self.cbor {
Value::Text(_) | Value::Array(_) => {
let complex_ctrl_result = if let Type2::ParenthesizedType { pt, .. } = controller {
Some(abnf_from_complex_controller(self.state.cddl, pt))
} else if let Type2::Typename { ident, .. } = controller {
if let Some(Rule::Type { rule, .. }) = rule_from_ident(self.state.cddl, ident) {
match abnf_from_complex_controller(self.state.cddl, &rule.value) {
Ok(values) => Some(Ok(values)),
Err(_) => None,
}
} else {
None
}
} else {
None
};
if let Some(result) = complex_ctrl_result {
match result {
Ok(values) => {
let error_count = self.errors.len();
for v in values.iter() {
let cur_errors = self.errors.len();
self.visit_type2(v)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
break;
}
}
}
Err(e) => self.add_error(e),
}
} else {
self.visit_type2(controller)?
}
}
_ => self.add_error(format!(
".abnf control can only be matched against a cbor string, got {:?}",
self.cbor,
)),
}
}
_ => self.add_error(format!(
".abnf can only be matched against string data type, got {}",
target,
)),
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::ABNFB => {
self.state.ctrl = Some(ctrl);
match target {
Type2::Typename { ident, .. }
if is_ident_byte_string_data_type(self.state.cddl, ident) =>
{
match self.cbor {
Value::Bytes(_) | Value::Array(_) => {
let complex_ctrl_result = if let Type2::ParenthesizedType { pt, .. } = controller {
Some(abnf_from_complex_controller(self.state.cddl, pt))
} else if let Type2::Typename { ident, .. } = controller {
if let Some(Rule::Type { rule, .. }) = rule_from_ident(self.state.cddl, ident) {
match abnf_from_complex_controller(self.state.cddl, &rule.value) {
Ok(values) => Some(Ok(values)),
Err(_) => None,
}
} else {
None
}
} else {
None
};
if let Some(result) = complex_ctrl_result {
match result {
Ok(values) => {
let error_count = self.errors.len();
for v in values.iter() {
let cur_errors = self.errors.len();
self.visit_type2(v)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
break;
}
}
}
Err(e) => self.add_error(e),
}
} else {
self.visit_type2(controller)?
}
}
_ => self.add_error(format!(
".abnfb control can only be matched against cbor bytes, got {:?}",
self.cbor,
)),
}
}
_ => self.add_error(format!(
".abnfb can only be matched against byte string target data type, got {}",
target,
)),
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "additional-controls")]
#[cfg(not(target_arch = "wasm32"))]
ControlOperator::FEATURE => {
self.state.ctrl = Some(ctrl);
if let Some(ef) = self.state.enabled_features {
let tv = text_value_from_type2(self.state.cddl, controller);
if let Some(Type2::TextValue { value, .. }) = tv {
if ef.contains(&&**value) {
let err_count = self.errors.len();
self.visit_type2(target)?;
if self.errors.len() > err_count {
self.state.has_feature_errors = true;
}
self.state.ctrl = None;
} else {
self
.state
.disabled_features
.get_or_insert(vec![value.to_string()])
.push(value.to_string());
}
} else if let Some(Type2::UTF8ByteString { value, .. }) = tv {
let value = std::str::from_utf8(value).map_err(Error::UTF8Parsing)?;
if ef.contains(&value) {
let err_count = self.errors.len();
self.visit_type2(target)?;
if self.errors.len() > err_count {
self.state.has_feature_errors = true;
}
self.state.ctrl = None;
} else {
self
.state
.disabled_features
.get_or_insert(vec![value.to_string()])
.push(value.to_string());
}
}
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "additional-controls")]
#[cfg(target_arch = "wasm32")]
ControlOperator::FEATURE => {
self.state.ctrl = Some(ctrl);
if let Some(ef) = &self.state.enabled_features {
let tv = text_value_from_type2(self.state.cddl, controller);
if let Some(Type2::TextValue { value, .. }) = tv {
if ef.contains(&JsValue::from(value.as_ref())) {
let err_count = self.errors.len();
self.visit_type2(target)?;
if self.errors.len() > err_count {
self.state.has_feature_errors = true;
}
self.state.ctrl = None;
} else {
self
.state
.disabled_features
.get_or_insert(vec![value.to_string()])
.push(value.to_string());
}
} else if let Some(Type2::UTF8ByteString { value, .. }) = tv {
let value = std::str::from_utf8(value).map_err(Error::UTF8Parsing)?;
if ef.contains(&JsValue::from(value)) {
let err_count = self.errors.len();
self.visit_type2(target)?;
if self.errors.len() > err_count {
self.state.has_feature_errors = true;
}
self.state.ctrl = None;
} else {
self
.state
.disabled_features
.get_or_insert(vec![value.to_string()])
.push(value.to_string());
}
}
}
self.state.ctrl = None;
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::B64U => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_b64u_text(target, controller, s, false) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .b64u encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".b64u can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".b64u can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::B64C => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_b64c_text(target, controller, s, false) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .b64c encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".b64c can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".b64c can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::B64USLOPPY => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_b64u_text(target, controller, s, true) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .b64u-sloppy encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".b64u-sloppy can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".b64u-sloppy can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::B64CSLOPPY => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_b64c_text(target, controller, s, true) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .b64c-sloppy encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".b64c-sloppy can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".b64c-sloppy can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::HEX => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_hex_text(
target,
controller,
s,
crate::validator::control::HexCase::Any,
) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .hex encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".hex can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".hex can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::HEXLC => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_hex_text(
target,
controller,
s,
crate::validator::control::HexCase::Lower,
) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .hexlc encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".hexlc can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".hexlc can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::HEXUC => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_hex_text(
target,
controller,
s,
crate::validator::control::HexCase::Upper,
) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .hexuc encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".hexuc can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".hexuc can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::B32 => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_b32_text(target, controller, s, false) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .b32 encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".b32 can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".b32 can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::H32 => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_b32_text(target, controller, s, true) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .h32 encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".h32 can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".h32 can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::B45 => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_b45_text(target, controller, s) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .b45 encoded bytes",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".b45 can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".b45 can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::BASE10 => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_base10_text(target, controller, s) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .base10 integer format",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".base10 can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".base10 can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::PRINTF => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_printf_text(target, controller, s) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!(
"text string \"{}\" does not match .printf format",
s
));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".printf can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".printf can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::JSON => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_json_text(target, controller, s) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!("text string \"{}\" does not contain valid JSON", s));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".json can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".json can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
ControlOperator::JOIN => {
match target {
Type2::Typename { ident, .. } if is_ident_string_data_type(self.state.cddl, ident) => {
match &self.cbor {
Value::Text(s) => {
match crate::validator::control::validate_join_text(
target,
controller,
s,
Some(self.state.cddl),
) {
Ok(is_valid) => {
if !is_valid {
self.add_error(format!("text string \"{}\" does not match .join result", s));
}
}
Err(e) => self.add_error(e),
}
}
_ => self.add_error(format!(
".join can only be matched against CBOR text, got {:?}",
self.cbor
)),
}
}
_ => self.add_error(format!(
".join can only be matched against string data type, got {}",
target
)),
}
Ok(())
}
}
}
fn visit_type2(&mut self, t2: &Type2<'a>) -> visitor::Result<Error<T>> {
if matches!(self.state.ctrl, Some(ControlOperator::CBOR)) {
if let Value::Bytes(b) = &self.cbor {
let value = decode_cbor(b);
match value {
Ok(value) => {
let current_location = self.state.data_location.clone();
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv =
CBORValidator::new(self.state.cddl, value, self.state.enabled_features.clone());
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(self.state.cddl, value, self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, value);
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.state.is_multi_group_choice = self.state.is_multi_group_choice;
cv.state.data_location.push_str(&self.state.data_location);
cv.state.type_group_name_entry = self.state.type_group_name_entry;
cv.visit_type2(t2)?;
if cv.errors.is_empty() {
self.state.data_location = current_location;
return Ok(());
}
self.errors.append(&mut cv.errors);
}
Err(e) => {
self.add_error(format!("error decoding embedded CBOR, {}", e));
}
}
}
return Ok(());
} else if matches!(self.state.ctrl, Some(ControlOperator::CBORSEQ)) {
if let Value::Bytes(b) = &self.cbor {
let value = decode_cbor(b);
match value {
Ok(Value::Array(_)) => {
let current_location = self.state.data_location.clone();
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
value.unwrap_or(Value::Null),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(
self.state.cddl,
value.unwrap_or(Value::Null),
self.state.enabled_features,
);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, value.unwrap_or(Value::Null));
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.state.is_multi_group_choice = self.state.is_multi_group_choice;
cv.state.data_location.push_str(&self.state.data_location);
cv.state.type_group_name_entry = self.state.type_group_name_entry;
cv.visit_type2(t2)?;
if cv.errors.is_empty() {
self.state.data_location = current_location;
return Ok(());
}
self.errors.append(&mut cv.errors);
}
Err(e) => {
self.add_error(format!("error decoding embedded CBOR, {}", e));
}
Ok(v) => self.add_error(format!(
"embedded CBOR must be a CBOR sequence, got {:?}",
v
)),
}
}
return Ok(());
}
match t2 {
Type2::TextValue { value, .. } => self.visit_value(&token::Value::TEXT(value.clone())),
Type2::Map { group, .. } => match &self.cbor {
Value::Map(m) => {
if self.state.is_member_key {
let current_location = self.state.data_location.clone();
for (k, v) in m.iter() {
#[cfg(feature = "additional-controls")]
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
k.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv =
CBORValidator::new(self.state.cddl, k.clone(), self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, k.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.state.is_multi_group_choice = self.state.is_multi_group_choice;
cv.state.data_location.push_str(&self.state.data_location);
cv.state.type_group_name_entry = self.state.type_group_name_entry;
cv.visit_type2(t2)?;
if cv.errors.is_empty() {
self.object_value = Some(v.clone());
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.state.data_location = current_location;
return Ok(());
}
self.errors.append(&mut cv.errors);
}
return Ok(());
}
if group.group_choices.len() == 1
&& group.group_choices[0].group_entries.is_empty()
&& !m.is_empty()
&& !matches!(
self.state.ctrl,
Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT)
)
{
self.add_error(format!("expected empty map, got {:?}", self.cbor));
return Ok(());
}
#[allow(clippy::needless_collect)]
let m = m.iter().map(|entry| entry.0.clone()).collect::<Vec<_>>();
self.visit_group(group)?;
if self.values_to_validate.is_none() {
for k in m.into_iter() {
if let Some(keys) = &self.validated_keys {
if !keys.contains(&k) {
self.add_error(format!("unexpected key {:?}", k));
}
}
}
}
self.state.is_cut_present = false;
self.cut_value = None;
Ok(())
}
Value::Array(_) => self.validate_array_items(&ArrayItemToken::Group(group)),
_ => {
self.add_error(format!("expected map object {}, got {:?}", t2, self.cbor));
Ok(())
}
},
Type2::Array { group, .. } => match &self.cbor {
Value::Array(a) => {
if group.group_choices.len() == 1
&& group.group_choices[0].group_entries.is_empty()
&& !a.is_empty()
&& !matches!(
self.state.ctrl,
Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT)
)
{
self.add_error(format!("expected empty array, got {:?}", self.cbor));
return Ok(());
}
self.state.entry_counts = Some(entry_counts_from_group(self.state.cddl, group));
self.visit_group(group)?;
self.state.entry_counts = None;
if let Some(errors) = &mut self.array_errors {
if let Some(indices) = &self.state.valid_array_items {
for idx in indices.iter() {
errors.remove(idx);
}
}
for error in errors.values_mut() {
self.errors.append(error);
}
}
self.state.valid_array_items = None;
self.array_errors = None;
Ok(())
}
Value::Map(m) if self.state.is_member_key => {
let current_location = self.state.data_location.clone();
self.state.entry_counts = Some(entry_counts_from_group(self.state.cddl, group));
for (k, v) in m.iter() {
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
k.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv =
CBORValidator::new(self.state.cddl, k.clone(), self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, k.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.entry_counts = self.state.entry_counts.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.state.is_multi_group_choice = self.state.is_multi_group_choice;
cv.state.data_location.push_str(&self.state.data_location);
cv.state.type_group_name_entry = self.state.type_group_name_entry;
cv.visit_type2(t2)?;
if cv.errors.is_empty() {
self.object_value = Some(v.clone());
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.state.data_location = current_location;
return Ok(());
}
self.errors.append(&mut cv.errors);
}
self.state.entry_counts = None;
Ok(())
}
_ => {
self.add_error(format!("expected array type, got {:?}", self.cbor));
Ok(())
}
},
Type2::ChoiceFromGroup {
ident,
generic_args,
..
} => {
if let Some(ga) = generic_args {
if let Some(rule) = rule_from_ident(self.state.cddl, ident) {
if let Some(gr) = self
.state
.generic_rules
.iter_mut()
.find(|gr| gr.name == ident.ident)
{
for arg in ga.args.iter() {
gr.args.push((*arg.arg).clone());
}
} else if let Some(params) = generic_params_from_rule(rule) {
self.state.generic_rules.push(GenericRule {
name: ident.ident,
params,
args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(),
});
}
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
self.cbor.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(
self.state.cddl,
self.cbor.clone(),
self.state.enabled_features,
);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, self.cbor.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = Some(ident.ident);
cv.state.is_group_to_choice_enum = true;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.visit_rule(rule)?;
self.errors.append(&mut cv.errors);
return Ok(());
}
}
if group_rule_from_ident(self.state.cddl, ident).is_none() {
self.add_error(format!(
"rule {} must be a group rule to turn it into a choice",
ident
));
return Ok(());
}
self.state.is_group_to_choice_enum = true;
self.visit_identifier(ident)?;
self.state.is_group_to_choice_enum = false;
Ok(())
}
Type2::ChoiceFromInlineGroup { group, .. } => {
self.state.is_group_to_choice_enum = true;
self.visit_group(group)?;
self.state.is_group_to_choice_enum = false;
Ok(())
}
Type2::Typename {
ident,
generic_args,
..
} => {
if let Some(ga) = generic_args {
if let Some(rule) = rule_from_ident(self.state.cddl, ident) {
if let Some(gr) = self
.state
.generic_rules
.iter_mut()
.find(|gr| gr.name == ident.ident)
{
for arg in ga.args.iter() {
gr.args.push((*arg.arg).clone());
}
} else if let Some(params) = generic_params_from_rule(rule) {
self.state.generic_rules.push(GenericRule {
name: ident.ident,
params,
args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(),
});
}
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
self.cbor.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(
self.state.cddl,
self.cbor.clone(),
self.state.enabled_features,
);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, self.cbor.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = Some(ident.ident);
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.visit_rule(rule)?;
self.errors.append(&mut cv.errors);
return Ok(());
}
}
let type_choice_alternates = type_choice_alternates_from_ident(self.state.cddl, ident);
if !type_choice_alternates.is_empty() {
self.state.is_multi_type_choice = true;
}
let error_count = self.errors.len();
for t in type_choice_alternates {
let cur_errors = self.errors.len();
self.visit_type(t)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
return Ok(());
}
}
self.visit_identifier(ident)
}
Type2::IntValue { value, .. } => self.visit_value(&token::Value::INT(*value)),
Type2::UintValue { value, .. } => self.visit_value(&token::Value::UINT(*value)),
Type2::FloatValue { value, .. } => self.visit_value(&token::Value::FLOAT(*value)),
Type2::UTF8ByteString { value, .. } => {
self.visit_value(&token::Value::BYTE(ByteValue::UTF8(value.clone())))
}
Type2::B16ByteString { value, .. } => {
self.visit_value(&token::Value::BYTE(ByteValue::B16(value.clone())))
}
Type2::ParenthesizedType { pt, .. } => self.visit_type(pt),
Type2::Unwrap {
ident,
generic_args,
..
} => {
if let Some(Type2::TaggedData { t, .. }) = tag_from_token(&lookup_ident(ident.ident)) {
return self.visit_type(&t);
}
if let Some(ga) = generic_args {
if let Some(rule) = unwrap_rule_from_ident(self.state.cddl, ident) {
if let Some(gr) = self
.state
.generic_rules
.iter_mut()
.find(|gr| gr.name == ident.ident)
{
for arg in ga.args.iter() {
gr.args.push((*arg.arg).clone());
}
} else if let Some(params) = generic_params_from_rule(rule) {
self.state.generic_rules.push(GenericRule {
name: ident.ident,
params,
args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(),
});
}
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
self.cbor.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(
self.state.cddl,
self.cbor.clone(),
self.state.enabled_features,
);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, self.cbor.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = Some(ident.ident);
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.visit_rule(rule)?;
self.errors.append(&mut cv.errors);
return Ok(());
}
}
if let Some(rule) = unwrap_rule_from_ident(self.state.cddl, ident) {
return self.visit_rule(rule);
}
self.add_error(format!(
"cannot unwrap identifier {}, rule not found",
ident
));
Ok(())
}
Type2::TaggedData { tag, t, .. } => match &self.cbor {
Value::Tag(actual_tag, value) => {
if let Some(tag_constraint) = tag {
if let Some(expected_tag) = tag_constraint.as_literal() {
if expected_tag != *actual_tag {
self.add_error(format!(
"expected tagged data #6.{}({}), got {:?}",
expected_tag, t, self.cbor
));
return Ok(());
}
} else {
}
} else if *actual_tag > 0 {
self.add_error(format!(
"expected tagged data #6({}), got {:?}",
t, self.cbor
));
return Ok(());
}
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
value.as_ref().clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(
self.state.cddl,
value.as_ref().clone(),
self.state.enabled_features,
);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, value.as_ref().clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.state.is_multi_group_choice = self.state.is_multi_group_choice;
cv.state.data_location.push_str(&self.state.data_location);
cv.state.type_group_name_entry = self.state.type_group_name_entry;
cv.visit_type(t)?;
self.errors.append(&mut cv.errors);
Ok(())
}
Value::Array(_) => self.validate_array_items(&ArrayItemToken::TaggedData(t2)),
_ => {
if let Some(tag) = tag {
self.add_error(format!(
"expected tagged data #6.{}({}), got {:?}",
tag, t, self.cbor
));
} else {
self.add_error(format!(
"expected tagged data #6({}), got {:?}",
t, self.cbor
));
}
Ok(())
}
},
Type2::DataMajorType { mt, constraint, .. } => match &self.cbor {
Value::Integer(i) => {
match mt {
0u8 => match constraint {
Some(c) => {
if let Some(literal_val) = c.as_literal() {
if i128::from(*i) == literal_val as i128 && i128::from(*i) >= 0i128 {
return Ok(());
}
}
self.add_error(format!(
"expected uint data type with constraint {} (#{}.{}), got {:?}",
c, mt, c, self.cbor
));
return Ok(());
}
_ => {
if i128::from(*i).is_negative() {
self.add_error(format!(
"expected uint data type (#{}), got {:?}",
mt, self.cbor
));
return Ok(());
}
}
},
1u8 => match constraint {
Some(c) => {
if let Some(literal_val) = c.as_literal() {
if i128::from(*i) == 0i128 - literal_val as i128 {
return Ok(());
}
}
self.add_error(format!(
"expected nint type with constraint {} (#{}.{}), got {:?}",
c, mt, c, self.cbor
));
return Ok(());
}
_ => {
if i128::from(*i) >= 0i128 {
self.add_error(format!(
"expected nint data type (#{}), got {:?}",
mt, self.cbor
));
return Ok(());
}
}
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got {:?}",
mt, constraint, self.cbor
)),
}
Ok(())
}
Value::Bytes(b) => {
match mt {
2u8 => match constraint {
Some(c) if c.is_literal(b.len() as u64) => return Ok(()),
Some(c) => self.add_error(format!(
"expected byte string type with constraint {} (#{}.{}), got {:?}",
c, mt, c, self.cbor
)),
_ => return Ok(()),
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got {:?}",
mt, constraint, self.cbor
)),
}
Ok(())
}
Value::Text(t) => {
match mt {
3u8 => match constraint {
Some(c) if c.is_literal(t.len() as u64) => return Ok(()),
Some(c) => self.add_error(format!(
"expected text string type with constraint {} (#{}.{}), got {:?}",
c, mt, c, self.cbor
)),
_ => return Ok(()),
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got {:?}",
mt, constraint, self.cbor
)),
}
Ok(())
}
Value::Array(a) => {
match mt {
4u8 => match constraint {
Some(c) if c.is_literal(a.len() as u64) => return Ok(()),
Some(c) => self.add_error(format!(
"expected array type with constraint {} (#{}.{}), got {:?}",
c, mt, c, self.cbor
)),
_ => return Ok(()),
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got {:?}",
mt, constraint, self.cbor
)),
}
Ok(())
}
Value::Map(m) => {
match mt {
5u8 => match constraint {
Some(c) if c.is_literal(m.len() as u64) => return Ok(()),
Some(c) => self.add_error(format!(
"expected map type with constraint {} (#{}.{}), got {:?}",
c, mt, c, self.cbor
)),
_ => return Ok(()),
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got {:?}",
mt, constraint, self.cbor
)),
}
Ok(())
}
Value::Float(_f) => {
match mt {
7u8 => match constraint {
Some(_c) => {
self.add_error(format!(
"expected simple value with constraint {} (#{}.{}), got {:?}",
_c, mt, _c, self.cbor
));
return Ok(());
}
_ => return Ok(()),
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got {:?}",
mt, constraint, self.cbor
)),
}
Ok(())
}
Value::Bool(b) => {
match mt {
7u8 => match constraint {
Some(c) => {
let expected = if *b { 21u64 } else { 20u64 };
if c.is_literal(expected) {
return Ok(());
}
self.add_error(format!(
"expected simple value with constraint {} (#{}.{}), got {:?}",
c, mt, c, self.cbor
));
return Ok(());
}
_ => return Ok(()),
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got {:?}",
mt, constraint, self.cbor
)),
}
Ok(())
}
Value::Null => {
match mt {
7u8 => match constraint {
Some(c) => {
if c.is_literal(22u64) {
return Ok(());
}
self.add_error(format!(
"expected simple value with constraint {} (#{}.{}), got {:?}",
c, mt, c, self.cbor
));
return Ok(());
}
_ => return Ok(()),
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got {:?}",
mt, constraint, self.cbor
)),
}
Ok(())
}
Value::Simple(s) => {
match mt {
7u8 => match constraint {
Some(c) => {
if c.is_literal(*s as u64) {
return Ok(());
}
self.add_error(format!(
"expected simple value with constraint {} (#{}.{}), got simple({})",
c, mt, c, s
));
return Ok(());
}
_ => return Ok(()),
},
_ => self.add_error(format!(
"expected major type {} with constraint {:?}, got simple({})",
mt, constraint, s
)),
}
Ok(())
}
_ => {
if let Some(constraint) = constraint {
self.add_error(format!(
"expected major type #{}.{}, got {:?}",
mt, constraint, self.cbor
));
} else {
self.add_error(format!("expected major type #{}, got {:?}", mt, self.cbor));
}
Ok(())
}
},
#[cfg(feature = "ast-span")]
Type2::Any { .. } => Ok(()),
#[cfg(not(feature = "ast-span"))]
Type2::Any {} => Ok(()),
_ => {
self.add_error(format!(
"unsupported data type for validating cbor, got {}",
t2
));
Ok(())
}
}
}
fn visit_identifier(&mut self, ident: &Identifier<'a>) -> visitor::Result<Error<T>> {
if let Some(name) = self.state.eval_generic_rule {
if let Some(gr) = self
.state
.generic_rules
.iter()
.find(|&gr| gr.name == name)
.cloned()
{
for (idx, gp) in gr.params.iter().enumerate() {
if *gp == ident.ident {
if let Some(arg) = gr.args.get(idx) {
return self.visit_type1(arg);
}
}
}
}
}
if !self.state.is_colon_shortcut_present {
if let Some(r) = rule_from_ident(self.state.cddl, ident) {
let rule_key = ident.ident.to_string();
if self.state.visited_rules.contains(&rule_key) {
return Ok(());
}
self.state.visited_rules.insert(rule_key.clone());
let result = self.visit_rule(r);
self.state.visited_rules.remove(&rule_key);
return result;
}
}
if is_ident_any_type(self.state.cddl, ident) {
return Ok(());
}
if let Value::Array(_) = &self.cbor {
if let Some(Rule::Type { rule, .. }) = rule_from_ident(self.state.cddl, ident) {
for tc in rule.value.type_choices.iter() {
if let Type2::Array { .. } = &tc.type1.type2 {
return self.visit_type_choice(tc);
}
}
}
}
let token = lookup_ident(ident.ident);
if let Token::DECFRAC | Token::BIGFLOAT = token {
if let Some(tagged_data_type) = tag_from_token(&token) {
return self.visit_type2(&tagged_data_type);
}
}
match &self.cbor {
Value::Null if is_ident_null_data_type(self.state.cddl, ident) => Ok(()),
Value::Bytes(_) if is_ident_byte_string_data_type(self.state.cddl, ident) => Ok(()),
Value::Bool(b) => {
if is_ident_bool_data_type(self.state.cddl, ident) {
return Ok(());
}
if ident_matches_bool_value(self.state.cddl, ident, *b) {
return Ok(());
}
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
Ok(())
}
Value::Integer(i) => {
if is_ident_uint_data_type(self.state.cddl, ident) {
if i128::from(*i).is_negative() {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
}
Ok(())
} else if is_ident_integer_data_type(self.state.cddl, ident) {
Ok(())
} else if is_ident_time_data_type(self.state.cddl, ident) {
if let chrono::LocalResult::None =
Utc.timestamp_millis_opt((i128::from(*i) * 1000) as i64)
{
let i = *i;
self.add_error(format!(
"expected time data type, invalid UNIX timestamp {:?}",
i,
));
}
Ok(())
} else {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
Ok(())
}
}
Value::Float(f) => {
if is_ident_float_data_type(self.state.cddl, ident) {
Ok(())
} else if is_ident_time_data_type(self.state.cddl, ident) {
if let chrono::LocalResult::None = Utc.timestamp_millis_opt((*f * 1000f64) as i64) {
let f = *f;
self.add_error(format!(
"expected time data type, invalid UNIX timestamp {:?}",
f,
));
}
Ok(())
} else {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
Ok(())
}
}
Value::Text(s) => {
if is_ident_uri_data_type(self.state.cddl, ident) {
if let Err(e) = uriparse::URI::try_from(&**s) {
self.add_error(format!("expected URI data type, decoding error: {}", e));
}
} else if is_ident_b64url_data_type(self.state.cddl, ident) {
if let Err(e) = base64_url::decode(s) {
self.add_error(format!(
"expected base64 URL data type, decoding error: {}",
e
));
}
} else if is_ident_tdate_data_type(self.state.cddl, ident) {
if let Err(e) = chrono::DateTime::parse_from_rfc3339(s) {
self.add_error(format!("expected tdate data type, decoding error: {}", e));
}
} else if is_ident_string_data_type(self.state.cddl, ident) {
return Ok(());
} else {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
}
Ok(())
}
Value::Tag(tag, value) => {
match *tag {
0 => {
if is_ident_tdate_data_type(self.state.cddl, ident) {
if let Value::Text(value) = value.as_ref() {
if let Err(e) = chrono::DateTime::parse_from_rfc3339(value) {
self.add_error(format!("expected tdate data type, decoding error: {}", e));
}
} else {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
}
} else {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
}
}
1 => {
if is_ident_time_data_type(self.state.cddl, ident) {
if let Value::Integer(value) = *value.as_ref() {
let dt = Utc.timestamp_opt(value.try_into().unwrap(), 0);
if let chrono::LocalResult::None = dt {
self.add_error(format!(
"expected time data type, invalid UNIX timestamp {:?}",
self.cbor
));
}
} else if let Value::Float(value) = value.as_ref() {
let seconds = value.trunc() as i64;
let nanoseconds = (value.fract() * 1e9) as u32;
let dt = Utc.timestamp_opt(seconds, nanoseconds);
if let chrono::LocalResult::None = dt {
self.add_error(format!(
"expected time data type, invalid UNIX timestamp {:?}",
self.cbor
));
}
} else {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
}
} else {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
}
}
_ => (),
}
Ok(())
}
Value::Array(_) => self.validate_array_items(&ArrayItemToken::Identifier(ident)),
Value::Map(m) => {
match &self.state.occurrence {
#[cfg(feature = "ast-span")]
Some(Occur::Optional { .. }) | None => {
if is_ident_string_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Text(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_integer_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Integer(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_bool_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bool(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_null_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_byte_string_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bytes(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_float_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if token::lookup_ident(ident.ident)
.in_standard_prelude()
.is_some()
{
self.add_error(format!(
"expected object value of type {}, got object",
ident.ident
));
return Ok(());
}
self.visit_value(&token::Value::TEXT(ident.ident.into()))
}
#[cfg(not(feature = "ast-span"))]
Some(Occur::Optional {}) | None => {
if is_ident_string_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Text(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
self.state.data_location.push_str(&format!("/{}", value));
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_integer_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Integer(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
self.state.data_location.push_str(&format!("/{}", value));
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_bool_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bool(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
self.state.data_location.push_str(&format!("/{}", value));
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_null_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
self.state.data_location.push_str(&format!("/{}", value));
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_byte_string_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bytes(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
self.state.data_location.push_str(&format!("/{}", value));
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_float_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
self.state.data_location.push_str(&format!("/{}", value));
} else {
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if token::lookup_ident(ident.ident)
.in_standard_prelude()
.is_some()
{
self.add_error(format!(
"expected object value of type {}, got object",
ident.ident
));
return Ok(());
}
self.visit_value(&token::Value::TEXT(ident.ident.into()))
}
Some(occur) => {
let mut errors = Vec::new();
if is_ident_string_data_type(self.state.cddl, ident) {
let values_to_validate = m
.iter()
.filter_map(|(k, v)| {
if let Some(keys) = &self.validated_keys {
if !keys.contains(k) {
if matches!(k, Value::Text(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
} else {
None
}
} else if matches!(k, Value::Text(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
})
.collect::<Vec<_>>();
self.values_to_validate = Some(values_to_validate);
}
if is_ident_integer_data_type(self.state.cddl, ident) {
let mut errors = Vec::new();
let values_to_validate = m
.iter()
.filter_map(|(k, v)| {
if let Some(keys) = &self.validated_keys {
if !keys.contains(k) {
if matches!(k, Value::Integer(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
} else {
None
}
} else if matches!(k, Value::Integer(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
})
.collect::<Vec<_>>();
self.values_to_validate = Some(values_to_validate);
}
if is_ident_bool_data_type(self.state.cddl, ident) {
let mut errors = Vec::new();
let values_to_validate = m
.iter()
.filter_map(|(k, v)| {
if let Some(keys) = &self.validated_keys {
if !keys.contains(k) {
if matches!(k, Value::Bool(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
} else {
None
}
} else if matches!(k, Value::Bool(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
})
.collect::<Vec<_>>();
self.values_to_validate = Some(values_to_validate);
}
if is_ident_byte_string_data_type(self.state.cddl, ident) {
let mut errors = Vec::new();
let values_to_validate = m
.iter()
.filter_map(|(k, v)| {
if let Some(keys) = &self.validated_keys {
if !keys.contains(k) {
if matches!(k, Value::Bytes(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
} else {
None
}
} else if matches!(k, Value::Bytes(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
})
.collect::<Vec<_>>();
self.values_to_validate = Some(values_to_validate);
}
if is_ident_null_data_type(self.state.cddl, ident) {
let mut errors = Vec::new();
let values_to_validate = m
.iter()
.filter_map(|(k, v)| {
if let Some(keys) = &self.validated_keys {
if !keys.contains(k) {
if matches!(k, Value::Null) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
} else {
None
}
} else if matches!(k, Value::Null) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
})
.collect::<Vec<_>>();
self.values_to_validate = Some(values_to_validate);
}
if is_ident_float_data_type(self.state.cddl, ident) {
let mut errors = Vec::new();
let values_to_validate = m
.iter()
.filter_map(|(k, v)| {
if let Some(keys) = &self.validated_keys {
if !keys.contains(k) {
if matches!(k, Value::Float(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
} else {
None
}
} else if matches!(k, Value::Float(_)) {
Some(v.clone())
} else {
errors.push(format!("key of type {} required, got {:?}", ident, k));
None
}
})
.collect::<Vec<_>>();
self.values_to_validate = Some(values_to_validate);
}
if !errors.is_empty() {
for e in errors.into_iter() {
self.add_error(e);
}
return Ok(());
}
#[cfg(feature = "ast-span")]
if let Occur::ZeroOrMore { .. } | Occur::OneOrMore { .. } = occur {
if let Occur::OneOrMore { .. } = occur {
if m.is_empty() {
self.add_error(format!(
"map cannot be empty, one or more entries with key type {} required",
ident
));
return Ok(());
}
}
} else if let Occur::Exact { lower, upper, .. } = occur {
if let Some(values_to_validate) = &self.values_to_validate {
if let Some(lower) = lower {
if let Some(upper) = upper {
if values_to_validate.len() < *lower || values_to_validate.len() > *upper {
if lower == upper {
self.add_error(format!(
"object must contain exactly {} entries of key of type {}",
lower, ident,
));
} else {
self.add_error(format!(
"object must contain between {} and {} entries of key of type {}",
lower, upper, ident,
));
}
return Ok(());
}
}
if values_to_validate.len() < *lower {
self.add_error(format!(
"object must contain at least {} entries of key of type {}",
lower, ident,
));
return Ok(());
}
}
if let Some(upper) = upper {
if values_to_validate.len() > *upper {
self.add_error(format!(
"object must contain no more than {} entries of key of type {}",
upper, ident,
));
return Ok(());
}
}
return Ok(());
}
}
#[cfg(not(feature = "ast-span"))]
if let Occur::ZeroOrMore {} | Occur::OneOrMore {} = occur {
if let Occur::OneOrMore {} = occur {
if m.is_empty() {
self.add_error(format!(
"object cannot be empty, one or more entries with key type {} required",
ident
));
return Ok(());
}
}
} else if let Occur::Exact { lower, upper } = occur {
if let Some(values_to_validate) = &self.values_to_validate {
if let Some(lower) = lower {
if let Some(upper) = upper {
if values_to_validate.len() < *lower || values_to_validate.len() > *upper {
if lower == upper {
self.add_error(format!(
"object must contain exactly {} entries of key of type {}",
lower, ident,
));
} else {
self.add_error(format!(
"object must contain between {} and {} entries of key of type {}",
lower, upper, ident,
));
}
return Ok(());
}
}
if values_to_validate.len() < *lower {
self.add_error(format!(
"object must contain at least {} entries of key of type {}",
lower, ident,
));
return Ok(());
}
}
if let Some(upper) = upper {
if values_to_validate.len() > *upper {
self.add_error(format!(
"object must contain no more than {} entries of key of type {}",
upper, ident,
));
return Ok(());
}
}
return Ok(());
}
}
if is_ident_string_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Text(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty())
|| (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty())
{
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_integer_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Integer(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty())
|| (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty())
{
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_bool_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bool(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty())
|| (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty())
{
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_null_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty())
|| (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty())
{
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_byte_string_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bytes(_))) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty())
|| (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty())
{
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if is_ident_float_data_type(self.state.cddl, ident) && !self.validating_value {
if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) {
self
.validated_keys
.get_or_insert(vec![k.clone()])
.push(k.clone());
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{:?}", v);
} else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty())
|| (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty())
{
self.add_error(format!("map requires entry key of type {}", ident));
}
return Ok(());
}
if token::lookup_ident(ident.ident)
.in_standard_prelude()
.is_some()
{
self.add_error(format!(
"expected object value of type {}, got object",
ident.ident
));
return Ok(());
}
self.visit_value(&token::Value::TEXT(ident.ident.into()))
}
}
}
_ => {
if let Some(cut_value) = self.cut_value.take() {
self.add_error(format!(
"cut present for member key {}. expected type {}, got {:?}",
cut_value, ident, self.cbor
));
} else {
self.add_error(format!("expected type {}, got {:?}", ident, self.cbor));
}
Ok(())
}
}
}
fn visit_value_member_key_entry(
&mut self,
entry: &ValueMemberKeyEntry<'a>,
) -> visitor::Result<Error<T>> {
if let Some(occur) = &entry.occur {
self.visit_occurrence(occur)?;
}
let current_location = self.state.data_location.clone();
if let Some(mk) = &entry.member_key {
let error_count = self.errors.len();
self.state.is_member_key = true;
self.visit_memberkey(mk)?;
self.state.is_member_key = false;
if self.errors.len() != error_count {
self.state.advance_to_next_entry = true;
return Ok(());
}
}
if let Some(values) = &self.values_to_validate {
for v in values.iter() {
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
v.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(self.state.cddl, v.clone(), self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, v.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.state.is_multi_group_choice = self.state.is_multi_group_choice;
cv.state.data_location.push_str(&self.state.data_location);
cv.state.type_group_name_entry = self.state.type_group_name_entry;
cv.validating_value = true;
cv.visit_type(&entry.entry_type)?;
self.state.data_location = current_location.clone();
self.errors.append(&mut cv.errors);
if entry.occur.is_some() {
self.state.occurrence = None;
}
}
return Ok(());
}
if let Some(v) = self.object_value.take() {
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(self.state.cddl, v, self.state.enabled_features.clone());
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(self.state.cddl, v, self.state.enabled_features);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, v);
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.state.is_multi_group_choice = self.state.is_multi_group_choice;
cv.state.data_location.push_str(&self.state.data_location);
cv.state.type_group_name_entry = self.state.type_group_name_entry;
cv.visit_type(&entry.entry_type)?;
self.state.data_location = current_location;
self.errors.append(&mut cv.errors);
if entry.occur.is_some() {
self.state.occurrence = None;
}
Ok(())
} else if !self.state.advance_to_next_entry {
if let Value::Array(a) = &self.cbor {
if self.state.group_entry_idx == Some(0) && self.state.occurrence.is_none() {
let length_errors: Vec<String> =
if let Some(entry_counts) = self.state.entry_counts.as_ref() {
let len = a.len();
if !validate_entry_count(entry_counts, len) {
if entry_counts.len() > 1 {
let counts: Vec<String> =
entry_counts.iter().map(|ec| ec.count.to_string()).collect();
vec![format!(
"expected array with length matching one of [{}], got {}",
counts.join(", "),
len
)]
} else {
entry_counts
.iter()
.map(|ec| {
if let Some(occur) = &ec.entry_occurrence {
format!("expected array with length per occurrence {}", occur)
} else {
format!("expected array with length {}, got {}", ec.count, len)
}
})
.collect()
}
} else {
Vec::new()
}
} else {
Vec::new()
};
if !length_errors.is_empty() {
for e in length_errors {
self.add_error(e);
}
return Ok(());
}
}
if let Some(idx) = self.state.group_entry_idx {
if let Some(element_value) = a.get(idx) {
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
element_value.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(
self.state.cddl,
element_value.clone(),
self.state.enabled_features,
);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.cddl, element_value.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = self.state.eval_generic_rule;
cv.state.is_multi_group_choice = self.state.is_multi_group_choice; let _ = write!(
cv.state.data_location,
"{}/{}",
self.state.data_location, idx
); cv.state.type_group_name_entry = self.state.type_group_name_entry;
cv.visit_type(&entry.entry_type)?;
self.errors.append(&mut cv.errors);
if entry.occur.is_some() {
self.state.occurrence = None;
}
return Ok(()); } else {
#[cfg(feature = "ast-span")]
if !matches!(
self.state.occurrence,
Some(Occur::Optional { .. }) | Some(Occur::ZeroOrMore { .. })
) {
self.add_error(format!(
"expected array element at index {}, but array only has {} elements",
idx,
a.len()
));
}
#[cfg(not(feature = "ast-span"))]
if !matches!(
self.state.occurrence,
Some(Occur::Optional {}) | Some(Occur::ZeroOrMore {})
) {
self.add_error(format!(
"expected array element at index {}, but array only has {} elements",
idx,
a.len()
));
}
return Ok(());
}
}
}
self.visit_type(&entry.entry_type)
} else {
Ok(())
}
}
fn visit_type_groupname_entry(
&mut self,
entry: &TypeGroupnameEntry<'a>,
) -> visitor::Result<Error<T>> {
self.state.type_group_name_entry = Some(entry.name.ident);
if let Some(ga) = &entry.generic_args {
if let Some(rule) = rule_from_ident(self.state.cddl, &entry.name) {
if let Some(gr) = self
.state
.generic_rules
.iter_mut()
.find(|gr| gr.name == entry.name.ident)
{
for arg in ga.args.iter() {
gr.args.push((*arg.arg).clone());
}
} else if let Some(params) = generic_params_from_rule(rule) {
self.state.generic_rules.push(GenericRule {
name: entry.name.ident,
params,
args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(),
});
}
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut cv = CBORValidator::new(
self.state.cddl,
self.cbor.clone(),
self.state.enabled_features.clone(),
);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut cv = CBORValidator::new(
self.state.cddl,
self.cbor.clone(),
self.state.enabled_features,
);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(self.state.cddl, self.cbor.clone());
cv.state.generic_rules = self.state.generic_rules.clone();
cv.state.eval_generic_rule = Some(entry.name.ident);
cv.state.is_multi_type_choice = self.state.is_multi_type_choice;
cv.visit_rule(rule)?;
self.errors.append(&mut cv.errors);
return Ok(());
}
}
let type_choice_alternates = type_choice_alternates_from_ident(self.state.cddl, &entry.name);
if !type_choice_alternates.is_empty() {
self.state.is_multi_type_choice = true;
}
let error_count = self.errors.len();
for t in type_choice_alternates {
let cur_errors = self.errors.len();
self.visit_type(t)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
return Ok(());
}
}
let error_count = self.errors.len();
let group_choice_alternates = group_choice_alternates_from_ident(self.state.cddl, &entry.name);
if !group_choice_alternates.is_empty() {
self.state.is_multi_group_choice = true;
}
for ge in group_choice_alternates {
let cur_errors = self.errors.len();
self.visit_group_entry(ge)?;
if self.errors.len() == cur_errors {
for _ in 0..self.errors.len() - error_count {
self.errors.pop();
}
return Ok(());
}
}
walk_type_groupname_entry(self, entry)?;
self.state.type_group_name_entry = None;
Ok(())
}
fn visit_memberkey(&mut self, mk: &MemberKey<'a>) -> visitor::Result<Error<T>> {
match mk {
MemberKey::Type1 { is_cut, .. } => {
self.state.is_cut_present = *is_cut;
walk_memberkey(self, mk)?;
self.state.is_cut_present = false;
}
MemberKey::Bareword { .. } => {
self.state.is_colon_shortcut_present = true;
walk_memberkey(self, mk)?;
self.state.is_colon_shortcut_present = false;
}
_ => return walk_memberkey(self, mk),
}
Ok(())
}
fn visit_value(&mut self, value: &token::Value<'a>) -> visitor::Result<Error<T>> {
let error: Option<String> = match &self.cbor {
Value::Integer(i) => match value {
token::Value::INT(v) => match &self.state.ctrl {
Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT)
if i128::from(*i) != *v as i128 =>
{
None
}
Some(ControlOperator::LT) if i128::from(*i) < *v as i128 => None,
Some(ControlOperator::LE) if i128::from(*i) <= *v as i128 => None,
Some(ControlOperator::GT) if i128::from(*i) > *v as i128 => None,
Some(ControlOperator::GE) if i128::from(*i) >= *v as i128 => None,
#[cfg(feature = "additional-controls")]
Some(ControlOperator::PLUS) => {
if i128::from(*i) == *v as i128 {
None
} else {
Some(format!("expected computed .plus value {}, got {:?}", v, i))
}
}
#[cfg(feature = "additional-controls")]
None | Some(ControlOperator::FEATURE) => {
if i128::from(*i) == *v as i128 {
None
} else {
Some(format!("expected value {}, got {:?}", v, i))
}
}
#[cfg(not(feature = "additional-controls"))]
None => {
if i128::from(*i) == *v as i128 {
None
} else {
Some(format!("expected value {}, got {:?}", v, i))
}
}
_ => Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
v,
i
)),
},
token::Value::UINT(v) => match &self.state.ctrl {
Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT)
if i128::from(*i) != *v as i128 =>
{
None
}
Some(ControlOperator::LT) if i128::from(*i) < *v as i128 => None,
Some(ControlOperator::LE) if i128::from(*i) <= *v as i128 => None,
Some(ControlOperator::GT) if i128::from(*i) > *v as i128 => None,
Some(ControlOperator::GE) if i128::from(*i) >= *v as i128 => None,
Some(ControlOperator::SIZE) => match 256i128.checked_pow(*v as u32) {
Some(n) if i128::from(*i) < n => None,
_ => Some(format!("expected value .size {}, got {:?}", v, i)),
},
Some(ControlOperator::BITS) => {
if let Some(sv) = 1u32.checked_shl(*v as u32) {
if (i128::from(*i) & sv as i128) != 0 {
None
} else {
Some(format!("expected uint .bits {}, got {:?}", v, i))
}
} else {
Some(format!("expected uint .bits {}, got {:?}", v, i))
}
}
#[cfg(feature = "additional-controls")]
Some(ControlOperator::PLUS) => {
if i128::from(*i) == *v as i128 {
None
} else {
Some(format!("expected computed .plus value {}, got {:?}", v, i))
}
}
#[cfg(feature = "additional-controls")]
None | Some(ControlOperator::FEATURE) => {
if i128::from(*i) == *v as i128 {
None
} else {
Some(format!("expected value {}, got {:?}", v, i))
}
}
#[cfg(not(feature = "additional-controls"))]
None => {
if i128::from(*i) == *v as i128 {
None
} else {
Some(format!("expected value {}, got {:?}", v, i))
}
}
_ => Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
v,
i
)),
},
_ => Some(format!("expected {}, got {:?}", value, i)),
},
Value::Float(f) => match value {
token::Value::FLOAT(v) => match &self.state.ctrl {
Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT)
if (*f - *v).abs() > f64::EPSILON =>
{
None
}
Some(ControlOperator::LT) if *f < *v => None,
Some(ControlOperator::LE) if *f <= *v => None,
Some(ControlOperator::GT) if *f > *v => None,
Some(ControlOperator::GE) if *f >= *v => None,
#[cfg(feature = "additional-controls")]
Some(ControlOperator::PLUS) => {
if (*f - *v).abs() < f64::EPSILON {
None
} else {
Some(format!("expected computed .plus value {}, got {:?}", v, f))
}
}
#[cfg(feature = "additional-controls")]
None | Some(ControlOperator::FEATURE) => {
if (*f - *v).abs() < f64::EPSILON {
None
} else {
Some(format!("expected value {}, got {:?}", v, f))
}
}
#[cfg(not(feature = "additional-controls"))]
None => {
if (*f - *v).abs() < f64::EPSILON {
None
} else {
Some(format!("expected value {}, got {:?}", v, f))
}
}
_ => Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
v,
f
)),
},
_ => Some(format!("expected {}, got {:?}", value, f)),
},
Value::Text(s) => match value {
token::Value::TEXT(t) => match &self.state.ctrl {
Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) => {
if s != t {
None
} else {
Some(format!("expected {} .ne to \"{}\"", value, s))
}
}
Some(ControlOperator::REGEXP) => {
let re = regex::Regex::new(
&format_regex(t)
.ok_or_else(|| Error::from_validator(self, "malformed regex".to_string()))?,
)
.map_err(|e| Error::from_validator(self, e.to_string()))?;
if re.is_match(s) {
None
} else {
Some(format!("expected \"{}\" to match regex \"{}\"", s, t))
}
}
Some(ControlOperator::PCRE) => {
#[cfg(feature = "freezer")]
{
let anchored = format!("^(?:{})$", t);
let re = fancy_regex::Regex::new(&anchored)
.map_err(|e| Error::from_validator(self, e.to_string()))?;
match re.is_match(s) {
Ok(true) => None,
Ok(false) => Some(format!("expected \"{}\" to match pcre \"{}\"", s, t)),
Err(e) => Some(format!("pcre matching error: {}", e)),
}
}
#[cfg(not(feature = "freezer"))]
{
let re = regex::Regex::new(
&format_regex(t)
.ok_or_else(|| Error::from_validator(self, "malformed regex".to_string()))?,
)
.map_err(|e| Error::from_validator(self, e.to_string()))?;
if re.is_match(s) {
None
} else {
Some(format!("expected \"{}\" to match pcre \"{}\"", s, t))
}
}
}
#[cfg(feature = "freezer")]
Some(ControlOperator::IREGEXP) => {
let anchored = format!("^(?:{})$", t);
let re = regex::Regex::new(&anchored)
.map_err(|e| Error::from_validator(self, e.to_string()))?;
if re.is_match(s) {
None
} else {
Some(format!("expected \"{}\" to match iregexp \"{}\"", s, t))
}
}
#[cfg(feature = "additional-controls")]
Some(ControlOperator::ABNF) => validate_abnf(t, s)
.err()
.map(|e| format!("\"{}\" is not valid against abnf: {}", s, e)),
_ => {
#[cfg(feature = "additional-controls")]
if s == t {
None
} else if let Some(ControlOperator::CAT) | Some(ControlOperator::DET) = &self.state.ctrl
{
Some(format!(
"expected value to match concatenated string {}, got \"{}\"",
value, s
))
} else if let Some(ctrl) = &self.state.ctrl {
Some(format!("expected value {} {}, got \"{}\"", ctrl, value, s))
} else {
Some(format!("expected value {} got \"{}\"", value, s))
}
#[cfg(not(feature = "additional-controls"))]
if s == t {
None
} else if let Some(ctrl) = &self.state.ctrl {
Some(format!("expected value {} {}, got \"{}\"", ctrl, value, s))
} else {
Some(format!("expected value {} got \"{}\"", value, s))
}
}
},
token::Value::UINT(u) => match &self.state.ctrl {
Some(ControlOperator::SIZE) => {
if s.len() == *u {
None
} else {
Some(format!("expected \"{}\" .size {}, got {}", s, u, s.len()))
}
}
_ => Some(format!("expected {}, got {}", u, s)),
},
token::Value::BYTE(token::ByteValue::UTF8(b)) if s.as_bytes() == b.as_ref() => None,
token::Value::BYTE(token::ByteValue::B16(b)) if s.as_bytes() == b.as_ref() => None,
token::Value::BYTE(token::ByteValue::B64(b)) if s.as_bytes() == b.as_ref() => None,
_ => Some(format!("expected {}, got \"{}\"", value, s)),
},
Value::Bytes(b) => match value {
token::Value::UINT(v) => match &self.state.ctrl {
Some(ControlOperator::SIZE) => {
if let Some(range_upper) = self.range_upper.as_ref() {
let len = b.len();
if len < *v || len > *range_upper {
Some(format!(
"expected bytes .size to be in range {} <= value <= {}, got {}",
v, range_upper, len
))
} else {
None
}
} else if b.len() == *v {
None
} else {
Some(format!("expected \"{:?}\" .size {}, got {}", b, v, b.len()))
}
}
Some(ControlOperator::BITS) => {
if let Some(rsv) = v.checked_shr(3) {
if let Some(s) = b.get(rsv) {
if let Some(lsv) = 1u32.checked_shl(*v as u32 & 7) {
if (*s as u32 & lsv) != 0 {
None
} else {
Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
v,
b
))
}
} else {
Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
v,
b
))
}
} else {
Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
v,
b
))
}
} else {
Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
v,
b
))
}
}
_ => {
if let Some(ctrl) = self.state.ctrl {
Some(format!("expected value {} {}, got {:?}", ctrl, v, b))
} else {
Some(format!("expected value {}, got {:?}", v, b))
}
}
},
#[cfg(feature = "additional-controls")]
token::Value::TEXT(t) => match &self.state.ctrl {
Some(ControlOperator::ABNFB) => {
validate_abnf(t, std::str::from_utf8(b).map_err(Error::UTF8Parsing)?)
.err()
.map(|e| {
format!(
"cbor bytes \"{:?}\" are not valid against abnf {}: {}",
b, t, e
)
})
}
_ => Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
t,
b
)),
},
#[cfg(feature = "additional-controls")]
token::Value::BYTE(bv) => match &self.state.ctrl {
Some(ControlOperator::ABNFB) => match bv {
ByteValue::UTF8(utf8bv) => validate_abnf(
std::str::from_utf8(utf8bv).map_err(Error::UTF8Parsing)?,
std::str::from_utf8(b).map_err(Error::UTF8Parsing)?,
)
.err()
.map(|e| {
format!(
"cbor bytes \"{:?}\" are not valid against abnf {}: {}",
b, bv, e
)
}),
ByteValue::B16(b16bv) => validate_abnf(
std::str::from_utf8(&base16::decode(b16bv).map_err(Error::Base16Decoding)?)
.map_err(Error::UTF8Parsing)?,
std::str::from_utf8(b).map_err(Error::UTF8Parsing)?,
)
.err()
.map(|e| {
format!(
"cbor bytes \"{:?}\" are not valid against abnf {}: {}",
b, bv, e
)
}),
ByteValue::B64(b64bv) => validate_abnf(
std::str::from_utf8(
&data_encoding::BASE64URL
.decode(b64bv)
.map_err(Error::Base64Decoding)?,
)
.map_err(Error::UTF8Parsing)?,
std::str::from_utf8(b).map_err(Error::UTF8Parsing)?,
)
.err()
.map(|e| {
format!(
"cbor bytes \"{:?}\" are not valid against abnf {}: {}",
b, bv, e
)
}),
},
_ => Some(format!(
"expected value {} {}, got {:?}",
self.state.ctrl.unwrap(),
bv,
b
)),
},
_ => Some(format!("expected {}, got {:?}", value, b)),
},
Value::Array(_) => {
self.validate_array_items(&ArrayItemToken::Value(value))?;
None
}
Value::Map(o) => {
if self.state.is_cut_present {
self.cut_value = Some(Type1::from(value.clone()));
}
if let token::Value::TEXT(Cow::Borrowed("any")) = value {
return Ok(());
}
let k = token_value_into_cbor_value(value.clone());
#[cfg(feature = "ast-span")]
if let Some(v) = o
.iter()
.find_map(|entry| if entry.0 == k { Some(&entry.1) } else { None })
{
self.validated_keys.get_or_insert(vec![k.clone()]).push(k);
self.object_value = Some(v.clone());
let _ = write!(self.state.data_location, "/{}", value);
None
} else if let Some(Occur::Optional { .. }) | Some(Occur::ZeroOrMore { .. }) =
&self.state.occurrence.take()
{
self.state.advance_to_next_entry = true;
None
} else if let Some(Occur::Exact {
lower: None,
upper: None,
..
}) = &self.state.occurrence.take()
{
self.state.advance_to_next_entry = true;
None
} else if let Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) = &self.state.ctrl
{
None
} else {
Some(format!("object missing key: {}", value))
}
#[cfg(not(feature = "ast-span"))]
if let Some(v) = o
.iter()
.find_map(|entry| if entry.0 == k { Some(&entry.1) } else { None })
{
self.validated_keys.get_or_insert(vec![k.clone()]).push(k);
self.object_value = Some(v.clone());
self.state.data_location.push_str(&format!("/{}", value));
None
} else if let Some(Occur::Optional {}) | Some(Occur::ZeroOrMore {}) =
&self.state.occurrence.take()
{
self.state.advance_to_next_entry = true;
None
} else if let Some(Token::NE) | Some(Token::DEFAULT) = &self.state.ctrl {
None
} else {
Some(format!("object missing key: {}", value))
}
}
_ => Some(format!("expected {}, got {:?}", value, self.cbor)),
};
if let Some(e) = error {
self.add_error(e);
}
Ok(())
}
fn visit_occurrence(&mut self, o: &Occurrence<'a>) -> visitor::Result<Error<T>> {
self.state.occurrence = Some(o.occur);
Ok(())
}
}
pub fn token_value_into_cbor_value(value: token::Value) -> Value {
match value {
token::Value::UINT(i) => Value::Integer(i.into()),
token::Value::INT(i) => Value::Integer(i.into()),
token::Value::FLOAT(f) => Value::Float(f),
token::Value::TEXT(t) => Value::Text(t.to_string()),
token::Value::BYTE(b) => match b {
ByteValue::UTF8(b) | ByteValue::B16(b) | ByteValue::B64(b) => Value::Bytes(b.into_owned()),
},
}
}
#[cfg(feature = "freezer")]
fn extract_bitfield_widths(controller: &Type2) -> Option<u128> {
if let Type2::Array { group, .. } = controller {
let mut total_bits: u128 = 0;
for gc in group.group_choices.iter() {
for (ge, _) in gc.group_entries.iter() {
match ge {
GroupEntry::ValueMemberKey { ge: vmke, .. } => {
if let Some(width) = extract_uint_from_type(&vmke.entry_type) {
total_bits = total_bits.checked_add(width as u128)?;
} else {
return None;
}
}
GroupEntry::TypeGroupname { ge: tge, .. } => {
if let Ok(width) = tge.name.ident.parse::<usize>() {
total_bits = total_bits.checked_add(width as u128)?;
} else {
return None;
}
}
_ => return None,
}
}
}
Some(total_bits)
} else {
None
}
}
#[cfg(feature = "freezer")]
fn extract_uint_from_type(t: &crate::ast::Type) -> Option<usize> {
if t.type_choices.len() != 1 {
return None;
}
let tc = &t.type_choices[0];
extract_uint_from_type2(&tc.type1.type2)
}
#[cfg(feature = "freezer")]
fn extract_uint_from_type2(t2: &Type2) -> Option<usize> {
match t2 {
Type2::UintValue { value, .. } => Some(*value),
Type2::IntValue { value, .. } if *value >= 0 => Some(*value as usize),
_ => None,
}
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
use ciborium::cbor;
use indoc::indoc;
#[cfg(not(feature = "additional-controls"))]
#[test]
fn validate() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
tcpflagbytes = bstr .bits flags
flags = &(
fin: 8,
syn: 9,
rst: 10,
psh: 11,
ack: 12,
urg: 13,
ece: 14,
cwr: 15,
ns: 0,
) / (4..7) ; data offset bits
"#
);
let cbor = Value::Bytes(vec![0x90, 0x6d]);
let cddl = crate::cddl_from_str(cddl, true)?;
let mut cv = CBORValidator::new(&cddl, cbor);
cv.validate()?;
Ok(())
}
#[cfg(feature = "additional-controls")]
#[test]
fn validate_abnfb_1() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
oid = bytes .abnfb ("oid" .det cbor-tags-oid)
roid = bytes .abnfb ("roid" .det cbor-tags-oid)
cbor-tags-oid = '
oid = 1*arc
roid = *arc
arc = [nlsb] %x00-7f
nlsb = %x81-ff *%x80-ff
'
"#
);
let sha256_oid = "2.16.840.1.101.3.4.2.1";
let cbor = Value::Bytes(sha256_oid.as_bytes().to_vec());
let cddl = cddl_from_str(cddl, true)?;
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
Ok(())
}
#[cfg(feature = "additional-controls")]
#[test]
fn validate_feature() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
v = JC<"v", 2>
JC<J, C> = J .feature "json" / C .feature "cbor"
"#
);
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing);
if let Err(e) = &cddl {
println!("{}", e);
}
let cbor = Value::Integer(2.into());
let cddl = cddl.unwrap();
let mut cv = CBORValidator::new(&cddl, cbor, Some(&["cbor"]));
cv.validate()?;
Ok(())
}
#[test]
fn validate_type_choice_alternate() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
tester = [ $vals ]
$vals /= 12
$vals /= 13
"#
);
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing);
if let Err(e) = &cddl {
println!("{}", e);
}
let cbor = Value::from(ciborium::cbor!([13]).unwrap());
let cddl = cddl.unwrap();
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
Ok(())
}
#[test]
fn validate_group_choice_alternate_in_array(
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
tester = [$$val]
$$val //= (
type: 10,
data: uint
)
$$val //= (
type: 11,
data: tstr
)
"#
);
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing);
if let Err(e) = &cddl {
println!("{}", e);
}
let cbor = Value::from(ciborium::cbor!([11, "test"]).unwrap());
let cddl = cddl.unwrap();
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
Ok(())
}
#[test]
fn validate_tdate_tag() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
root = time
"#
);
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing);
if let Err(e) = &cddl {
println!("{}", e);
}
let cbor = Value::Tag(1, Box::from(Value::Float(1680965875.01_f64)));
let cddl = cddl.unwrap();
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
Ok(())
}
#[test]
fn validate_abnfb_2() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
; Binary ABNF Test Schema
test_cbor = {
61285: sub_map
}
sub_map = {
1: signature_abnf
}
signature = bytes .size 64
signature_abnf = signature .abnfb '
ANYDATA
ANYDATA = *OCTET
OCTET = %x00-FF
'
"#
);
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing);
if let Err(e) = &cddl {
println!("{}", e);
}
let cbor = Value::Map(vec![(
Value::Integer(61285.into()),
Value::Map(vec![(
Value::Integer(1.into()),
Value::Bytes(b"test".to_vec()),
)]),
)]);
let cddl = cddl.unwrap();
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
Ok(())
}
#[test]
fn multi_type_choice_type_rule_array_validation(
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
Ref = nil / refShort / refFull
blobSize = uint
hashID = uint .lt 23
hashName = text
hashDigest = bytes
refShort = [ blobSize, hashID, hashDigest ]
refFull = { 1: blobSize, 2: hashName, 3: hashDigest }
"#
);
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing);
if let Err(e) = &cddl {
println!("{}", e);
}
let cbor = Value::Array(vec![
Value::Integer(3.into()),
Value::Integer(2.into()),
Value::Bytes(
base16::decode("BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD").unwrap(),
),
]);
let cddl = cddl.unwrap();
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
Ok(())
}
#[test]
fn tagged_data_in_array_validation() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
start = [ * help ]
help = #6.123(bstr)
"#
);
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing);
if let Err(e) = &cddl {
println!("{}", e);
}
let cbor = Value::Array(vec![Value::Tag(
123,
Box::from(Value::Bytes(base16::decode("00").unwrap())),
)]);
let cddl = cddl.unwrap();
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
Ok(())
}
#[test]
fn test_conditional_array_validation() {
let cddl_str = r#"
NestedPart = [
disposition: 0,
language: tstr,
partIndex: uint,
( NullPart // SinglePart )
]
NullPart = ( cardinality: 0 )
SinglePart = (
cardinality: 1,
contentType: tstr,
content: bstr
)
"#;
let cddl = cddl_from_str(cddl_str, true).unwrap();
let cbor_data = Value::Array(vec![
Value::Integer(0.into()), Value::Text("en".to_string()), Value::Integer(1.into()), Value::Integer(1.into()), Value::Text("text/plain".to_string()), Value::Bytes(b"hello world".to_vec()), ]);
#[cfg(all(feature = "additional-controls", target_arch = "wasm32"))]
let mut validator = CBORValidator::new(&cddl, cbor_data, None);
#[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))]
let mut validator = CBORValidator::new(&cddl, cbor_data, None);
#[cfg(not(feature = "additional-controls"))]
let mut validator = CBORValidator::new(&cddl, cbor_data);
let result = validator.validate();
assert!(
result.is_ok(),
"Validation should succeed for SinglePart structure: {:?}",
result
);
}
#[test]
fn extract_cbor() {
let cbor = Value::Float(1.23);
let cddl = cddl_from_str("start = any", true).unwrap();
let cv = CBORValidator::new(&cddl, cbor, None);
assert_eq!(cv.extract_cbor(), Value::Float(1.23));
}
#[test]
fn validate_bstr_size_range() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
m = { field: bstr .size (16..1000) }
"#
);
let cddl = cddl_from_str(cddl, true)?;
let valid_bytes = vec![0u8; 100];
let valid_cbor = Value::Map(vec![(
Value::Text("field".to_string()),
Value::Bytes(valid_bytes),
)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, valid_cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, valid_cbor);
assert!(cv.validate().is_ok());
let short_bytes = vec![0u8; 10];
let short_cbor = Value::Map(vec![(
Value::Text("field".to_string()),
Value::Bytes(short_bytes),
)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, short_cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, short_cbor);
assert!(cv.validate().is_err());
let long_bytes = vec![0u8; 1500];
let long_cbor = Value::Map(vec![(
Value::Text("field".to_string()),
Value::Bytes(long_bytes),
)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, long_cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, long_cbor);
assert!(cv.validate().is_err());
Ok(())
}
#[test]
fn validate_bstr_size_exclusive_range() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
m = { field: bstr .size (16...1000) }
"#
);
let cddl = cddl_from_str(cddl, true)?;
let valid_bytes = vec![0u8; 17];
let valid_cbor = Value::Map(vec![(
Value::Text("field".to_string()),
Value::Bytes(valid_bytes),
)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, valid_cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, valid_cbor);
assert!(cv.validate().is_ok());
let boundary_bytes = vec![0u8; 16];
let boundary_cbor = Value::Map(vec![(
Value::Text("field".to_string()),
Value::Bytes(boundary_bytes),
)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, boundary_cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, boundary_cbor);
assert!(cv.validate().is_ok());
Ok(())
}
#[test]
fn validate_nested_cbor() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
root = {
foo: bstr .cbor bar
}
bar = {
a: text,
b: int,
c: bstr
}
"#
);
let cddl = cddl_from_str(cddl, true)?;
let inner_cbor = ciborium::cbor!({
"a" => "test",
"b" => -42,
"c" => ciborium::value::Value::Bytes(b"bytes".to_vec())
})?;
let mut inner_bytes = Vec::new();
ciborium::ser::into_writer(&inner_cbor, &mut inner_bytes)?;
let outer_cbor = Value::Map(vec![(
Value::Text("foo".to_string()),
Value::Bytes(inner_bytes),
)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, outer_cbor.clone(), None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, outer_cbor.clone());
assert!(cv.validate().is_ok());
let invalid_inner = ciborium::cbor!({
"a" => "test",
"b" => -42
})?;
let mut invalid_bytes = Vec::new();
ciborium::ser::into_writer(&invalid_inner, &mut invalid_bytes)?;
let invalid_outer = Value::Map(vec![(
Value::Text("foo".to_string()),
Value::Bytes(invalid_bytes),
)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, invalid_outer, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, invalid_outer);
assert!(cv.validate().is_err());
Ok(())
}
#[test]
fn validate_nested_cbor_in_array() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
root = [
foo: bstr .cbor bar
]
bar = {
a: text,
b: int,
c: bstr
}
"#
);
let cddl = cddl_from_str(cddl, true)?;
let inner_cbor = ciborium::cbor!({
"a" => "test",
"b" => -42,
"c" => ciborium::value::Value::Bytes(b"bytes".to_vec())
})?;
let mut inner_bytes = Vec::new();
ciborium::ser::into_writer(&inner_cbor, &mut inner_bytes)?;
let outer_cbor = Value::Array(vec![Value::Bytes(inner_bytes)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, outer_cbor.clone(), None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, outer_cbor.clone());
cv.validate()?;
assert!(
cv.validate().is_ok(),
);
let invalid_inner = ciborium::cbor!({
"a" => "test",
"b" => -42
})?;
let mut invalid_bytes = Vec::new();
ciborium::ser::into_writer(&invalid_inner, &mut invalid_bytes)?;
let invalid_outer = Value::Array(vec![Value::Bytes(invalid_bytes)]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, invalid_outer, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, invalid_outer);
assert!(cv.validate().is_err());
Ok(())
}
#[test]
fn validate_nested_arrays() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
array = [0, [* int]]
"#
);
let cbor = Value::from(ciborium::cbor!([0, [1, 2]]).unwrap());
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing)?;
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
let cddl = indoc!(
r#"
root = [0, inner]
inner = [* int]
"#
);
let cbor = Value::from(ciborium::cbor!([0, [1, 2]]).unwrap());
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing)?;
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
let cddl = indoc!(
r#"
direct = [1, [2, 3]]
"#
);
let cbor = Value::from(ciborium::cbor!([1, [2, 3]]).unwrap());
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing)?;
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
Ok(())
}
#[test]
fn validate_direct_nested_array() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
direct = [1, [2, 3]]
"#
);
let cbor = Value::from(ciborium::cbor!([1, [2, 3]]).unwrap());
let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing)?;
let mut cv = CBORValidator::new(&cddl, cbor, None);
match cv.validate() {
Ok(_) => println!("Validation successful!"),
Err(e) => {
eprintln!("Validation error: {}", e);
if let Error::Validation(errors) = &e {
for (i, err) in errors.iter().enumerate() {
eprintln!("Error {}: {} at {}", i, err.reason, err.cbor_location);
}
}
return Err(e.into());
}
}
Ok(())
}
#[test]
fn validate_recursive_structures() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl = indoc!(
r#"
Tree = {
root: Node
}
Node = [
value: text,
children: [* Node]
]
"#
);
let cddl = cddl_from_str(cddl, true)?;
let cbor = Value::from(ciborium::cbor!({
"root" => ["value", [["child1", []], ["child2", []]]]
})?);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, cbor);
let result = cv.validate();
assert!(
result.is_ok(),
"Recursive structure validation should not cause stack overflow"
);
Ok(())
}
#[test]
fn test_issue_221_empty_map_with_extra_keys_cbor() {
let cddl_str = "root = {}";
let cddl = cddl_from_str(cddl_str, true).unwrap();
let cbor_data = Value::Map(vec![(
Value::Text("x".to_string()),
Value::Text("y".to_string()),
)]);
#[cfg(feature = "additional-controls")]
let mut validator = CBORValidator::new(&cddl, cbor_data, None);
#[cfg(not(feature = "additional-controls"))]
let mut validator = CBORValidator::new(&cddl, cbor_data);
let result = validator.validate();
assert!(
result.is_err(),
"CBOR validation should fail for extra keys in empty map schema"
);
if let Err(Error::Validation(errors)) = result {
assert_eq!(errors.len(), 1);
assert!(errors[0].reason.contains("expected empty map"));
} else {
panic!("Expected validation error but got something else");
}
}
#[test]
fn test_empty_map_schema_with_empty_cbor() -> std::result::Result<(), Box<dyn std::error::Error>>
{
let cddl_str = "root = {}";
let cddl = cddl_from_str(cddl_str, true)?;
let cbor_data = Value::Map(vec![]);
#[cfg(feature = "additional-controls")]
let mut validator = CBORValidator::new(&cddl, cbor_data, None);
#[cfg(not(feature = "additional-controls"))]
let mut validator = CBORValidator::new(&cddl, cbor_data);
let result = validator.validate();
assert!(
result.is_ok(),
"CBOR validation should pass for empty map with empty map schema"
);
Ok(())
}
#[test]
fn test_issue_221_reproduce_exact_scenario_cbor(
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl_str = "root = {}";
let cddl = cddl_from_str(cddl_str, true)?;
let cbor_data = Value::Map(vec![(
Value::Text("x".to_string()),
Value::Text("y".to_string()),
)]);
#[cfg(feature = "additional-controls")]
let mut validator = CBORValidator::new(&cddl, cbor_data, None);
#[cfg(not(feature = "additional-controls"))]
let mut validator = CBORValidator::new(&cddl, cbor_data);
let result = validator.validate();
match result {
Err(Error::Validation(errors)) => {
assert!(!errors.is_empty(), "Should have validation errors");
let error_message = &errors[0].reason;
assert!(
error_message.contains("expected empty map"),
"Error message should indicate expected empty map, got: {}",
error_message
);
}
Ok(_) => panic!(
"Issue #221 bug detected: CBOR validation incorrectly passed for extra keys in empty map"
),
Err(other) => panic!("Unexpected error type: {:?}", other),
}
Ok(())
}
#[cfg(feature = "additional-controls")]
#[test]
fn validate_abnf_with_det_typename_controller(
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let cddl_str = indoc!(
r#"
start = modified-date-time
modified-date-time = text .abnf modified-dt-abnf
modified-dt-abnf = "modified-dt" .det rfc3339z
rfc3339z = '
date-fullyear = 4DIGIT
date-month = 2DIGIT
date-mday = 2DIGIT
time-hour = 2DIGIT
time-minute = 2DIGIT
time-second = 2DIGIT
time-secfrac = "." 1*DIGIT
DIGIT = %x30-39
partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]
full-date = date-fullyear "-" date-month "-" date-mday
modified-dt = full-date ["T" partial-time "Z"]
'
"#
);
let cbor = Value::Text("1985-04-12T23:20:50.52Z".to_string());
let cddl = cddl_from_str(cddl_str, true)?;
let mut cv = CBORValidator::new(&cddl, cbor, None);
cv.validate()?;
let cbor_invalid = Value::Text("not-a-date".to_string());
let mut cv_invalid = CBORValidator::new(&cddl, cbor_invalid, None);
assert!(
cv_invalid.validate().is_err(),
"Expected validation to fail for invalid datetime value"
);
Ok(())
}
#[test]
fn multi_type_choice_array_optional_no_error() {
let cddl_str = r#"
root = [? item]
item = uint / tstr
"#;
let cddl = cddl_from_str(cddl_str, true).unwrap();
let cbor = Value::Array(vec![Value::Integer(42.into())]);
let cbor1 = Value::Array(vec![Value::Text(String::from("test-string"))]);
#[cfg(feature = "additional-controls")]
let (mut cv, mut cv1) = {
let cv = CBORValidator::new(&cddl, cbor, None);
let cv1 = CBORValidator::new(&cddl, cbor1, None);
(cv, cv1)
};
#[cfg(not(feature = "additional-controls"))]
let (mut cv, mut cv1) = {
let cv = CBORValidator::new(&cddl, cbor);
let cv1 = CBORValidator::new(&cddl, cbor1);
(cv, cv1)
};
assert!(
cv.validate().is_ok(),
"Optional multi-type-choice should pass for a valid element"
);
assert!(
cv1.validate().is_ok(),
"Optional multi-type-choice should pass for a valid element"
);
}
#[test]
fn multi_type_choice_array_zero_or_more_no_error() {
let cddl_str = r#"
root = [* item]
item = uint / tstr
"#;
let cddl = cddl_from_str(cddl_str, true).unwrap();
let cbor = Value::Array(vec![
Value::Integer(1.into()),
Value::Text("hello".to_string()),
]);
let cbor_empty = Value::Array(vec![]);
#[cfg(feature = "additional-controls")]
let (mut cv, mut cv_empty) = {
let cv = CBORValidator::new(&cddl, cbor, None);
let cv_empty = CBORValidator::new(&cddl, cbor_empty, None);
(cv, cv_empty)
};
#[cfg(not(feature = "additional-controls"))]
let (mut cv, mut cv_empty) = {
let cv = CBORValidator::new(&cddl, cbor);
let cv_empty = CBORValidator::new(&cddl, cbor_empty);
(cv, cv_empty)
};
assert!(
cv.validate().is_ok(),
"Zero-or-more multi-type-choice should pass for valid elements"
);
assert!(
cv_empty.validate().is_ok(),
"Zero-or-more multi-type-choice should pass for valid elements"
);
}
#[test]
fn multi_type_choice_array_one_or_more_fails_empty() {
let cddl_str = r#"
root = [+ item]
item = uint / tstr
"#;
let cddl = cddl_from_str(cddl_str, true).unwrap();
let cbor = Value::Array(vec![]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, cbor);
assert!(
cv.validate().is_err(),
"One-or-more with empty array should fail validation"
);
}
#[test]
fn multi_type_choice_array_one_or_more_single_type_passes() {
let cddl_str = r#"
root = [+ uint]
"#;
let cddl = cddl_from_str(cddl_str, true).unwrap();
let cbor = Value::Array(vec![Value::Integer(1.into()), Value::Integer(2.into())]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, cbor);
assert!(
cv.validate().is_ok(),
"One-or-more with matching uint elements should pass"
);
}
#[test]
fn multi_type_choice_array_wrong_type_single_fails() {
let cddl_str = r#"
root = [+ uint]
"#;
let cddl = cddl_from_str(cddl_str, true).unwrap();
let cbor = Value::Array(vec![Value::Text("not a uint".to_string())]);
#[cfg(feature = "additional-controls")]
let mut cv = CBORValidator::new(&cddl, cbor, None);
#[cfg(not(feature = "additional-controls"))]
let mut cv = CBORValidator::new(&cddl, cbor);
assert!(
cv.validate().is_err(),
"Array with non-matching type should fail validation"
);
}
}