use crate::ast::{
CallStyle, Extension, ExtensionFunction, ExtensionOutputValue, ExtensionValue,
ExtensionValueWithArgs, Literal, Name, Type, Value, ValueKind,
};
use crate::entities::SchemaType;
use crate::evaluator;
use std::sync::Arc;
#[allow(clippy::expect_used)]
mod names {
use crate::ast::Name;
lazy_static::lazy_static! {
pub static ref EXTENSION_NAME : Name = Name::parse_unqualified_name("ipaddr").expect("should be a valid identifier");
pub static ref IP_FROM_STR_NAME : Name = Name::parse_unqualified_name("ip").expect("should be a valid identifier");
pub static ref IS_IPV4 : Name = Name::parse_unqualified_name("isIpv4").expect("should be a valid identifier");
pub static ref IS_IPV6 : Name = Name::parse_unqualified_name("isIpv6").expect("should be a valid identifier");
pub static ref IS_LOOPBACK : Name = Name::parse_unqualified_name("isLoopback").expect("should be a valid identifier");
pub static ref IS_MULTICAST : Name = Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier");
pub static ref IS_IN_RANGE : Name = Name::parse_unqualified_name("isInRange").expect("should be a valid identifier");
}
}
const ADVICE_MSG: &str = "maybe you forgot to apply the `ip` constructor?";
const PREFIX_MAX_LEN_V4: u8 = 32;
const PREFIX_MAX_LEN_V6: u8 = 128;
const PREFIX_STR_MAX_LEN_V4: u8 = 2;
const PREFIX_STR_MAX_LEN_V6: u8 = 3;
const IP_STR_REP_MAX_LEN: u8 = 43;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
struct IPAddr {
addr: std::net::IpAddr,
prefix: u8,
}
impl IPAddr {
fn typename() -> Name {
names::EXTENSION_NAME.clone()
}
fn from_str(str: impl AsRef<str>) -> Result<Self, String> {
str.as_ref().parse()
}
fn is_ipv4(&self) -> bool {
self.addr.is_ipv4()
}
fn is_ipv6(&self) -> bool {
self.addr.is_ipv6()
}
fn is_loopback(&self) -> bool {
self.addr.is_loopback() && self.prefix >= if self.is_ipv4() { 8 } else { PREFIX_MAX_LEN_V6 }
}
fn is_multicast(&self) -> bool {
self.addr.is_multicast() && self.prefix >= if self.is_ipv4() { 4 } else { 8 }
}
fn is_in_range(&self, other: &Self) -> bool {
match (&self.addr, &other.addr) {
(std::net::IpAddr::V4(self_v4), std::net::IpAddr::V4(other_v4)) => {
let netmask = |prefix: u8| {
u32::MAX
.checked_shl((PREFIX_MAX_LEN_V4 - prefix).into())
.unwrap_or(0)
};
let hostmask = |prefix: u8| u32::MAX.checked_shr(prefix.into()).unwrap_or(0);
let self_network = u32::from(*self_v4) & netmask(self.prefix);
let other_network = u32::from(*other_v4) & netmask(other.prefix);
let self_broadcast = u32::from(*self_v4) | hostmask(self.prefix);
let other_broadcast = u32::from(*other_v4) | hostmask(other.prefix);
other_network <= self_network && self_broadcast <= other_broadcast
}
(std::net::IpAddr::V6(self_v6), std::net::IpAddr::V6(other_v6)) => {
let netmask = |prefix: u8| {
u128::MAX
.checked_shl((PREFIX_MAX_LEN_V6 - prefix).into())
.unwrap_or(0)
};
let hostmask = |prefix: u8| u128::MAX.checked_shr(prefix.into()).unwrap_or(0);
let self_network = u128::from(*self_v6) & netmask(self.prefix);
let other_network = u128::from(*other_v6) & netmask(other.prefix);
let self_broadcast = u128::from(*self_v6) | hostmask(self.prefix);
let other_broadcast = u128::from(*other_v6) | hostmask(other.prefix);
other_network <= self_network && self_broadcast <= other_broadcast
}
(_, _) => false,
}
}
}
fn parse_prefix(s: &str, max: u8, max_len: u8) -> Result<u8, String> {
if s.len() > max_len as usize {
return Err(format!(
"error parsing prefix: string length {} is too large",
s.len()
));
}
if s.chars().any(|c| !c.is_ascii_digit()) {
return Err(format!("error parsing prefix `{s}`: encountered non-digit"));
}
if s.starts_with('0') && s != "0" {
return Err(format!("error parsing prefix `{s}`: leading zero(s)"));
}
let res: u8 = s
.parse()
.map_err(|err| format!("error parsing prefix from the string `{s}`: {err}"))?;
if res > max {
return Err(format!(
"error parsing prefix: {res} is larger than the limit {max}"
));
}
Ok(res)
}
impl std::str::FromStr for IPAddr {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.bytes().len() > IP_STR_REP_MAX_LEN as usize {
return Err(format!(
"error parsing IP address from string `{s}`: string length is too large"
));
}
str_contains_colons_and_dots(s)?;
match s.split_once('/') {
Some((addr_str, prefix_str)) => {
let addr: std::net::IpAddr = addr_str.parse().map_err(|e| {
format!("error parsing IP address from the string `{addr_str}`: {e}")
})?;
let prefix = match addr {
std::net::IpAddr::V4(_) => {
parse_prefix(prefix_str, PREFIX_MAX_LEN_V4, PREFIX_STR_MAX_LEN_V4)?
}
std::net::IpAddr::V6(_) => {
parse_prefix(prefix_str, PREFIX_MAX_LEN_V6, PREFIX_STR_MAX_LEN_V6)?
}
};
Ok(Self { addr, prefix })
}
None => match std::net::IpAddr::from_str(s) {
Ok(singleaddr) => Ok(Self {
addr: singleaddr,
prefix: if singleaddr.is_ipv4() {
PREFIX_MAX_LEN_V4
} else {
PREFIX_MAX_LEN_V6
},
}),
Err(_) => Err(format!("invalid IP address: {s}")),
},
}
}
}
impl std::fmt::Display for IPAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", self.addr, self.prefix)
}
}
impl ExtensionValue for IPAddr {
fn typename(&self) -> Name {
Self::typename()
}
}
fn extension_err(msg: impl Into<String>) -> evaluator::EvaluationError {
evaluator::EvaluationError::failed_extension_function_application(
names::EXTENSION_NAME.clone(),
msg.into(),
None, )
}
fn contains_at_least_two(s: &str, c: char) -> bool {
let idx = s.find(c);
match idx {
Some(i) => {
#[allow(clippy::indexing_slicing)]
#[allow(clippy::unwrap_used)]
let idx = s.get(i + c.len_utf8()..).unwrap().find(c);
idx.is_some()
}
None => false,
}
}
#[cfg(kani)]
mod proof {
#[kani::proof]
#[kani::unwind(7)]
fn contains_at_least_two_correct() {
let buf: [u8; 6] = kani::any();
let len: usize = kani::any();
kani::assume(len <= 6);
let slice = &buf[0..len];
if let Ok(s) = std::str::from_utf8(slice) {
let pat = kani::any();
let _ = super::contains_at_least_two(s, pat);
}
}
}
fn str_contains_colons_and_dots(s: &str) -> Result<(), String> {
if contains_at_least_two(s, ':') && contains_at_least_two(s, '.') {
return Err(format!(
"error parsing IP address from string: We do not accept IPv4 embedded in IPv6 (e.g., ::ffff:127.0.0.1). Found: `{}`", &s.to_string()));
}
Ok(())
}
fn ip_from_str(arg: Value) -> evaluator::Result<ExtensionOutputValue> {
let str = arg.get_as_string()?;
let function_name = names::IP_FROM_STR_NAME.clone();
let arg_source_loc = arg.source_loc().cloned();
let ipaddr = ExtensionValueWithArgs::new(
Arc::new(IPAddr::from_str(str.as_str()).map_err(extension_err)?),
function_name,
vec![arg.into()],
);
Ok(Value {
value: ValueKind::ExtensionValue(Arc::new(ipaddr)),
loc: arg_source_loc, }
.into())
}
fn as_ipaddr(v: &Value) -> Result<&IPAddr, evaluator::EvaluationError> {
match &v.value {
ValueKind::ExtensionValue(ev) if ev.typename() == IPAddr::typename() => {
#[allow(clippy::expect_used)]
let ipaddr = ev
.value()
.as_any()
.downcast_ref::<IPAddr>()
.expect("already typechecked, so this downcast should succeed");
Ok(ipaddr)
}
ValueKind::Lit(Literal::String(_)) => {
Err(evaluator::EvaluationError::type_error_with_advice_single(
Type::Extension {
name: IPAddr::typename(),
},
v,
ADVICE_MSG.into(),
))
}
_ => Err(evaluator::EvaluationError::type_error_single(
Type::Extension {
name: IPAddr::typename(),
},
v,
)),
}
}
fn is_ipv4(arg: Value) -> evaluator::Result<ExtensionOutputValue> {
let ipaddr = as_ipaddr(&arg)?;
Ok(ipaddr.is_ipv4().into())
}
fn is_ipv6(arg: Value) -> evaluator::Result<ExtensionOutputValue> {
let ipaddr = as_ipaddr(&arg)?;
Ok(ipaddr.is_ipv6().into())
}
fn is_loopback(arg: Value) -> evaluator::Result<ExtensionOutputValue> {
let ipaddr = as_ipaddr(&arg)?;
Ok(ipaddr.is_loopback().into())
}
fn is_multicast(arg: Value) -> evaluator::Result<ExtensionOutputValue> {
let ipaddr = as_ipaddr(&arg)?;
Ok(ipaddr.is_multicast().into())
}
fn is_in_range(child: Value, parent: Value) -> evaluator::Result<ExtensionOutputValue> {
let child_ip = as_ipaddr(&child)?;
let parent_ip = as_ipaddr(&parent)?;
Ok(child_ip.is_in_range(parent_ip).into())
}
pub fn extension() -> Extension {
let ipaddr_type = SchemaType::Extension {
name: IPAddr::typename(),
};
Extension::new(
names::EXTENSION_NAME.clone(),
vec![
ExtensionFunction::unary(
names::IP_FROM_STR_NAME.clone(),
CallStyle::FunctionStyle,
Box::new(ip_from_str),
ipaddr_type.clone(),
SchemaType::String,
),
ExtensionFunction::unary(
names::IS_IPV4.clone(),
CallStyle::MethodStyle,
Box::new(is_ipv4),
SchemaType::Bool,
ipaddr_type.clone(),
),
ExtensionFunction::unary(
names::IS_IPV6.clone(),
CallStyle::MethodStyle,
Box::new(is_ipv6),
SchemaType::Bool,
ipaddr_type.clone(),
),
ExtensionFunction::unary(
names::IS_LOOPBACK.clone(),
CallStyle::MethodStyle,
Box::new(is_loopback),
SchemaType::Bool,
ipaddr_type.clone(),
),
ExtensionFunction::unary(
names::IS_MULTICAST.clone(),
CallStyle::MethodStyle,
Box::new(is_multicast),
SchemaType::Bool,
ipaddr_type.clone(),
),
ExtensionFunction::binary(
names::IS_IN_RANGE.clone(),
CallStyle::MethodStyle,
Box::new(is_in_range),
SchemaType::Bool,
(ipaddr_type.clone(), ipaddr_type),
),
],
)
}
#[allow(clippy::panic)]
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Expr, Type, Value};
use crate::evaluator::test::{basic_entities, basic_request};
use crate::evaluator::{evaluation_errors, EvaluationError, Evaluator};
use crate::extensions::Extensions;
use crate::parser::parse_expr;
use cool_asserts::assert_matches;
use nonempty::nonempty;
#[track_caller] fn assert_ipaddr_err<T: std::fmt::Debug>(res: evaluator::Result<T>) {
assert_matches!(res, Err(EvaluationError::FailedExtensionFunctionExecution(evaluation_errors::ExtensionFunctionExecutionError { extension_name, .. })) => {
assert_eq!(
extension_name,
Name::parse_unqualified_name("ipaddr")
.expect("should be a valid identifier")
);
});
}
fn ip(arg: impl Into<Literal>) -> Expr {
Expr::call_extension_fn(
Name::parse_unqualified_name("ip").expect("should be a valid identifier"),
vec![Expr::val(arg)],
)
}
#[test]
fn constructors() {
let ext = extension();
assert!(ext
.get_func(&Name::parse_unqualified_name("ip").expect("should be a valid identifier"))
.expect("function should exist")
.is_constructor());
assert!(!ext
.get_func(
&Name::parse_unqualified_name("isIpv4").expect("should be a valid identifier")
)
.expect("function should exist")
.is_constructor());
assert!(!ext
.get_func(
&Name::parse_unqualified_name("isIpv6").expect("should be a valid identifier")
)
.expect("function should exist")
.is_constructor());
assert!(!ext
.get_func(
&Name::parse_unqualified_name("isLoopback").expect("should be a valid identifier")
)
.expect("function should exist")
.is_constructor());
assert!(!ext
.get_func(
&Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier")
)
.expect("function should exist")
.is_constructor());
assert!(!ext
.get_func(
&Name::parse_unqualified_name("isInRange").expect("should be a valid identifier")
)
.expect("function should exist")
.is_constructor(),);
}
#[test]
fn ip_creation() {
let ext_array = [extension()];
let exts = Extensions::specific_extensions(&ext_array).unwrap();
let request = basic_request();
let entities = basic_entities();
let eval = Evaluator::new(request, &entities, &exts);
assert_eq!(
eval.interpret_inline_policy(
&parse_expr(r#""pancakes" like "pan*""#).expect("parsing error")
),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv4").expect("should be a valid identifier"),
vec![ip("127.0.0.1")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv6").expect("should be a valid identifier"),
vec![ip("127.0.0.1")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv4").expect("should be a valid identifier"),
vec![ip("::1")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv6").expect("should be a valid identifier"),
vec![ip("::1")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv4").expect("should be a valid identifier"),
vec![ip("::ffff:ff00:1")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv6").expect("should be a valid identifier"),
vec![ip("::ffff:ff00:1")]
)),
Ok(Value::from(true))
);
assert_ipaddr_err(eval.interpret_inline_policy(&ip("380.0.0.1")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("?")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("ab.ab.ab.ab")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("foo::1")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("::ffff:127.0.0.1")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("::127.0.0.1")));
assert_matches!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("ip").expect("should be a valid identifier"),
vec![Expr::set(vec!(
Expr::val(127),
Expr::val(0),
Expr::val(0),
Expr::val(1)
))]
)),
Err(EvaluationError::TypeError(evaluation_errors::TypeError { expected, actual, advice, .. })) => {
assert_eq!(expected, nonempty![Type::String]);
assert_eq!(actual, Type::Set);
assert_eq!(advice, None);
}
);
assert_matches!(
eval.interpret_inline_policy(&Expr::less(ip("127.0.0.1"), ip("10.0.0.10"))),
Err(EvaluationError::TypeError(evaluation_errors::TypeError { expected, actual, advice, .. })) => {
assert_eq!(expected, nonempty![Type::Long]);
assert_eq!(actual, Type::Extension {
name: Name::parse_unqualified_name("ipaddr")
.expect("should be a valid identifier")
});
assert_eq!(advice, None);
}
);
assert_matches!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv4").expect("should be a valid identifier"),
vec![Expr::val("127.0.0.1")]
)),
Err(EvaluationError::TypeError(evaluation_errors::TypeError { expected, actual, advice, .. })) => {
assert_eq!(expected, nonempty![Type::Extension {
name: Name::parse_unqualified_name("ipaddr")
.expect("should be a valid identifier")
}]);
assert_eq!(actual, Type::String);
assert_eq!(advice, Some(ADVICE_MSG.into()));
}
);
assert_eq!(
eval.interpret_inline_policy(&ip("127.0.0.1"))
.unwrap()
.to_string(),
"127.0.0.1/32"
);
assert_eq!(
eval.interpret_inline_policy(&ip("ffee::11"))
.unwrap()
.to_string(),
"ffee::11/128"
);
}
#[test]
fn ip_range_creation() {
let ext_array = [extension()];
let exts = Extensions::specific_extensions(&ext_array).unwrap();
let request = basic_request();
let entities = basic_entities();
let eval = Evaluator::new(request, &entities, &exts);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv4").expect("should be a valid identifier"),
vec![ip("127.0.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv6").expect("should be a valid identifier"),
vec![ip("127.0.0.1/24")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv4").expect("should be a valid identifier"),
vec![ip("ffee::/64")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isIpv6").expect("should be a valid identifier"),
vec![ip("ffee::/64")]
)),
Ok(Value::from(true))
);
assert_matches!(eval.interpret_inline_policy(&ip("127.0.0.1/0")), Ok(_));
assert_matches!(eval.interpret_inline_policy(&ip("127.0.0.1/32")), Ok(_));
assert_matches!(eval.interpret_inline_policy(&ip("ffee::/0")), Ok(_));
assert_matches!(eval.interpret_inline_policy(&ip("ffee::/128")), Ok(_));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("127.0.0.1/8/24")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("fee::/64::1")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("172.0.0.1/64")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("ffee::/132")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("ffee::/+1")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("ffee::/01")));
assert_ipaddr_err(eval.interpret_inline_policy(&ip("ffee::/1234")));
assert_eq!(
eval.interpret_inline_policy(&ip("127.0.0.1/0"))
.unwrap()
.to_string(),
"127.0.0.1/0"
);
assert_eq!(
eval.interpret_inline_policy(&ip("127.0.0.1/8"))
.unwrap()
.to_string(),
"127.0.0.1/8"
);
assert_eq!(
eval.interpret_inline_policy(&ip("127.0.0.1/32"))
.unwrap()
.to_string(),
"127.0.0.1/32"
);
assert_eq!(
eval.interpret_inline_policy(&ip("ffee::/64"))
.unwrap()
.to_string(),
"ffee::/64"
);
}
#[test]
fn ip_equality() {
let ext_array = [extension()];
let exts = Extensions::specific_extensions(&ext_array).unwrap();
let request = basic_request();
let entities = basic_entities();
let eval = Evaluator::new(request, &entities, &exts);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("127.0.0.1"), ip("127.0.0.1"))),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("192.168.0.1"), ip("8.8.8.8"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("127.0.0.1"), ip("::1"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("127.0.0.1"), Expr::val("127.0.0.1"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("::1"), Expr::val(1))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("127.0.0.1"), ip("192.168.0.1/24"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("192.168.0.1/24"), ip("8.8.8.8/8"))),
Ok(Value::from(false))
);
}
#[test]
fn is_loopback_and_is_multicast() {
let ext_array = [extension()];
let exts = Extensions::specific_extensions(&ext_array).unwrap();
let request = basic_request();
let entities = basic_entities();
let eval = Evaluator::new(request, &entities, &exts);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isLoopback").expect("should be a valid identifier"),
vec![ip("127.0.0.2")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isLoopback").expect("should be a valid identifier"),
vec![ip("::1")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isLoopback").expect("should be a valid identifier"),
vec![ip("::2")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isLoopback").expect("should be a valid identifier"),
vec![ip("127.255.200.200/0")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier"),
vec![ip("228.228.228.0")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier"),
vec![ip("224.0.0.0/3")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier"),
vec![ip("224.0.0.0/5")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier"),
vec![ip("ff00::/7")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier"),
vec![ip("ff00::/9")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier"),
vec![ip("127.0.0.1")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier"),
vec![ip("127.0.0.1/1")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isMulticast").expect("should be a valid identifier"),
vec![ip("ff00::2")]
)),
Ok(Value::from(true))
);
}
#[test]
fn ip_is_in_range() {
let ext_array = [extension()];
let exts = Extensions::specific_extensions(&ext_array).unwrap();
let request = basic_request();
let entities = basic_entities();
let eval = Evaluator::new(request, &entities, &exts);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("192.168.0.1/24"), ip("192.168.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("192.168.0.1"), ip("192.168.0.1/28")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("192.168.0.10"), ip("192.168.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("192.168.0.10"), ip("192.168.0.1/28")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("192.168.0.75"), ip("192.168.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("192.168.0.75"), ip("192.168.0.1/28")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("192.168.0.1"), ip("192.168.0.1")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("1:2:3:4::"), ip("1:2:3:4::/48")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("1:2:3:4::"), ip("1:2:3:4::/52")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("1:2:3:6::"), ip("1:2:3:4::/48")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("1:2:3:6::"), ip("1:2:3:4::/52")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("1:2:3:ffff::"), ip("1:2:3:4::/48")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("1:2:3:ffff::"), ip("1:2:3:4::/52")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("1:2:3:4::"), ip("1:2:3:4::")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("192.168.0.1"), ip("1:2:3:4::/48")]
)),
Ok(Value::from(false))
);
}
#[test]
fn more_ip_semantics() {
let ext_array = [extension()];
let exts = Extensions::specific_extensions(&ext_array).unwrap();
let request = basic_request();
let entities = basic_entities();
let eval = Evaluator::new(request, &entities, &exts);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("10.0.0.0"), ip("10.0.0.0"))),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("10.0.0.0"), ip("10.0.0.1"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("10.0.0.0/32"), ip("10.0.0.0"))),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("10.0.0.0/24"), ip("10.0.0.0"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("10.0.0.0/32"), ip("10.0.0.0/32"))),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("10.0.0.0/24"), ip("10.0.0.0/32"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("10.0.0.0/24"), ip("10.0.0.1/24"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::is_eq(ip("10.0.0.1/24"), ip("10.0.0.1/29"))),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0"), ip("10.0.0.0/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0"), ip("10.0.0.0/32")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0"), ip("10.0.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0"), ip("10.0.0.1/32")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.1"), ip("10.0.0.0/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.1"), ip("10.0.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/24"), ip("10.0.0.0/32")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/32"), ip("10.0.0.0/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.1/24"), ip("10.0.0.0/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.1/24"), ip("10.0.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/24"), ip("10.0.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/24"), ip("10.0.0.0/29")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/29"), ip("10.0.0.0/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/24"), ip("10.0.0.1/29")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/29"), ip("10.0.0.1/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.1/24"), ip("10.0.0.0/29")]
)),
Ok(Value::from(false))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.1/29"), ip("10.0.0.0/24")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/32"), ip("10.0.0.0/32")]
)),
Ok(Value::from(true))
);
assert_eq!(
eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/32"), ip("10.0.0.0")]
)),
Ok(Value::from(true))
);
assert_ipaddr_err(eval.interpret_inline_policy(&Expr::call_extension_fn(
Name::parse_unqualified_name("isInRange").expect("should be a valid identifier"),
vec![ip("10.0.0.0/33"), ip("10.0.0.0/32")],
)));
}
#[test]
fn test_contains_at_least_two() {
assert!(contains_at_least_two(":::", ':'));
assert!(contains_at_least_two("::", ':'));
assert!(!contains_at_least_two(":", ':'));
}
#[test]
fn test_contains_two_multibyte() {
assert!(!contains_at_least_two("\u{f1b}", '\u{f1b}'));
}
}