use core::cmp::Ordering;
use vitaminc_aead::{Float, Int, Logical, Physical, TimeUnit};
use crate::target::{RuntimeNulls, StaticTarget};
use crate::{
order, order_with_nulls, NullOrder, OrderEncrypt, OrderQuery, OrderSpace, OrderTarget, Supports,
};
#[derive(Clone, Copy)]
struct Row {
target: OrderTarget,
ty: Option<Logical>,
below: Physical<'static>,
above: Physical<'static>,
empty: Option<Physical<'static>>,
}
fn rows() -> Vec<Row> {
use OrderTarget as T;
use Physical::{Bytes, Float as F, Int as I, Text};
let plain = |target, below, above| Row {
target,
ty: None,
below,
above,
empty: None,
};
let typed = |target, ty, below, above| Row {
target,
ty: Some(ty),
below,
above,
empty: None,
};
vec![
plain(T::I16, I(Int::I16(i16::MIN)), I(Int::I16(i16::MAX))),
plain(T::U16, I(Int::U16(0)), I(Int::U16(u16::MAX))),
plain(T::I32, I(Int::I32(i32::MIN)), I(Int::I32(i32::MAX))),
plain(T::U32, I(Int::U32(0)), I(Int::U32(u32::MAX))),
plain(T::I64, I(Int::I64(i64::MIN)), I(Int::I64(i64::MAX))),
plain(T::U64, I(Int::U64(0)), I(Int::U64(u64::MAX))),
plain(T::I128, I(Int::I128(i128::MIN)), I(Int::I128(i128::MAX))),
plain(T::U128, I(Int::U128(0)), I(Int::U128(u128::MAX))),
plain(
T::F64,
F(Float::F64(f64::NEG_INFINITY)),
F(Float::F64(f64::INFINITY)),
),
Row {
target: T::Text,
ty: None,
below: Text("apple"),
above: Text("Banana"),
empty: Some(Text("")),
},
Row {
target: T::Bytes,
ty: None,
below: Bytes(&[0]),
above: Bytes(&[255]),
empty: Some(Bytes(&[])),
},
typed(
T::Date,
Logical::Date,
I(Int::I32(-96_465_292)),
I(Int::I32(95_026_236)),
),
typed(
T::Timestamp,
Logical::Timestamp(TimeUnit::Nanos),
I(Int::I64(-1)),
I(Int::I64(0)),
),
typed(
T::Decimal,
Logical::Decimal { scale: 2 },
I(Int::I64(-150)),
I(Int::I64(150)),
),
typed(T::Uuid, Logical::Uuid, Bytes(&[0; 16]), Bytes(&[1; 16])),
]
}
pub fn conformance<S: Supports<OrderTarget>>(a: &S, b: &S) {
for row in rows().iter().filter(|row| S::supports(row.target)) {
let name = row.target.domain();
let space = a.target(row.target).context("conformance");
let other_context = a.target(row.target).context("other");
let other_key = b.target(row.target).context("conformance");
let term = |space: &OrderSpace<'_, S>, v: Physical<'_>| {
let plaintext = order(row.target, row.ty, v).expect("the inventory encodes");
space
.encrypt_term(&plaintext)
.unwrap_or_else(|e| panic!("{name}: the scheme refused a term of its target: {e}"))
};
let (below, above, again) = (
term(&space, row.below),
term(&space, row.above),
term(&space, row.below),
);
assert_eq!(
space.compare(&below, &above),
Some(Ordering::Less),
"{name}: the terms of two values in order do not compare in that order"
);
assert_eq!(
space.compare(&above, &below),
Some(Ordering::Greater),
"{name}: the comparison is not antisymmetric"
);
assert_eq!(
space.compare(&below, &again),
Some(Ordering::Equal),
"{name}: two terms of one value do not compare equal"
);
let mut values = vec![row.below, row.above];
if let Some(empty) = row.empty {
values.push(empty);
}
for v in values {
let own = term(&space, v);
assert!(
!own.as_bytes().is_empty(),
"{name}: a term is empty, so it depends on nothing"
);
let (under_other_context, under_other_key) =
(term(&other_context, v), term(&other_key, v));
assert_ne!(
S::compare(own.raw(), under_other_context.raw()),
Some(Ordering::Equal),
"{name}: a term compares equal to one under another context, so it does not depend on the salt"
);
assert_ne!(
S::compare(own.raw(), under_other_key.raw()),
Some(Ordering::Equal),
"{name}: a term compares equal to one under another key, so it does not depend on the key, or `a` and `b` hold one key"
);
assert_ne!(
own.as_bytes(),
under_other_context.as_bytes(),
"{name}: a term is the same under another context, so it does not depend on the salt"
);
assert_ne!(
own.as_bytes(),
under_other_key.as_bytes(),
"{name}: a term is the same under another key, so it does not depend on the key, or `a` and `b` hold one key"
);
}
}
for (row, nulls) in placed_rows::<S>() {
let name = row
.target
.domain_with_nulls(nulls)
.expect("a fixed-width row");
let plain = a.target(row.target).context("conformance");
let [space, other_context, other_key] = placed_spaces(a, b, row.target, nulls);
let null = |space: &OrderSpace<'_, S, RuntimeNulls>| {
space
.encrypt_null()
.unwrap_or_else(|e| panic!("{name}: the scheme refused a null's term: {e}"))
};
let value =
order_with_nulls(row.target, nulls, row.ty, row.below).expect("the inventory encodes");
let below = space
.encrypt_term(&value)
.unwrap_or_else(|e| panic!("{name}: the scheme refused a term of its target: {e}"));
let (own, again) = (null(&space), null(&space));
assert_eq!(
space.compare(&below, &own),
Some(end(nulls)),
"{name}: a null does not sort at its end of the space"
);
assert_eq!(
space.compare(&own, &again),
Some(Ordering::Equal),
"{name}: two nulls' terms do not compare equal"
);
assert!(
!own.as_bytes().is_empty(),
"{name}: a null's term is empty, so it depends on nothing"
);
let (under_other_context, under_other_key) = (null(&other_context), null(&other_key));
assert_ne!(
S::compare(own.raw(), under_other_context.raw()),
Some(Ordering::Equal),
"{name}: a null's term compares equal to one under another context, so it does not depend on the salt"
);
assert_ne!(
S::compare(own.raw(), under_other_key.raw()),
Some(Ordering::Equal),
"{name}: a null's term compares equal to one under another key, so it does not depend on the key, or `a` and `b` hold one key"
);
assert_ne!(
own.as_bytes(),
under_other_context.as_bytes(),
"{name}: a null's term is the same under another context, so it does not depend on the salt"
);
assert_ne!(
own.as_bytes(),
under_other_key.as_bytes(),
"{name}: a null's term is the same under another key, so it does not depend on the key, or `a` and `b` hold one key"
);
let in_plain = plain
.encrypt_term(&order(row.target, row.ty, row.below).expect("the inventory encodes"))
.unwrap_or_else(|e| panic!("{name}: the scheme refused a term of its target: {e}"));
assert_ne!(
S::compare(below.raw(), in_plain.raw()),
Some(Ordering::Equal),
"{name}: a term compares equal to the plain space's, so the salt does not bind the null policy"
);
}
}
fn placed_rows<S: Supports<OrderTarget>>() -> Vec<(Row, NullOrder)> {
rows()
.into_iter()
.filter(|row| S::supports(row.target) && row.target.width().is_some())
.flat_map(|row| [NullOrder::First, NullOrder::Last].map(|nulls| (row, nulls)))
.collect()
}
fn placed_spaces<'s, S: Supports<OrderTarget>>(
a: &'s S,
b: &'s S,
target: OrderTarget,
nulls: NullOrder,
) -> [OrderSpace<'s, S, RuntimeNulls>; 3] {
let placed = |scheme: &'s S, context: &str| {
scheme
.target(target)
.context(context)
.nulls(nulls)
.expect("a fixed-width target places nulls")
};
[
placed(a, "conformance"),
placed(a, "other"),
placed(b, "conformance"),
]
}
fn end(nulls: NullOrder) -> Ordering {
match nulls {
NullOrder::First => Ordering::Greater,
NullOrder::Last => Ordering::Less,
}
}
pub fn query_conformance<S: OrderQuery + Supports<OrderTarget>>(a: &S, b: &S) {
for row in rows().iter().filter(|row| S::supports(row.target)) {
let name = row.target.domain();
let space = a.target(row.target).context("conformance");
let other_context = a.target(row.target).context("other");
let other_key = b.target(row.target).context("conformance");
let plaintext =
|v: Physical<'_>| order(row.target, row.ty, v).expect("the inventory encodes");
let term = |space: &OrderSpace<'_, S>, v: Physical<'_>| {
space
.encrypt_term(&plaintext(v))
.unwrap_or_else(|e| panic!("{name}: the scheme refused a term of its target: {e}"))
};
let query = |space: &OrderSpace<'_, S>, v: Physical<'_>| {
space
.encrypt_query_term(&plaintext(v))
.unwrap_or_else(|e| panic!("{name}: the scheme refused a query of its target: {e}"))
};
let (below, above) = (term(&space, row.below), term(&space, row.above));
let (query_below, query_above) = (query(&space, row.below), query(&space, row.above));
assert_eq!(
space.compare_query(&query_below, &above),
Some(Ordering::Less),
"{name}: the query of a lower value does not compare below the term of a higher"
);
assert_eq!(
space.compare_query(&query_above, &below),
Some(Ordering::Greater),
"{name}: the query of a higher value does not compare above the term of a lower"
);
assert_eq!(
space.compare_query(&query_below, &below),
Some(Ordering::Equal),
"{name}: the query of a value does not compare equal to its term"
);
let mut values = vec![row.below, row.above];
if let Some(empty) = row.empty {
values.push(empty);
}
for v in values {
let own = term(&space, v);
let own_query = query(&space, v);
assert!(
!own_query.as_bytes().is_empty(),
"{name}: a query is empty, so it depends on nothing"
);
assert_ne!(
S::compare_query(query(&other_context, v).raw(), own.raw()),
Some(Ordering::Equal),
"{name}: a query under another context compares equal to the term, so it does not depend on the salt"
);
assert_ne!(
S::compare_query(query(&other_key, v).raw(), own.raw()),
Some(Ordering::Equal),
"{name}: a query under another key compares equal to the term, so it does not depend on the key"
);
assert_ne!(
S::compare_query(own_query.raw(), term(&other_context, v).raw()),
Some(Ordering::Equal),
"{name}: a query compares equal to the term under another context, so it does not depend on the salt"
);
assert_ne!(
S::compare_query(own_query.raw(), term(&other_key, v).raw()),
Some(Ordering::Equal),
"{name}: a query compares equal to the term under another key, so it does not depend on the key"
);
}
}
for (row, nulls) in placed_rows::<S>() {
let name = row
.target
.domain_with_nulls(nulls)
.expect("a fixed-width row");
let [space, other_context, other_key] = placed_spaces(a, b, row.target, nulls);
let value =
order_with_nulls(row.target, nulls, row.ty, row.below).expect("the inventory encodes");
let below = space
.encrypt_term(&value)
.unwrap_or_else(|e| panic!("{name}: the scheme refused a term of its target: {e}"));
let null = space
.encrypt_null()
.unwrap_or_else(|e| panic!("{name}: the scheme refused a null's term: {e}"));
let query = |space: &OrderSpace<'_, S, RuntimeNulls>| {
space
.null_query()
.unwrap_or_else(|e| panic!("{name}: the scheme refused a null's query: {e}"))
};
let own = query(&space);
assert_eq!(
space.compare_query(&own, &null),
Some(Ordering::Equal),
"{name}: the null's query does not compare equal to a null's term"
);
assert_eq!(
space.compare_query(&own, &below),
Some(end(nulls).reverse()),
"{name}: the null's query does not sort at its end of the space"
);
assert!(
!own.as_bytes().is_empty(),
"{name}: a null's query is empty, so it depends on nothing"
);
assert_ne!(
S::compare_query(query(&other_context).raw(), null.raw()),
Some(Ordering::Equal),
"{name}: a null's query under another context compares equal to the term, so it does not depend on the salt"
);
assert_ne!(
S::compare_query(query(&other_key).raw(), null.raw()),
Some(Ordering::Equal),
"{name}: a null's query under another key compares equal to the term, so it does not depend on the key"
);
}
}
pub fn static_target<S: Supports<T> + Supports<OrderTarget>, T: StaticTarget>() {
assert!(
<S as Supports<OrderTarget>>::supports(T::TARGET),
"{} implements Supports<{:?}> but refuses it at runtime",
S::NAME,
T::TARGET
);
}