use crate::{
forwarding_state::ForwardingState,
types::{NetworkError, Prefix, RouterId},
};
use itertools::iproduct;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, error::Error};
use thiserror::Error;
pub trait Policy<P: Prefix> {
type Err: Error;
fn check(&self, fw_state: &mut ForwardingState<P>) -> Result<(), Self::Err>;
fn router(&self) -> Option<RouterId>;
fn prefix(&self) -> Option<P>;
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(bound(deserialize = "P: for<'a> serde::Deserialize<'a>"))]
pub enum FwPolicy<P: Prefix> {
Reachable(RouterId, P),
NotReachable(RouterId, P),
PathCondition(RouterId, P, PathCondition),
LoopFree(RouterId, P),
LoadBalancing(RouterId, P, usize),
LoadBalancingVertexDisjoint(RouterId, P, usize),
LoadBalancingEdgeDisjoint(RouterId, P, usize),
}
impl<P: Prefix> Policy<P> for FwPolicy<P> {
type Err = PolicyError<P>;
fn check(&self, fw_state: &mut ForwardingState<P>) -> Result<(), Self::Err> {
match self {
Self::Reachable(r, p) => match fw_state.get_paths(*r, *p) {
Ok(_) => Ok(()),
Err(NetworkError::ForwardingLoop(path)) => Err(PolicyError::ForwardingLoop {
path: prepare_loop_path(path),
prefix: *p,
}),
Err(NetworkError::ForwardingBlackHole(path)) => Err(PolicyError::BlackHole {
router: *path.last().unwrap(),
prefix: *p,
}),
Err(e) => panic!("Unrecoverable error detected: {e}"),
},
Self::NotReachable(r, p) => match fw_state.get_paths(*r, *p) {
Err(NetworkError::ForwardingBlackHole(_)) => Ok(()),
Err(NetworkError::ForwardingLoop(_)) => Ok(()),
Err(e) => panic!("Unrecoverable error detected: {e}"),
Ok(paths) => Err(PolicyError::UnallowedPathExists {
router: *r,
prefix: *p,
paths,
}),
},
Self::PathCondition(r, p, c) => match fw_state.get_paths(*r, *p) {
Ok(paths) => paths.iter().try_for_each(|path| c.check(path, *p)),
_ => Ok(()),
},
Self::LoopFree(r, p) => match fw_state.get_paths(*r, *p) {
Err(NetworkError::ForwardingLoop(path)) => Err(PolicyError::ForwardingLoop {
path: prepare_loop_path(path),
prefix: *p,
}),
_ => Ok(()),
},
Self::LoadBalancing(r, p, k) => match fw_state.get_paths(*r, *p) {
Ok(paths) if paths.len() >= *k => Ok(()),
_ => Err(PolicyError::InsufficientPathsExist {
router: *r,
prefix: *p,
k: *k,
}),
},
Self::LoadBalancingVertexDisjoint(_, _, _)
| Self::LoadBalancingEdgeDisjoint(_, _, _) => unimplemented!(),
}
}
fn router(&self) -> Option<RouterId> {
Some(match self {
FwPolicy::Reachable(r, _) => *r,
FwPolicy::NotReachable(r, _) => *r,
FwPolicy::PathCondition(r, _, _) => *r,
FwPolicy::LoopFree(r, _) => *r,
FwPolicy::LoadBalancing(r, _, _) => *r,
FwPolicy::LoadBalancingVertexDisjoint(r, _, _) => *r,
FwPolicy::LoadBalancingEdgeDisjoint(r, _, _) => *r,
})
}
fn prefix(&self) -> Option<P> {
Some(match self {
FwPolicy::Reachable(_, p) => *p,
FwPolicy::NotReachable(_, p) => *p,
FwPolicy::PathCondition(_, p, _) => *p,
FwPolicy::LoopFree(_, p) => *p,
FwPolicy::LoadBalancing(_, p, _) => *p,
FwPolicy::LoadBalancingVertexDisjoint(_, p, _) => *p,
FwPolicy::LoadBalancingEdgeDisjoint(_, p, _) => *p,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PathCondition {
Node(RouterId),
Edge(RouterId, RouterId),
And(Vec<PathCondition>),
Or(Vec<PathCondition>),
Not(Box<PathCondition>),
Positional(Vec<Waypoint>),
}
impl PathCondition {
pub fn check<P: Prefix>(&self, path: &[RouterId], prefix: P) -> Result<(), PolicyError<P>> {
if match self {
Self::And(v) => v.iter().all(|c| c.check(path, prefix).is_ok()),
Self::Or(v) => v.iter().any(|c| c.check(path, prefix).is_ok()),
Self::Not(c) => c.check(path, prefix).is_err(),
Self::Node(v) => path.iter().any(|x| x == v),
Self::Edge(x, y) => {
let mut iter_path = path.iter().peekable();
let mut found = false;
while let (Some(a), Some(b)) = (iter_path.next(), iter_path.peek()) {
if x == a && y == *b {
found = true;
}
}
found
}
Self::Positional(v) => {
let mut p = path.iter();
let mut v = v.iter();
'alg: loop {
match v.next() {
Some(Waypoint::Any) => {
if p.next().is_none() {
break 'alg false;
}
}
Some(Waypoint::Fix(n)) => {
if p.next() != Some(n) {
break 'alg false;
}
}
Some(Waypoint::Star) => {
'star: loop {
match v.next() {
Some(Waypoint::Any) => {
if p.next().is_none() {
break 'alg false;
}
}
Some(Waypoint::Star) => {
}
Some(Waypoint::Fix(n)) => {
for u in &mut p {
if u == n {
break 'star;
}
}
break 'alg false;
}
None => {
break 'alg true;
}
}
}
}
None => {
break 'alg p.next().is_none();
}
}
}
}
} {
Ok(())
} else {
Err(PolicyError::PathCondition {
path: path.to_owned(),
condition: self.clone(),
prefix,
})
}
}
fn into_cnf_recursive(self) -> Vec<(Vec<Self>, Vec<Self>)> {
match self {
Self::Node(a) => vec![(vec![Self::Node(a)], vec![])],
Self::Edge(a, b) => vec![(vec![Self::Edge(a, b)], vec![])],
Self::Positional(v) => vec![(vec![Self::Positional(v)], vec![])],
Self::And(v) => {
v.into_iter()
.flat_map(|e| e.into_cnf_recursive().into_iter())
.collect()
}
Self::Or(v) => {
let mut v_iter = v.into_iter();
let mut x = v_iter
.next()
.map(|e| e.into_cnf_recursive())
.unwrap_or_else(|| vec![(vec![], vec![])]);
for e in v_iter {
let e = e.into_cnf_recursive();
x = iproduct!(x.into_iter(), e.into_iter())
.map(|((mut xt, mut xf), (mut et, mut ef))| {
xt.append(&mut et);
xf.append(&mut ef);
(xt, xf)
})
.collect()
}
x
}
Self::Not(e) => match *e {
Self::Node(a) => vec![(vec![], vec![Self::Node(a)])],
Self::Edge(a, b) => vec![(vec![], vec![Self::Edge(a, b)])],
Self::Positional(v) => vec![(vec![], vec![Self::Positional(v)])],
Self::Not(e) => e.into_cnf_recursive(),
Self::And(v) => Self::Or(v.into_iter().map(|e| Self::Not(Box::new(e))).collect())
.into_cnf_recursive(),
Self::Or(v) => Self::And(v.into_iter().map(|e| Self::Not(Box::new(e))).collect())
.into_cnf_recursive(),
},
}
}
}
impl From<PathCondition> for PathConditionCNF {
fn from(val: PathCondition) -> Self {
PathConditionCNF::new(val.into_cnf_recursive())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)]
pub enum Waypoint {
Any,
Star,
Fix(RouterId),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PathConditionCNF {
pub e: Vec<(Vec<PathCondition>, Vec<PathCondition>)>,
pub(super) is_cnf: bool,
}
impl PathConditionCNF {
pub fn new(e: Vec<(Vec<PathCondition>, Vec<PathCondition>)>) -> Self {
let is_cnf = e
.iter()
.flat_map(|(t, f)| t.iter().chain(f.iter()))
.all(|c| matches!(c, PathCondition::Node(_) | PathCondition::Edge(_, _)));
Self { e, is_cnf }
}
pub fn is_cnf(&self) -> bool {
self.is_cnf
}
pub fn check<P: Prefix>(&self, path: &[RouterId], prefix: P) -> Result<(), PolicyError<P>> {
fn cnf_or<P: Prefix>(
vt: &[PathCondition],
vf: &[PathCondition],
path: &[RouterId],
prefix: P,
) -> bool {
vt.iter().any(|c| c.check(path, prefix).is_ok())
|| vf.iter().any(|c| c.check(path, prefix).is_err())
}
if self.e.iter().all(|(vt, vf)| cnf_or(vt, vf, path, prefix)) {
Ok(())
} else {
Err(PolicyError::PathCondition {
path: path.to_owned(),
condition: self.clone().into(),
prefix,
})
}
}
}
impl From<PathConditionCNF> for PathCondition {
fn from(val: PathConditionCNF) -> Self {
PathCondition::And(
val.e
.into_iter()
.map(|(vt, vf)| {
PathCondition::Or(
vf.into_iter()
.map(|e| PathCondition::Not(Box::new(e)))
.chain(vt)
.collect(),
)
})
.collect(),
)
}
}
#[derive(Debug, Error, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
#[serde(bound(deserialize = "P: for<'a> serde::Deserialize<'a>"))]
pub enum PolicyError<P: Prefix> {
#[error("Black Hole at router {router:?} for {prefix}")]
BlackHole {
router: RouterId,
prefix: P,
},
#[error("Forwarding Loop {path:?} for {prefix}")]
ForwardingLoop {
path: Vec<RouterId>,
prefix: P,
},
#[error("Invalid Path for {prefix}: path: {path:?}")]
PathCondition {
path: Vec<RouterId>,
condition: PathCondition,
prefix: P,
},
#[error("Router {router:?} should not be able to reach {prefix} but the following path(s) is valid: {paths:?}")]
UnallowedPathExists {
router: RouterId,
prefix: P,
paths: Vec<Vec<RouterId>>,
},
#[error("Router {router:?} should be able to reach {prefix} by at least {k} paths")]
InsufficientPathsExist {
router: RouterId,
prefix: P,
k: usize,
},
#[error("Network did not converge")]
NoConvergence,
}
fn prepare_loop_path(path: Vec<RouterId>) -> Vec<RouterId> {
let len = path.len();
let loop_router = path[len - 1];
let mut first_loop_router: Option<usize> = None;
for (i, r) in path.iter().enumerate().take(len - 1) {
if *r == loop_router {
first_loop_router = Some(i);
break;
}
}
let first_loop_router =
first_loop_router.unwrap_or_else(|| panic!("Loop-Free path given: {path:?}"));
let mut loop_unordered: VecDeque<RouterId> =
path.into_iter().skip(first_loop_router + 1).collect();
let lowest_pos = loop_unordered
.iter()
.enumerate()
.min_by(|a, b| a.1.cmp(b.1))
.map(|(i, _)| i)
.expect("Loop is empty");
loop_unordered.rotate_left(lowest_pos);
loop_unordered.into_iter().collect()
}
#[cfg(test)]
mod test {
use super::*;
use rand::prelude::*;
use super::PathCondition::*;
use super::Waypoint::*;
use crate::types::SimplePrefix as Prefix;
#[test]
fn path_condition_node() {
let c = Node(0.into());
assert!(c
.check(&[1.into(), 0.into(), 2.into()], Prefix::from(0))
.is_ok());
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[2.into(), 1.into()], Prefix::from(0)).is_err());
assert!(c.check(&[], Prefix::from(0)).is_err());
}
#[test]
fn path_condition_edge() {
let c = Edge(0.into(), 1.into());
assert!(c
.check(&[2.into(), 0.into(), 1.into(), 3.into()], Prefix::from(0))
.is_ok());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[1.into(), 0.into()], Prefix::from(0)).is_err());
assert!(c
.check(&[0.into(), 2.into(), 1.into()], Prefix::from(0))
.is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_err());
assert!(c.check(&[1.into()], Prefix::from(0)).is_err());
}
#[test]
fn path_condition_not() {
let c = Not(Box::new(Node(0.into())));
assert!(c
.check(&[1.into(), 0.into(), 2.into()], Prefix::from(0))
.is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_err());
assert!(c.check(&[2.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[], Prefix::from(0)).is_ok());
}
#[test]
fn path_condition_or() {
let c = Or(vec![Node(0.into()), Node(1.into())]);
assert!(c
.check(&[0.into(), 2.into(), 1.into()], Prefix::from(0))
.is_ok());
assert!(c.check(&[2.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into(), 2.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[3.into(), 2.into()], Prefix::from(0)).is_err());
assert!(c.check(&[], Prefix::from(0)).is_err());
let c = Or(vec![]);
assert!(c
.check(&[0.into(), 2.into(), 1.into()], Prefix::from(0))
.is_err());
assert!(c.check(&[], Prefix::from(0)).is_err());
}
#[test]
fn path_condition_and() {
let c = And(vec![Node(0.into()), Node(1.into())]);
assert!(c
.check(&[0.into(), 2.into(), 1.into()], Prefix::from(0))
.is_ok());
assert!(c.check(&[2.into(), 1.into()], Prefix::from(0)).is_err());
assert!(c.check(&[0.into(), 2.into()], Prefix::from(0)).is_err());
assert!(c.check(&[3.into(), 2.into()], Prefix::from(0)).is_err());
assert!(c.check(&[], Prefix::from(0)).is_err());
let c = And(vec![]);
assert!(c
.check(&[0.into(), 2.into(), 1.into()], Prefix::from(0))
.is_ok());
assert!(c.check(&[], Prefix::from(0)).is_ok());
}
fn test_cnf_equivalence(c: PathCondition, n: usize, num_devices: usize) {
let c_cnf: PathConditionCNF = c.clone().into();
let c_rev: PathCondition = c_cnf.clone().into();
let mut rng = rand::thread_rng();
for _ in 0..n {
let mut path: Vec<RouterId> = (0..num_devices).map(|x| (x as u32).into()).collect();
path.shuffle(&mut rng);
let path: Vec<RouterId> = path.into_iter().take(rng.next_u32() as usize).collect();
assert_eq!(
c.check(&path, Prefix::from(0)).is_ok(),
c_cnf.check(&path, Prefix::from(0)).is_ok()
);
assert_eq!(
c.check(&path, Prefix::from(0)).is_ok(),
c_rev.check(&path, Prefix::from(0)).is_ok()
);
}
}
#[test]
fn path_condition_to_cnf_simple() {
let r0: RouterId = 0.into();
let r1: RouterId = 1.into();
test_cnf_equivalence(Node(r0), 1000, 10);
test_cnf_equivalence(Edge(r0, r1), 1000, 10);
test_cnf_equivalence(Not(Box::new(Node(r0))), 1000, 10);
test_cnf_equivalence(And(vec![Node(r0), Node(r1)]), 1000, 10);
test_cnf_equivalence(Or(vec![Node(r0), Node(r1)]), 1000, 10);
}
#[test]
fn path_condition_to_cnf_complex() {
let r0: RouterId = 0.into();
let r1: RouterId = 1.into();
let r2: RouterId = 2.into();
test_cnf_equivalence(
And(vec![Not(Box::new(Node(r0))), Not(Box::new(Node(r1)))]),
1000,
10,
);
test_cnf_equivalence(
Or(vec![Not(Box::new(Node(r0))), Not(Box::new(Node(r1)))]),
1000,
10,
);
test_cnf_equivalence(
Or(vec![
And(vec![Node(r0), Node(r1)]),
And(vec![Edge(r0, r1), Node(r2)]),
Not(Box::new(Node(r2))),
]),
1000,
10,
);
test_cnf_equivalence(
Or(vec![
And(vec![Node(r0), Node(r1)]),
And(vec![Not(Box::new(Edge(r0, r1))), Node(r2)]),
Not(Box::new(Node(r2))),
]),
1000,
10,
);
test_cnf_equivalence(
Or(vec![
And(vec![
Node(r0),
Or(vec![Node(r2), Not(Box::new(Edge(r0, r1)))]),
]),
And(vec![Not(Box::new(Edge(r0, r1))), Node(r2)]),
Not(Box::new(Node(r2))),
]),
1000,
10,
);
test_cnf_equivalence(
Not(Box::new(Or(vec![
And(vec![
Node(r0),
Or(vec![Node(r2), Not(Box::new(Edge(r0, r1)))]),
]),
And(vec![Not(Box::new(Edge(r0, r1))), Node(r2)]),
Not(Box::new(Node(r2))),
]))),
1000,
10,
);
}
#[test]
fn path_positional_single_any() {
let c = Positional(vec![Any]);
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[1.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_err());
}
#[test]
fn path_positional_single_star() {
let c = Positional(vec![Star]);
assert!(c.check(&[], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
}
#[test]
fn path_positional_single_fix() {
let c = Positional(vec![Fix(0.into())]);
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[1.into()], Prefix::from(0)).is_err());
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_err());
}
#[test]
fn path_positional_star_any() {
let c = Positional(vec![Star, Any]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
let c = Positional(vec![Any, Star]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
}
#[test]
fn path_positional_star_star() {
let c = Positional(vec![Star, Star]);
assert!(c.check(&[], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
}
#[test]
fn path_positional_any_any() {
let c = Positional(vec![Any, Any]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_err());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
}
#[test]
fn path_positional_star_fix() {
let c = Positional(vec![Star, Fix(0.into())]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[1.into(), 0.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[2.into(), 1.into(), 0.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(&[2.into(), 1.into(), 0.into(), 3.into()], Prefix::from(0))
.is_err());
assert!(c
.check(&[2.into(), 1.into(), 3.into()], Prefix::from(0))
.is_err());
}
#[test]
fn path_positional_fix_star() {
let c = Positional(vec![Fix(0.into()), Star]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(&[3.into(), 0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
assert!(c
.check(&[3.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
}
#[test]
fn path_positional_star_fix_star() {
let c = Positional(vec![Star, Fix(0.into()), Star]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_ok());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(&[3.into(), 0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(
&[3.into(), 4.into(), 0.into(), 1.into(), 2.into()],
Prefix::from(0)
)
.is_ok());
assert!(c
.check(&[3.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
}
#[test]
fn path_positional_star_fix_fix_star() {
let c = Positional(vec![Star, Fix(0.into()), Fix(1.into()), Star]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_err());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(&[3.into(), 0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(
&[3.into(), 4.into(), 0.into(), 1.into(), 2.into()],
Prefix::from(0)
)
.is_ok());
assert!(c
.check(&[3.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
assert!(c
.check(&[3.into(), 0.into(), 2.into(), 1.into()], Prefix::from(0))
.is_err());
assert!(c
.check(&[3.into(), 2.into(), 1.into()], Prefix::from(0))
.is_err());
}
#[test]
fn path_positional_star_fix_any_fix_star() {
let c = Positional(vec![Star, Fix(0.into()), Any, Fix(1.into()), Star]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_err());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_err());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
assert!(c
.check(&[3.into(), 0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
assert!(c
.check(
&[3.into(), 4.into(), 0.into(), 1.into(), 2.into()],
Prefix::from(0)
)
.is_err());
assert!(c
.check(&[3.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
assert!(c
.check(&[3.into(), 0.into(), 2.into(), 1.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(
&[3.into(), 0.into(), 2.into(), 1.into(), 3.into()],
Prefix::from(0)
)
.is_ok());
assert!(c
.check(
&[3.into(), 0.into(), 2.into(), 3.into(), 1.into()],
Prefix::from(0)
)
.is_err());
assert!(c
.check(&[3.into(), 2.into(), 1.into()], Prefix::from(0))
.is_err());
}
#[test]
fn path_positional_star_fix_star_fix_star() {
let c = Positional(vec![Star, Fix(0.into()), Star, Fix(1.into()), Star]);
assert!(c.check(&[], Prefix::from(0)).is_err());
assert!(c.check(&[0.into()], Prefix::from(0)).is_err());
assert!(c.check(&[0.into(), 1.into()], Prefix::from(0)).is_ok());
assert!(c
.check(&[0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(&[3.into(), 0.into(), 1.into(), 2.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(
&[3.into(), 4.into(), 0.into(), 1.into(), 2.into()],
Prefix::from(0)
)
.is_ok());
assert!(c
.check(&[3.into(), 1.into(), 2.into()], Prefix::from(0))
.is_err());
assert!(c
.check(&[3.into(), 0.into(), 2.into(), 1.into()], Prefix::from(0))
.is_ok());
assert!(c
.check(
&[3.into(), 0.into(), 2.into(), 1.into(), 3.into()],
Prefix::from(0)
)
.is_ok());
assert!(c
.check(
&[3.into(), 0.into(), 2.into(), 3.into(), 1.into()],
Prefix::from(0)
)
.is_ok());
assert!(c
.check(&[3.into(), 2.into(), 1.into()], Prefix::from(0))
.is_err());
assert!(c
.check(&[3.into(), 2.into(), 1.into(), 0.into()], Prefix::from(0))
.is_err());
}
}