use core::cmp::Ordering;
use vitaminc_aead::Encrypt;
use vitaminc_normalize::NormalizeError;
use vitaminc_protected::OpaqueDebug;
use crate::scheme::{same, OrderError, OrderPlaintext, OrderScheme, OrderSpace, OrderTerm, Scalar};
use crate::target::{
FitsIn, FixedWidth, NullPolicy, Nulls, RuntimeNulls, StaticTarget, Supports, Target,
};
use crate::{NullOrder, OrderTarget};
pub trait OrderQuery: OrderScheme {
type Query: AsRef<[u8]>;
fn encrypt_query(
&self,
space: &Self::Space,
plaintext: &OrderPlaintext,
) -> Result<Self::Query, Self::Error>;
fn compare_query(query: &Self::Query, term: &Self::Term) -> Option<Ordering>;
}
#[derive(OpaqueDebug, Clone, PartialEq, Eq)]
pub struct QueryTerm<R, T: Target> {
raw: R,
#[non_sensitive]
target: T,
}
impl<R, T: Target> QueryTerm<R, T> {
pub fn target(&self) -> OrderTarget {
self.target.runtime()
}
pub fn null_order(&self) -> Option<NullOrder> {
self.target.null_order()
}
pub fn raw(&self) -> &R {
&self.raw
}
pub fn into_raw(self) -> R {
self.raw
}
}
impl<R: AsRef<[u8]>, T: Target> QueryTerm<R, T> {
pub fn as_bytes(&self) -> &[u8] {
self.raw.as_ref()
}
}
impl<R: AsRef<[u8]>, T: Target> AsRef<[u8]> for QueryTerm<R, T> {
fn as_ref(&self) -> &[u8] {
self.raw.as_ref()
}
}
impl<S: OrderQuery + Supports<T> + Supports<OrderTarget> + ?Sized, T: Target> OrderSpace<'_, S, T> {
pub fn encrypt_query_checked<V: Encrypt>(
&self,
value: V,
) -> Result<QueryTerm<S::Query, T>, OrderError<S::Error>> {
self.supported()?;
let plaintext = value.visit(Scalar::of(self.target))?;
self.query_opened(&plaintext).map_err(OrderError::Backend)
}
pub fn encrypt_query_term(
&self,
plaintext: &OrderPlaintext,
) -> Result<QueryTerm<S::Query, T>, OrderError<S::Error>> {
if plaintext.domain() != self.domain() {
return Err(OrderError::Term(NormalizeError::Mismatch));
}
self.supported()?;
self.query_opened(plaintext).map_err(OrderError::Backend)
}
pub fn query(&self, raw: S::Query) -> QueryTerm<S::Query, T> {
QueryTerm {
raw,
target: self.target,
}
}
pub fn compare_query(
&self,
query: &QueryTerm<S::Query, T>,
term: &OrderTerm<S::Term, T>,
) -> Option<Ordering> {
(same(query.target, self.target) && same(term.target, self.target))
.then(|| S::compare_query(&query.raw, term.raw()))
.flatten()
}
fn query_opened(&self, plaintext: &OrderPlaintext) -> Result<QueryTerm<S::Query, T>, S::Error> {
let opened = self.opened()?;
self.scheme
.encrypt_query(opened, plaintext)
.map(|raw| self.query(raw))
}
}
impl<S: OrderQuery + Supports<T> + Supports<OrderTarget> + ?Sized, T: StaticTarget>
OrderSpace<'_, S, T>
{
pub fn encrypt_query<V: FitsIn<T>>(
&self,
value: V,
) -> Result<QueryTerm<S::Query, T>, S::Error> {
let plaintext = match value.visit(Scalar::of(T::default())) {
Ok(plaintext) => plaintext,
Err(refused) => unreachable!("a `FitsIn` value was refused: {refused}"),
};
self.query_opened(&plaintext)
}
}
impl<
S: OrderQuery + Supports<T> + Supports<OrderTarget> + ?Sized,
T: FixedWidth,
P: NullPolicy,
> OrderSpace<'_, S, Nulls<T, P>>
{
#[allow(clippy::type_complexity)]
pub fn null_query(&self) -> Result<QueryTerm<S::Query, Nulls<T, P>>, S::Error> {
self.query_opened(&self.sentinel())
}
}
impl<S: OrderQuery + Supports<OrderTarget> + ?Sized> OrderSpace<'_, S, RuntimeNulls> {
#[allow(clippy::type_complexity)]
pub fn null_query(&self) -> Result<QueryTerm<S::Query, RuntimeNulls>, OrderError<S::Error>> {
self.supported()?;
self.query_opened(&self.sentinel())
.map_err(OrderError::Backend)
}
}