Skip to main content

vitaminc_ore/
scheme.rs

1//! The sealed order plaintext, the trait a scheme implements to encrypt
2//! it, and the space a caller encrypts through.
3//!
4//! A caller opens a space on a scheme, with the target every value in it
5//! is encoded as and the context its terms compare within, and encrypts
6//! values: `scheme.target(I32).context(("orders", "total")).encrypt(42i16)`.
7//! It never sees the encoding. A scheme opens a space once, under the
8//! salt the space derives, and encrypts the sealed [`OrderPlaintext`]s of
9//! that space with what it derived; it never sees bytes that skipped the
10//! encoding (the only ways to make a plaintext are
11//! [`order`](crate::order) and [`order_with_nulls`](crate::order_with_nulls))
12//! or a salt it could derive differently from another scheme.
13
14use core::cmp::Ordering;
15use core::fmt;
16use std::sync::OnceLock;
17
18use vitaminc_aead::plaintext::{self, SourceError};
19use vitaminc_aead::{Encrypt, FixedType, Logical, MapSink, Physical, SeqSink, Sink};
20use vitaminc_context::{Context, IntoContext};
21use vitaminc_normalize::NormalizeError;
22use vitaminc_protected::{Controlled, OpaqueDebug, Protected};
23
24use crate::order::{encode, label, null_plaintext};
25use crate::target::{
26    FitsIn, FixedWidth, NullPolicy, Nulls, OwnTarget, RuntimeNulls, StaticTarget, Supports, Target,
27};
28use crate::{NullOrder, OrderTarget};
29
30/// The label every salt this crate derives leads with.
31const SALT_LABEL: &[u8] = b"vitaminc/ore/v1";
32
33/// One scalar's order term before encryption: bytes whose order is the
34/// value's order, encoded as the target they were made for.
35///
36/// Only [`order`](crate::order) and [`order_with_nulls`](crate::order_with_nulls)
37/// build one, so a scheme can rely on the bytes being the
38/// encoding of their target, and on there being at least one: a
39/// fixed-width target encodes to its width, and text and bytes start with
40/// [`VARIABLE_WIDTH_MARKER`](crate::VARIABLE_WIDTH_MARKER). A caller
41/// never needs one: [`OrderSpace::encrypt`] takes the value itself.
42/// `Debug` shows the domain and width and masks the bytes.
43#[derive(OpaqueDebug)]
44pub struct OrderPlaintext {
45    #[non_sensitive]
46    target: OrderTarget,
47    #[non_sensitive]
48    nulls: Option<NullOrder>,
49    #[non_sensitive]
50    domain: &'static str,
51    bytes: Protected<Vec<u8>>,
52}
53
54impl OrderPlaintext {
55    pub(crate) fn new(
56        target: OrderTarget,
57        nulls: Option<NullOrder>,
58        domain: &'static str,
59        bytes: Protected<Vec<u8>>,
60    ) -> Self {
61        debug_assert_eq!(
62            label(target, nulls),
63            Some(domain),
64            "the domain is the space's label"
65        );
66        debug_assert!(
67            target.width().is_none_or(|w| w == bytes.risky_ref().len()),
68            "{target:?} encodes to its width"
69        );
70        debug_assert!(
71            !bytes.risky_ref().is_empty(),
72            "an order plaintext is never empty"
73        );
74        Self {
75            target,
76            nulls,
77            domain,
78            bytes,
79        }
80    }
81
82    /// The target the bytes were encoded as.
83    pub fn target(&self) -> OrderTarget {
84        self.target
85    }
86
87    /// Where the space the plaintext was encoded for places nulls, or
88    /// `None` for a space that gives them no term.
89    pub fn null_order(&self) -> Option<NullOrder> {
90        self.nulls
91    }
92
93    /// The frozen domain label, one of the [`domain`](crate::domain)
94    /// constants: the target's, or the label of its null policy.
95    pub fn domain(&self) -> &'static str {
96        self.domain
97    }
98
99    /// The width every term of this target has, or `None` for text and
100    /// bytes, whose terms vary in length. A fixed-width scheme says which
101    /// widths it takes in [`Supports::supports`], so it refuses a target
102    /// rather than a value, and never sees a plaintext of another.
103    pub fn width(&self) -> Option<usize> {
104        self.target.width()
105    }
106
107    /// The term's bytes, protected, never empty. A scheme reads them with
108    /// [`risky_ref`](Controlled::risky_ref) and nothing else should.
109    pub fn bytes(&self) -> &Protected<Vec<u8>> {
110        &self.bytes
111    }
112}
113
114/// Why a space produced no term. Never includes plaintext.
115#[derive(Debug, thiserror::Error)]
116#[non_exhaustive]
117pub enum OrderError<E> {
118    /// The scalar has no order term as the space's target: no order for
119    /// its kind, another kind than the target, outside the target, not
120    /// valid for its logical type, or text with a code point unassigned
121    /// in Unicode 16.
122    #[error("no order term: {0}")]
123    Term(#[from] NormalizeError),
124    /// The value is a sequence, a map or a passthrough, which has no
125    /// single term. Walk it with [`order_terms`](crate::order_terms).
126    #[error("a container has no single order term; walk it for one per scalar")]
127    NotScalar,
128    /// The value could not be read: its source failed before any term
129    /// was made.
130    #[error("the value could not be read: {0}")]
131    Source(SourceError),
132    /// The scheme does not take terms of this target, chosen while the
133    /// program ran: a fixed-width scheme given text, or a width above its
134    /// limit. A target named when the program is compiled is checked
135    /// then, through [`Supports`], and never reaches this.
136    #[error("{scheme} has no order term for {domain}")]
137    Unsupported {
138        /// The scheme's [`NAME`](OrderScheme::NAME).
139        scheme: &'static str,
140        /// The target's domain label.
141        domain: &'static str,
142    },
143    /// The scheme's own failure.
144    #[error("the order scheme failed: {0}")]
145    Backend(E),
146}
147
148/// An order scheme: encrypts one sealed plaintext so that the terms still
149/// compare.
150///
151/// A scheme implements [`open`](OrderScheme::open), which derives what it
152/// keeps for one space from the salt, [`encrypt_plaintext`](OrderScheme::encrypt_plaintext)
153/// over that, and [`compare`](OrderScheme::compare); and it says which
154/// targets it takes through [`Supports`]. A caller does not call any of
155/// them: it opens a space with [`OrderEncrypt::target`], which every
156/// scheme has, and encrypts values there. The space derives the salt the
157/// scheme binds, so no scheme can leave a part of it out, opens the
158/// scheme under it once, and refuses a target the scheme does not take
159/// before the scheme sees it.
160///
161/// Terms compare only between terms of the same scheme, key, target and
162/// context: a space. The scheme keeps its own comparison (CLWW ORE and
163/// block ORE do not compare byte by byte) in [`compare`](Self::compare),
164/// and its own stored bytes through `AsRef<[u8]>`. A term is not `Ord`:
165/// that would promise an order between any two terms, and no term carries
166/// which space it came from, so two terms of different spaces would
167/// compare as noise and break every sort and tree that trusted the
168/// promise.
169///
170/// Implemented by the scheme crates behind a `vitaminc` feature, and by a
171/// mock in this crate's tests. An implementation reads the plaintext's
172/// [`bytes`](OrderPlaintext::bytes) and binds the salt through what
173/// [`open`](Self::open) derived from it; it never pads or truncates. The bytes are never empty (a variable-width encoding starts
174/// with [`VARIABLE_WIDTH_MARKER`](crate::VARIABLE_WIDTH_MARKER)), so a
175/// scheme can rely on running its keyed primitive at least once, and
176/// every term it makes must depend on the key and the salt: a term that
177/// is the same under every key could be written into any index by
178/// anyone. `test_support` (behind the `test-support` feature) checks a
179/// real scheme for this.
180pub trait OrderScheme {
181    /// The scheme's name, bound into every term's salt and named in its
182    /// refusals: `"cllw-ore"`, `"cllw-ope"`, `"block-ore"`.
183    const NAME: &'static str;
184
185    /// What one term encrypts to.
186    type Term: AsRef<[u8]>;
187
188    /// The scheme's own failure. [`Infallible`](core::convert::Infallible)
189    /// for a scheme that cannot fail once keyed.
190    type Error: core::error::Error + Send + Sync + 'static;
191
192    /// What the scheme keeps for one space: whatever it derives from the
193    /// salt, so that it is derived for the space rather than for every
194    /// term. Block ORE keeps the keys and cipher it derives under the
195    /// salt; a scheme that passes the salt straight to its primitive, as
196    /// CLWW does, keeps the salt's bytes (`Protected<Vec<u8>>`).
197    ///
198    /// It must wipe itself when it is dropped, because what a scheme
199    /// derives from its key is as secret as the key. A space is dropped
200    /// with its [`OrderSpace`], and also as the loser of a race to open
201    /// (see [`open`](Self::open)), where nothing else would wipe it. Keep
202    /// derived bytes in [`Protected`], hold a cipher whose crate wipes it
203    /// on drop, or use `()` for a scheme that keeps nothing. A `Vec<u8>`
204    /// of derived keys does not compile.
205    ///
206    /// An [`OrderSpace`] holds a reference to the scheme and one of these,
207    /// so it is `Send` when the scheme is `Sync` and this is `Send`, and
208    /// `Sync` when this is `Sync` too. A scheme that keeps a random number
209    /// generator behind a `RefCell` here has a space per thread.
210    type Space: zeroize::ZeroizeOnDrop;
211
212    /// Open a space: derive what [`Space`](Self::Space) keeps, binding
213    /// `salt`: `PAE("vitaminc/ore/v1", NAME, domain, context)`, which the
214    /// space derives. Called when a space's first value is encrypted, or
215    /// from [`OrderSpace::open`], and not for every term.
216    ///
217    /// `target` is the space's, the one every plaintext the space hands
218    /// to [`encrypt_plaintext`](Self::encrypt_plaintext) is encoded as.
219    /// The salt binds its domain but does not give it back, and a scheme
220    /// that keys one of several engines by width, or pads text and not
221    /// bytes, decides that here, once, rather than for every term. A
222    /// scheme with one engine for every target ignores it.
223    ///
224    /// It may run more than once for one space: again after it fails, and
225    /// by more than one thread at once if they race to the first value,
226    /// in which case one result is kept and the rest dropped. So it must
227    /// be a pure derivation from `salt` and the scheme's key, with no
228    /// side effect that assumes it runs once.
229    ///
230    /// # Errors
231    ///
232    /// The scheme's own failure: seeding its random number generator,
233    /// say. The space reports it from every encryption, and tries again
234    /// on the next.
235    fn open(&self, target: OrderTarget, salt: &Context<'_>) -> Result<Self::Space, Self::Error>;
236
237    /// Encrypt a sealed plaintext of `space`, which [`open`](Self::open)
238    /// made under the space's salt.
239    ///
240    /// The plaintext is of a target the scheme [`Supports`]: the space
241    /// checks before calling. So the only failure is the scheme's own.
242    ///
243    /// # Errors
244    ///
245    /// The scheme's own failure: its random number generator, say.
246    fn encrypt_plaintext(
247        &self,
248        space: &Self::Space,
249        plaintext: &OrderPlaintext,
250    ) -> Result<Self::Term, Self::Error>;
251
252    /// The order of two terms of one space, as the order of the values
253    /// they encrypt. Needs no key: that is what makes the scheme an order
254    /// scheme.
255    ///
256    /// Over the terms of one space this is a total order, so a caller may
257    /// sort with it. `None` says the two cannot be compared: one is not a
258    /// term of this scheme, or the two were not made to compare (block
259    /// ORE under different padding). A scheme returns `None` wherever it
260    /// can tell, and never falls back to comparing bytes it cannot order.
261    /// It cannot tell everything: two well-formed terms of different
262    /// spaces may compare as noise, so a caller keeps a space's terms
263    /// together, as [`OrderSpace::compare`] says.
264    ///
265    /// The noise is in the direction, never in equality. `Some(Equal)`
266    /// means the two terms encrypt one value in one space, up to the
267    /// scheme's own collision bound: a term that lacks every block, or
268    /// that ignores its key or salt, compares equal to one of another
269    /// space, and the `test_support` checks fail it for that. Block ORE
270    /// keeps this because equality needs every block's keyed tag to
271    /// agree, and an OPE because equality is equal bytes. A scheme that
272    /// cannot keep it answers `None` across spaces instead.
273    fn compare(a: &Self::Term, b: &Self::Term) -> Option<Ordering>;
274}
275
276/// What a caller does with any [`OrderScheme`]: name a target, a context,
277/// or just a value, and get terms. Implemented for every scheme and for
278/// nothing else, so a scheme cannot change how a space binds its terms.
279///
280/// Every step of the chain is usable for terms. A target alone is a space
281/// under the empty context; a context alone takes values whose Rust type
282/// says the target; a value alone uses both defaults. Queries come only
283/// from a space, through [`OrderSpace::encrypt_query`], so a chain that
284/// makes one names its target.
285///
286/// ```
287/// use std::cmp::Ordering;
288/// use vitaminc_ore::target::I32;
289/// use vitaminc_ore::{OrderEncrypt, OrderTarget};
290/// # use vitaminc_ore::{Context, OrderPlaintext, OrderScheme};
291/// # /// Keeps the bytes as they are, for the example. Any scheme will do.
292/// # struct Clear;
293/// # impl OrderScheme for Clear {
294/// #     const NAME: &'static str = "clear";
295/// #     type Term = Vec<u8>;
296/// #     type Error = core::convert::Infallible;
297/// #     type Space = ();
298/// #     fn open(&self, _: vitaminc_ore::OrderTarget, _: &Context<'_>) -> Result<(), Self::Error> {
299/// #         Ok(())
300/// #     }
301/// #     fn encrypt_plaintext(&self, _: &(), p: &OrderPlaintext) -> Result<Vec<u8>, Self::Error> {
302/// #         use vitaminc_protected::Controlled;
303/// #         Ok(p.bytes().risky_ref().clone())
304/// #     }
305/// #     fn compare(a: &Vec<u8>, b: &Vec<u8>) -> Option<Ordering> {
306/// #         Some(a.cmp(b))
307/// #     }
308/// # }
309/// # vitaminc_ore::supports_every_target!(Clear);
310/// # let scheme = Clear;
311///
312/// // Held, for many values.
313/// let quantity = scheme.target(I32).context(("orders", "quantity"));
314/// let a = quantity.encrypt(42i16)?; // an i16 always fits an i32
315/// let b = quantity.encrypt_checked(43i64)?; // an i64 is checked
316/// assert_eq!(quantity.compare(&a, &b), Some(Ordering::Less));
317///
318/// // One-shot chains.
319/// scheme.target(I32).encrypt(42i16)?; // context ()
320/// scheme.context("tenant-7").encrypt(42i64)?; // the type's own target, I64
321/// scheme.encrypt(42i64)?; // own target, no context
322///
323/// // A target chosen while the program runs, as an index layer needs.
324/// let index = scheme.target(OrderTarget::I32).context("ctx");
325/// index.encrypt_checked(42i64)?;
326/// # Ok::<(), Box<dyn std::error::Error>>(())
327/// ```
328pub trait OrderEncrypt: OrderScheme {
329    /// A space of `target` values under the empty context: a marker from
330    /// [`target`](crate::target), or an [`OrderTarget`] chosen while the
331    /// program runs. Chain [`context`](OrderSpace::context) to bind one.
332    ///
333    /// Compiles only for a target this scheme [`Supports`].
334    fn target<T: Target>(&self, target: T) -> OrderSpace<'_, Self, T>
335    where
336        Self: Supports<T> + Supports<OrderTarget>,
337    {
338        OrderSpace::new(self, target, &().into_context().encode())
339    }
340
341    /// The terms that compare within `context` (a table and column, a
342    /// tenant, whatever they must compare within): chain
343    /// [`target`](OrderContext::target), or encrypt a value whose type is
344    /// its target.
345    fn context<'a, C: IntoContext<'a>>(&self, context: C) -> OrderContext<'_, Self> {
346        OrderContext {
347            scheme: self,
348            context: context.into_context().encode().into_owned(),
349        }
350    }
351
352    /// Encrypt a value as the target its Rust type is, under the empty
353    /// context: `scheme.target(V::Target).encrypt(value)`. A one-shot
354    /// chain makes a space per call, so the scheme opens per value; hold
355    /// `scheme.target(..).context(..)` to derive once for many values.
356    ///
357    /// # Errors
358    ///
359    /// The scheme's own failure, and nothing else: the value always fits.
360    fn encrypt<V>(&self, value: V) -> Result<OrderTerm<Self::Term, V::Target>, Self::Error>
361    where
362        V: OwnTarget + FitsIn<V::Target>,
363        Self: Supports<V::Target> + Supports<OrderTarget>,
364    {
365        self.target(V::Target::default()).encrypt(value)
366    }
367
368    /// Encrypt a value as the target its Rust type is, checking it while
369    /// the program runs, under the empty context:
370    /// `scheme.target(V::Target).encrypt_checked(value)`.
371    ///
372    /// # Errors
373    ///
374    /// As [`OrderSpace::encrypt_checked`].
375    #[allow(clippy::type_complexity)]
376    fn encrypt_checked<V: OwnTarget>(
377        &self,
378        value: V,
379    ) -> Result<OrderTerm<Self::Term, V::Target>, OrderError<Self::Error>>
380    where
381        Self: Supports<V::Target> + Supports<OrderTarget>,
382    {
383        self.target(V::Target::default()).encrypt_checked(value)
384    }
385
386    /// `scheme.target(target).context(context)`, as the 0.6 API spelt it.
387    #[deprecated(since = "0.7.0", note = "use `scheme.target(target).context(context)`")]
388    fn column<'a, C: IntoContext<'a>>(
389        &self,
390        target: OrderTarget,
391        context: C,
392    ) -> OrderSpace<'_, Self, OrderTarget>
393    where
394        Self: Supports<OrderTarget>,
395    {
396        self.target(target).context(context)
397    }
398}
399
400impl<S: OrderScheme + ?Sized> OrderEncrypt for S {}
401
402/// `PAE("vitaminc/ore/v1", scheme, domain, context)`. Leading with this
403/// crate's own label keeps it apart from any context another crate
404/// derives from the same parts. The domain is the space's label, which
405/// names its null policy, so a space that places nulls never shares a
406/// salt with the plain space of its target.
407fn salt(scheme: &str, domain: &str, context: &Context<'_>) -> Context<'static> {
408    Context::pae(&[
409        SALT_LABEL,
410        scheme.as_bytes(),
411        domain.as_bytes(),
412        context.as_bytes(),
413    ])
414}
415
416/// A scheme and a context, with the target still to come: from
417/// [`OrderEncrypt::context`]. `Debug` masks the scheme, whose key it
418/// holds.
419#[derive(OpaqueDebug)]
420pub struct OrderContext<'s, S: OrderScheme + ?Sized> {
421    scheme: &'s S,
422    #[non_sensitive]
423    context: Context<'static>,
424}
425
426impl<'s, S: OrderScheme + ?Sized> OrderContext<'s, S> {
427    /// The space of `target` values under this context.
428    ///
429    /// Compiles only for a target the scheme [`Supports`].
430    pub fn target<T: Target>(&self, target: T) -> OrderSpace<'s, S, T>
431    where
432        S: Supports<T> + Supports<OrderTarget>,
433    {
434        OrderSpace::new(self.scheme, target, &self.context)
435    }
436
437    /// Encrypt a value as the target its Rust type is, under this context.
438    ///
439    /// # Errors
440    ///
441    /// The scheme's own failure, and nothing else: the value always fits.
442    pub fn encrypt<V>(&self, value: V) -> Result<OrderTerm<S::Term, V::Target>, S::Error>
443    where
444        V: OwnTarget + FitsIn<V::Target>,
445        S: Supports<V::Target> + Supports<OrderTarget>,
446    {
447        self.target(V::Target::default()).encrypt(value)
448    }
449
450    /// Encrypt a value as the target its Rust type is, checking it while
451    /// the program runs, under this context.
452    ///
453    /// # Errors
454    ///
455    /// As [`OrderSpace::encrypt_checked`].
456    #[allow(clippy::type_complexity)]
457    pub fn encrypt_checked<V: OwnTarget>(
458        &self,
459        value: V,
460    ) -> Result<OrderTerm<S::Term, V::Target>, OrderError<S::Error>>
461    where
462        S: Supports<V::Target> + Supports<OrderTarget>,
463    {
464        self.target(V::Target::default()).encrypt_checked(value)
465    }
466}
467
468/// One space of an order index on a scheme: the set of terms that compare
469/// with each other, under one scheme, key, target and context. It holds
470/// the target, the salt its terms are bound under, fixed when it is made
471/// so every term of it agrees, and what the scheme derives under that
472/// salt ([`OrderScheme::Space`]), derived once for every term of the
473/// space. From [`OrderEncrypt::target`] or [`OrderContext::target`].
474/// `Debug` shows the target and domain and masks the scheme, whose key
475/// it holds, the context, the salt and what was derived.
476///
477/// The scheme opens the space when its first value is encrypted, or when
478/// [`open`](Self::open) is called, so making one cannot fail and a chain
479/// never needs a `?` before its value. If the scheme fails to open, every
480/// encryption reports it and the next tries again. A one-shot chain
481/// (`scheme.encrypt(v)`, `scheme.target(I32).encrypt(v)`) makes a space
482/// per call and so opens the scheme per value; hold the space to derive
483/// once for many values.
484///
485/// `T` is the target: a marker from [`target`](crate::target), whose
486/// values the compiler checks through [`encrypt`](Self::encrypt), or
487/// [`OrderTarget`], chosen while the program runs, whose values
488/// [`encrypt_checked`](Self::encrypt_checked) checks then.
489///
490/// A space holds a reference to the scheme and the scheme's
491/// [`Space`](OrderScheme::Space), so it is `Send` when the scheme is
492/// `Sync` and its `Space` is `Send`, and `Sync` when its `Space` is `Sync`
493/// too. A space of a scheme that keeps a random number generator behind
494/// a `RefCell` is opened per thread.
495#[derive(OpaqueDebug)]
496pub struct OrderSpace<'s, S: OrderScheme + ?Sized, T: Target = OrderTarget> {
497    pub(crate) scheme: &'s S,
498    #[non_sensitive]
499    pub(crate) target: T,
500    /// The space's domain label: the target's, or its null policy's.
501    #[non_sensitive]
502    domain: &'static str,
503    /// The context the space was made under, kept so that
504    /// [`nulls`](OrderSpace::nulls) can make the same space with a policy.
505    /// Masked in `Debug` with the salt it is bound into.
506    context: Context<'static>,
507    salt: Context<'static>,
508    /// What the scheme derived under the salt, once it has.
509    opened: OnceLock<S::Space>,
510}
511
512/// `OrderSpace` under a target chosen while the program runs, as the 0.6
513/// API named it.
514#[deprecated(since = "0.7.0", note = "use `OrderSpace<'s, S, OrderTarget>`")]
515pub type OrderColumn<'s, S> = OrderSpace<'s, S, OrderTarget>;
516
517impl<'s, S: Supports<T> + Supports<OrderTarget> + ?Sized, T: Target> OrderSpace<'s, S, T> {
518    fn new(scheme: &'s S, target: T, context: &Context<'_>) -> Self {
519        // A marker impl vouches for its target; the runtime list must
520        // agree, or a space under the marker would hand the scheme a
521        // plaintext it cannot take. `supports_targets!` writes both from
522        // one list; this catches a pair written by hand that disagree.
523        debug_assert!(
524            !<S as Supports<T>>::supports(target)
525                || <S as Supports<OrderTarget>>::supports(target.runtime()),
526            "{} implements Supports<{:?}> but refuses it at runtime",
527            S::NAME,
528            target.runtime()
529        );
530        // Every `Nulls` target is made by a `nulls` method, which takes a
531        // fixed-width target only, so every space's target has a label.
532        let domain = label(target.runtime(), target.null_order())
533            .expect("a null policy is only ever set on a fixed-width target");
534        Self {
535            scheme,
536            target,
537            domain,
538            context: context.clone().into_owned(),
539            salt: salt(S::NAME, domain, context),
540            opened: OnceLock::new(),
541        }
542    }
543
544    /// Open the space now rather than at its first value: have the scheme
545    /// derive what it keeps for the space under the salt. Encrypting does
546    /// this when it has not been done; call it to hear about a scheme
547    /// that cannot open before the first value, or to pay for the
548    /// derivation up front.
549    ///
550    /// # Errors
551    ///
552    /// The scheme's own failure, from [`OrderScheme::open`]. Nothing is
553    /// kept, so the next call tries again.
554    pub fn open(&self) -> Result<(), S::Error> {
555        self.opened().map(|_| ())
556    }
557
558    /// What the scheme keeps for this space, opening it if it has not
559    /// been. Two threads may open it at once; one result is kept and the
560    /// other dropped, and both are as good.
561    pub(crate) fn opened(&self) -> Result<&S::Space, S::Error> {
562        if let Some(opened) = self.opened.get() {
563            return Ok(opened);
564        }
565        let opened = self.scheme.open(self.target.runtime(), &self.salt)?;
566        Ok(self.opened.get_or_init(|| opened))
567    }
568
569    /// This target under `context` instead: a new space, whose terms do
570    /// not compare with this one's.
571    pub fn context<'a, C: IntoContext<'a>>(&self, context: C) -> Self {
572        Self::new(self.scheme, self.target, &context.into_context().encode())
573    }
574
575    /// The type every value of this space is encoded as.
576    pub fn target(&self) -> OrderTarget {
577        self.target.runtime()
578    }
579
580    /// Where this space places nulls, or `None` if it gives them no term.
581    /// Set with [`nulls`](OrderSpace::nulls).
582    pub fn null_order(&self) -> Option<NullOrder> {
583        self.target.null_order()
584    }
585
586    /// The frozen domain label of every term of this space: the target's,
587    /// or the label of its null policy.
588    pub fn domain(&self) -> &'static str {
589        self.domain
590    }
591
592    /// The salt every term of this space is bound under. Not secret.
593    pub fn salt(&self) -> &Context<'static> {
594        &self.salt
595    }
596
597    /// Encrypt one scalar as this space's target, checking it while the
598    /// program runs: an integer of any width it fits, a float, a string,
599    /// bytes, or a typed value such as a date. Any [`Encrypt`] value,
600    /// which is what an index layer holds when the target was chosen
601    /// while the program ran.
602    ///
603    /// A scalar with no term is refused rather than skipped: a caller who
604    /// asks for one term and gets none should hear about it. A null has a
605    /// term only in a space that places nulls, where it is the space's
606    /// sentinel.
607    ///
608    /// # Errors
609    ///
610    /// [`OrderError::Unsupported`] if the scheme does not take this
611    /// target, [`OrderError::Term`] if the scalar has no term as the
612    /// space's target (with [`NormalizeError::Reserved`] for the maximum
613    /// of an integer or uuid target in a space that places nulls),
614    /// [`OrderError::NotScalar`] if the value is a container,
615    /// [`OrderError::Source`] if the value could not be read,
616    /// [`OrderError::Backend`] if the scheme failed.
617    pub fn encrypt_checked<V: Encrypt>(
618        &self,
619        value: V,
620    ) -> Result<OrderTerm<S::Term, T>, OrderError<S::Error>> {
621        self.supported()?;
622        let plaintext = value.visit(Scalar::of(self.target))?;
623        self.encrypt_plaintext(&plaintext)
624    }
625
626    /// Encrypt a plaintext [`order_terms`](crate::order_terms) collected
627    /// from a whole value.
628    ///
629    /// # Errors
630    ///
631    /// [`OrderError::Term`] with [`NormalizeError::Mismatch`] if it was
632    /// encoded for another space than this one (another target, or
633    /// another null policy), else as
634    /// [`encrypt_checked`](Self::encrypt_checked).
635    pub fn encrypt_term(
636        &self,
637        plaintext: &OrderPlaintext,
638    ) -> Result<OrderTerm<S::Term, T>, OrderError<S::Error>> {
639        if plaintext.domain() != self.domain {
640            return Err(OrderError::Term(NormalizeError::Mismatch));
641        }
642        self.supported()?;
643        self.encrypt_plaintext(plaintext)
644    }
645
646    /// A term the scheme already made in this space, read back from
647    /// storage say, as the space's own: it then compares with the space's
648    /// other terms. The caller vouches that it was made here; the space
649    /// cannot tell.
650    pub fn term(&self, raw: S::Term) -> OrderTerm<S::Term, T> {
651        OrderTerm {
652            raw,
653            target: self.target,
654        }
655    }
656
657    /// The order of two of this space's terms, as the order of the values
658    /// they encrypt: [`OrderScheme::compare`]. A total order over the
659    /// space's terms, so they sort by it.
660    ///
661    /// Both terms must be this space's. A term of a target named when the
662    /// program is compiled cannot be another target's or another null
663    /// policy's: that does not compile. A term of one chosen while it ran
664    /// carries its target and its null policy, so two of different
665    /// targets or policies, or of another than this space's, are `None`. The scheme cannot always
666    /// tell two spaces' terms apart otherwise, and may order them as
667    /// noise. `None` where it can tell: a term not of this scheme, or two
668    /// terms not made to compare.
669    pub fn compare(
670        &self,
671        a: &OrderTerm<S::Term, T>,
672        b: &OrderTerm<S::Term, T>,
673    ) -> Option<Ordering> {
674        (same(a.target, self.target) && same(b.target, self.target))
675            .then(|| S::compare(&a.raw, &b.raw))
676            .flatten()
677    }
678
679    /// Refuse a target the scheme does not take, by the scheme's runtime
680    /// list. A marker's impl already vouched for its target when the
681    /// space was opened; asking again costs a match.
682    pub(crate) fn supported(&self) -> Result<(), OrderError<S::Error>> {
683        if <S as Supports<OrderTarget>>::supports(self.target.runtime()) {
684            Ok(())
685        } else {
686            Err(OrderError::Unsupported {
687                scheme: S::NAME,
688                domain: self.domain,
689            })
690        }
691    }
692
693    fn encrypt_plaintext(
694        &self,
695        plaintext: &OrderPlaintext,
696    ) -> Result<OrderTerm<S::Term, T>, OrderError<S::Error>> {
697        self.encrypt_opened(plaintext).map_err(OrderError::Backend)
698    }
699
700    /// Open the space if it is not, then encrypt one plaintext of it.
701    fn encrypt_opened(
702        &self,
703        plaintext: &OrderPlaintext,
704    ) -> Result<OrderTerm<S::Term, T>, S::Error> {
705        let opened = self.opened()?;
706        self.scheme
707            .encrypt_plaintext(opened, plaintext)
708            .map(|raw| self.term(raw))
709    }
710}
711
712impl<S: Supports<T> + Supports<OrderTarget> + ?Sized, T: StaticTarget> OrderSpace<'_, S, T> {
713    /// Encrypt a value that always fits this space's target, as the
714    /// [`FitsIn`] table says: an `i16` into an `I32` space, an `f32` into
715    /// an `F64` one. The compiler refuses a value that might not fit, and
716    /// names [`encrypt_checked`](Self::encrypt_checked) for it.
717    ///
718    /// # Errors
719    ///
720    /// The scheme's own failure, and nothing else: the value always fits,
721    /// and the scheme [`Supports`] the target. A scheme whose `Error` is
722    /// [`Infallible`](core::convert::Infallible) cannot fail at all.
723    pub fn encrypt<V: FitsIn<T>>(&self, value: V) -> Result<OrderTerm<S::Term, T>, S::Error> {
724        let plaintext = match value.visit(Scalar::of(T::default())) {
725            Ok(plaintext) => plaintext,
726            // `FitsIn` is a sealed table of conversions the encoding never
727            // refuses, and this crate's tests encrypt every row at its
728            // extremes, so no value reaches here.
729            Err(refused) => unreachable!("a `FitsIn` value was refused: {refused}"),
730        };
731        self.encrypt_opened(&plaintext)
732    }
733}
734
735/// Whether two targets name one space's target: the same type and the
736/// same null policy.
737pub(crate) fn same<T: Target>(a: T, b: T) -> bool {
738    a.runtime() == b.runtime() && a.null_order() == b.null_order()
739}
740
741impl<'s, S: Supports<T> + Supports<OrderTarget> + ?Sized, T: FixedWidth> OrderSpace<'s, S, T> {
742    /// This target and context in a space that places nulls as the policy
743    /// marker says, [`First`](crate::target::First) or
744    /// [`Last`](crate::target::Last): a new space, whose terms do not
745    /// compare with this one's.
746    ///
747    /// Every null in it, typed or plain, encodes to the space's sentinel,
748    /// and for an integer or uuid target the target's maximum is refused
749    /// with [`NormalizeError::Reserved`]; [`NullOrder`] has the rule and
750    /// what a placed null reveals. A value that might be the maximum goes
751    /// through [`encrypt_checked`](OrderSpace::encrypt_checked), which
752    /// reports it, so a space with a policy has no
753    /// [`encrypt`](OrderSpace::encrypt).
754    ///
755    /// The policy is part of the space's type, `Nulls<T, P>`, so a term of
756    /// a nulls-first space does not compile against a nulls-last space's
757    /// [`compare`](OrderSpace::compare). Compiles only for a fixed-width
758    /// marker: text and bytes take no null policy. A layer that chooses
759    /// the policy while the program runs opens the space with an
760    /// [`OrderTarget`] and passes a [`NullOrder`] instead, as the second
761    /// half of the example shows.
762    ///
763    /// ```
764    /// use vitaminc_ore::target::{Last, I32};
765    /// use vitaminc_ore::{NormalizeError, NullOrder, OrderEncrypt, OrderError, OrderTarget};
766    /// # use vitaminc_ore::{Context, OrderPlaintext, OrderScheme};
767    /// # struct Clear;
768    /// # impl OrderScheme for Clear {
769    /// #     const NAME: &'static str = "clear";
770    /// #     type Term = Vec<u8>;
771    /// #     type Error = core::convert::Infallible;
772    /// #     type Space = ();
773    /// #     fn open(&self, _: vitaminc_ore::OrderTarget, _: &Context<'_>) -> Result<(), Self::Error> {
774    /// #         Ok(())
775    /// #     }
776    /// #     fn encrypt_plaintext(&self, _: &(), p: &OrderPlaintext) -> Result<Vec<u8>, Self::Error> {
777    /// #         use vitaminc_protected::Controlled;
778    /// #         Ok(p.bytes().risky_ref().clone())
779    /// #     }
780    /// #     fn compare(a: &Vec<u8>, b: &Vec<u8>) -> Option<core::cmp::Ordering> {
781    /// #         Some(a.cmp(b))
782    /// #     }
783    /// # }
784    /// # vitaminc_ore::supports_every_target!(Clear);
785    /// # let scheme = Clear;
786    /// use std::cmp::Ordering;
787    ///
788    /// // A marker target takes a marker policy, checked by the compiler.
789    /// let ages = scheme.context("users.age").target(I32).nulls(Last);
790    /// let (age, absent) = (ages.encrypt_checked(36i32)?, ages.encrypt_checked(None::<i32>)?);
791    /// assert_eq!(ages.compare(&age, &absent), Some(Ordering::Less));
792    /// assert!(matches!(
793    ///     ages.encrypt_checked(i32::MAX),
794    ///     Err(OrderError::Term(NormalizeError::Reserved))
795    /// ));
796    ///
797    /// // A target chosen while the program runs takes a policy chosen then
798    /// // too, and refuses text and bytes.
799    /// let index = scheme.target(OrderTarget::I32).context("users.age").nulls(NullOrder::Last)?;
800    /// let (age, absent) = (index.encrypt_checked(36i32)?, index.encrypt_null()?);
801    /// assert_eq!(index.compare(&age, &absent), Some(Ordering::Less));
802    /// assert_eq!(
803    ///     scheme.target(OrderTarget::Text).nulls(NullOrder::Last).err(),
804    ///     Some(NormalizeError::NoDomain)
805    /// );
806    /// # Ok::<(), Box<dyn std::error::Error>>(())
807    /// ```
808    pub fn nulls<P: NullPolicy>(&self, _: P) -> OrderSpace<'s, S, Nulls<T, P>> {
809        OrderSpace::new(self.scheme, Nulls::new(self.target), &self.context)
810    }
811}
812
813impl<'s, S: Supports<OrderTarget> + ?Sized> OrderSpace<'s, S, OrderTarget> {
814    /// This target and context in a space that places nulls as `nulls`
815    /// says, for a target chosen while the program runs: a new space,
816    /// whose terms do not compare with this one's. As
817    /// [`nulls`](OrderSpace::nulls) on a marker target, which has the
818    /// rules, except that the policy is a value: a term carries it, and
819    /// two terms of different policies compare as `None`.
820    ///
821    /// # Errors
822    ///
823    /// [`NormalizeError::NoDomain`] for text and bytes, which take no null
824    /// policy, rather than a space that refuses every value.
825    pub fn nulls(
826        &self,
827        nulls: NullOrder,
828    ) -> Result<OrderSpace<'s, S, RuntimeNulls>, NormalizeError> {
829        if self.target.domain_with_nulls(nulls).is_none() {
830            return Err(NormalizeError::NoDomain);
831        }
832        Ok(OrderSpace::<'s, S, RuntimeNulls>::new(
833            self.scheme,
834            RuntimeNulls::new(self.target, nulls),
835            &self.context,
836        ))
837    }
838}
839
840impl<S: Supports<T> + Supports<OrderTarget> + ?Sized, T: Target> OrderSpace<'_, S, T> {
841    /// The space's sentinel, sealed. Called only on a space that places
842    /// nulls, whose target is a [`Nulls`] or a [`RuntimeNulls`].
843    pub(crate) fn sentinel(&self) -> OrderPlaintext {
844        self.target
845            .null_order()
846            .and_then(|order| null_plaintext(self.target.runtime(), order))
847            .expect("a null policy is only ever set on a fixed-width target")
848    }
849}
850
851impl<S: Supports<T> + Supports<OrderTarget> + ?Sized, T: FixedWidth, P: NullPolicy>
852    OrderSpace<'_, S, Nulls<T, P>>
853{
854    /// The term of a null in this space: its sentinel, encrypted. The same
855    /// as [`encrypt_checked`](OrderSpace::encrypt_checked) of any null.
856    ///
857    /// # Errors
858    ///
859    /// The scheme's own failure, and nothing else: the scheme
860    /// [`Supports`] the target.
861    #[allow(clippy::type_complexity)]
862    pub fn encrypt_null(&self) -> Result<OrderTerm<S::Term, Nulls<T, P>>, S::Error> {
863        self.encrypt_opened(&self.sentinel())
864    }
865}
866
867impl<S: Supports<OrderTarget> + ?Sized> OrderSpace<'_, S, RuntimeNulls> {
868    /// The term of a null in this space, for a target chosen while the
869    /// program runs: its sentinel, encrypted. The same as
870    /// [`encrypt_checked`](OrderSpace::encrypt_checked) of any null.
871    ///
872    /// # Errors
873    ///
874    /// [`OrderError::Unsupported`] if the scheme does not take this
875    /// target, [`OrderError::Backend`] if the scheme failed.
876    #[allow(clippy::type_complexity)]
877    pub fn encrypt_null(&self) -> Result<OrderTerm<S::Term, RuntimeNulls>, OrderError<S::Error>> {
878        self.supported()?;
879        self.encrypt_plaintext(&self.sentinel())
880    }
881}
882
883/// One term of an order space: the scheme's term, labelled with the target
884/// it was made under.
885///
886/// Under a marker from [`target`](crate::target) the label is a zero-sized
887/// type, so two terms of different targets cannot meet in
888/// [`OrderSpace::compare`]: that does not compile, and nor does a term of a
889/// space that places nulls in a plain space's comparison, or a term of a
890/// nulls-first space in a nulls-last space's, since the policy is part of
891/// the [`Nulls`] label's type. Under an [`OrderTarget`] the label is
892/// stored, and two of different targets compare as `None` instead of as
893/// noise. A [`RuntimeNulls`] label stores its policy beside its target,
894/// so a nulls-first and a nulls-last term of it compare as `None` too. Storage reads
895/// the scheme's bytes through `AsRef<[u8]>`, and [`raw`](Self::raw) is the
896/// scheme's term itself.
897///
898/// `==` compares the stored term, byte for byte, not the value: the order
899/// of two values is [`OrderSpace::compare`], and a randomised scheme
900/// (block ORE carries a nonce) gives one value many terms, which compare
901/// `Equal` and are not `==`.
902#[derive(Debug, Clone, PartialEq, Eq)]
903pub struct OrderTerm<R, T: Target> {
904    raw: R,
905    pub(crate) target: T,
906}
907
908impl<R, T: Target> OrderTerm<R, T> {
909    /// The target the term was made under.
910    pub fn target(&self) -> OrderTarget {
911        self.target.runtime()
912    }
913
914    /// Where the space the term was made in places nulls, or `None` for a
915    /// space that gives them no term.
916    pub fn null_order(&self) -> Option<NullOrder> {
917        self.target.null_order()
918    }
919
920    /// The scheme's own term.
921    pub fn raw(&self) -> &R {
922        &self.raw
923    }
924
925    /// The scheme's own term, unlabelled.
926    pub fn into_raw(self) -> R {
927        self.raw
928    }
929}
930
931impl<R: AsRef<[u8]>, T: Target> OrderTerm<R, T> {
932    /// The stored bytes: the scheme's term's.
933    pub fn as_bytes(&self) -> &[u8] {
934        self.raw.as_ref()
935    }
936}
937
938impl<R: AsRef<[u8]>, T: Target> AsRef<[u8]> for OrderTerm<R, T> {
939    fn as_ref(&self) -> &[u8] {
940        self.raw.as_ref()
941    }
942}
943
944/// A scheme keyed from secret bytes, as
945/// [`PrfKeyInit`](https://docs.rs/vitaminc-prf) keys a PRF, so an index
946/// layer can construct any scheme the same way.
947pub trait OrderKeyInit: OrderScheme + Sized {
948    /// The scheme's full-strength key, a protected array.
949    type Key: Controlled;
950    /// Why key material of a length only known at runtime was refused.
951    type KeyError: core::error::Error + Send + Sync + 'static;
952
953    /// Key the scheme with a full-strength key. Cannot fail: the type
954    /// carries the length.
955    fn new(key: Self::Key) -> Self;
956
957    /// Key the scheme with material whose length is only known at runtime,
958    /// such as a KMS response.
959    ///
960    /// # Errors
961    ///
962    /// [`KeyError`](OrderKeyInit::KeyError) if the material is unfit, a
963    /// short key say. Accepting it would produce terms anyone can order.
964    fn try_from_bytes(key: Protected<Vec<u8>>) -> Result<Self, Self::KeyError>;
965}
966
967/// The sink one scalar is walked into: it encodes the scalar as its
968/// space's target, under the space's null policy, and refuses a
969/// container.
970#[derive(Clone, Copy)]
971pub(crate) struct Scalar {
972    target: OrderTarget,
973    nulls: Option<NullOrder>,
974}
975
976impl Scalar {
977    /// The sink for a space of `target`.
978    pub(crate) fn of<T: Target>(target: T) -> Self {
979        Self {
980            target: target.runtime(),
981            nulls: target.null_order(),
982        }
983    }
984}
985
986/// How one scalar fails to become a plaintext.
987#[derive(Debug, thiserror::Error)]
988pub(crate) enum ScalarError {
989    #[error(transparent)]
990    Term(NormalizeError),
991    #[error("not a scalar")]
992    NotScalar,
993    #[error(transparent)]
994    Source(SourceError),
995}
996
997impl plaintext::Error for ScalarError {
998    fn source(error: SourceError) -> Self {
999        Self::Source(error)
1000    }
1001}
1002
1003impl<E> From<ScalarError> for OrderError<E> {
1004    fn from(error: ScalarError) -> Self {
1005        match error {
1006            ScalarError::Term(e) => Self::Term(e),
1007            ScalarError::NotScalar => Self::NotScalar,
1008            ScalarError::Source(e) => Self::Source(e),
1009        }
1010    }
1011}
1012
1013impl Sink for Scalar {
1014    type Ok = OrderPlaintext;
1015    type Error = ScalarError;
1016    type Seq = Never;
1017    type Map = Never;
1018
1019    fn scalar(
1020        self,
1021        ty: Option<Logical>,
1022        value: Physical<'_>,
1023    ) -> Result<OrderPlaintext, ScalarError> {
1024        encode(self.target, self.nulls, ty, value).map_err(ScalarError::Term)
1025    }
1026
1027    /// The same as a plain null: the space's sentinel in a space that
1028    /// places nulls, and no term in one that does not. The sentinel is the
1029    /// extreme code of the space's target, so the null's own type adds
1030    /// nothing.
1031    fn typed_null(self, _: FixedType) -> Result<OrderPlaintext, ScalarError> {
1032        self.scalar(None, Physical::Null)
1033    }
1034
1035    fn seq(self, _: Option<usize>) -> Result<Never, ScalarError> {
1036        Err(ScalarError::NotScalar)
1037    }
1038
1039    fn map(self, _: Option<usize>) -> Result<Never, ScalarError> {
1040        Err(ScalarError::NotScalar)
1041    }
1042
1043    fn passthrough<P: Encrypt + Clone>(self, _: P) -> Result<OrderPlaintext, ScalarError> {
1044        Err(ScalarError::NotScalar)
1045    }
1046}
1047
1048/// The container sink [`Scalar`] never hands out. Uninhabited, so it
1049/// cannot be built.
1050pub(crate) enum Never {}
1051
1052impl SeqSink for Never {
1053    type Ok = OrderPlaintext;
1054    type Error = ScalarError;
1055    type Element<'s>
1056        = Scalar
1057    where
1058        Self: 's;
1059
1060    fn element(&mut self) -> Result<Scalar, ScalarError> {
1061        match *self {}
1062    }
1063
1064    fn end(self) -> Result<OrderPlaintext, ScalarError> {
1065        match self {}
1066    }
1067}
1068
1069impl MapSink for Never {
1070    type Ok = OrderPlaintext;
1071    type Error = ScalarError;
1072    type Entry<'s>
1073        = Scalar
1074    where
1075        Self: 's;
1076
1077    fn entry(&mut self, _: &str) -> Result<Scalar, ScalarError> {
1078        match *self {}
1079    }
1080
1081    fn end(self) -> Result<OrderPlaintext, ScalarError> {
1082        match self {}
1083    }
1084}
1085
1086impl fmt::Display for OrderPlaintext {
1087    /// The domain only: never the bytes.
1088    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1089        f.write_str(self.domain())
1090    }
1091}
1092
1093#[cfg(test)]
1094mod tests {
1095    use super::*;
1096    use crate::{domain, order};
1097    use vitaminc_aead::Int;
1098
1099    #[test]
1100    fn every_target_has_the_width_its_encoding_has() {
1101        for target in OrderTarget::ALL {
1102            let variable = matches!(target, OrderTarget::Text | OrderTarget::Bytes);
1103            assert_eq!(target.width().is_none(), variable, "{target:?}");
1104        }
1105        let p = order(OrderTarget::I32, None, Physical::Int(Int::I8(1))).expect("term");
1106        assert_eq!(p.width(), Some(4));
1107        assert_eq!(p.bytes().risky_ref().len(), 4);
1108        assert_eq!(p.target(), OrderTarget::I32);
1109        assert_eq!(
1110            order(OrderTarget::Text, None, Physical::Text("a"))
1111                .expect("term")
1112                .width(),
1113            None
1114        );
1115    }
1116
1117    #[test]
1118    fn the_salt_binds_label_scheme_domain_and_context() {
1119        let context = ("tenant", 1u64).into_context().encode();
1120        let salted = salt("mock", domain::INT16, &context);
1121        assert_eq!(
1122            salted,
1123            Context::pae(&[
1124                b"vitaminc/ore/v1",
1125                b"mock",
1126                domain::INT16.as_bytes(),
1127                context.as_bytes()
1128            ])
1129        );
1130        assert_ne!(salted, salt("other", domain::INT16, &context));
1131        assert_ne!(salted, salt("mock", domain::UINT16, &context));
1132        assert_ne!(
1133            salted,
1134            salt(
1135                "mock",
1136                domain::INT16,
1137                &("tenant", 2u64).into_context().encode()
1138            )
1139        );
1140        // A null policy is its own label, so its own salt.
1141        assert_ne!(salted, salt("mock", domain::INT16_NULLS_FIRST, &context));
1142        assert_ne!(salted, salt("mock", domain::INT16_NULLS_LAST, &context));
1143    }
1144
1145    #[test]
1146    fn debug_and_display_show_the_domain_and_mask_the_bytes() {
1147        let p = order(OrderTarget::Text, None, Physical::Text("secret")).expect("term");
1148        let shown = format!("{p:?}");
1149        assert!(shown.contains("Text") && shown.contains("***"), "{shown}");
1150        assert!(!shown.contains("secret"), "{shown}");
1151        assert_eq!(p.to_string(), domain::TEXT);
1152    }
1153
1154    #[test]
1155    fn errors_say_what_failed_without_plaintext() {
1156        type E = OrderError<&'static str>;
1157        assert_eq!(
1158            E::Term(NormalizeError::NoDomain).to_string(),
1159            "no order term: no domain for this kind of value"
1160        );
1161        assert_eq!(
1162            E::Term(NormalizeError::Mismatch).to_string(),
1163            "no order term: the value is not of the kind the index declares"
1164        );
1165        assert_eq!(
1166            E::Term(NormalizeError::OutOfRange).to_string(),
1167            "no order term: the value does not fit the type the index declares"
1168        );
1169        assert_eq!(
1170            E::NotScalar.to_string(),
1171            "a container has no single order term; walk it for one per scalar"
1172        );
1173        assert_eq!(
1174            E::Source(SourceError::msg("getter threw")).to_string(),
1175            "the value could not be read: getter threw"
1176        );
1177        assert_eq!(
1178            E::Unsupported {
1179                scheme: "block-ore",
1180                domain: domain::TEXT
1181            }
1182            .to_string(),
1183            "block-ore has no order term for vitaminc/ore/value/text-folded/unicode-16/v1"
1184        );
1185        assert_eq!(
1186            E::Backend("down").to_string(),
1187            "the order scheme failed: down"
1188        );
1189        let from: E = NormalizeError::Invalid.into();
1190        assert!(matches!(from, E::Term(NormalizeError::Invalid)));
1191    }
1192
1193    #[test]
1194    fn the_scalar_sink_encodes_one_scalar_as_its_target_and_refuses_a_container() {
1195        let p = 7i16.visit(Scalar::of(OrderTarget::I64)).expect("scalar");
1196        assert_eq!(p.domain(), domain::INT64);
1197        assert!(matches!(
1198            vec![1u16, 2].visit(Scalar::of(OrderTarget::U16)),
1199            Err(ScalarError::NotScalar)
1200        ));
1201        assert!(matches!(
1202            std::collections::BTreeMap::from([("k", 1u16)]).visit(Scalar::of(OrderTarget::U16)),
1203            Err(ScalarError::NotScalar)
1204        ));
1205        assert!(matches!(
1206            true.visit(Scalar::of(OrderTarget::U16)),
1207            Err(ScalarError::Term(NormalizeError::NoDomain))
1208        ));
1209    }
1210
1211    /// A fixed-width `None` reaches the sink as a typed null. In a space
1212    /// with no null policy it has no order term, as a plain null has none;
1213    /// in a space that places nulls it is the space's sentinel.
1214    #[test]
1215    fn the_scalar_sink_gives_a_typed_null_no_term_without_a_policy_and_the_sentinel_with_one() {
1216        let plain = Scalar::of(OrderTarget::U32);
1217        assert!(
1218            matches!(
1219                None::<u32>.visit(plain),
1220                Err(ScalarError::Term(NormalizeError::NoDomain))
1221            ),
1222            "a typed null must have no order term in a plain space"
1223        );
1224        assert!(
1225            matches!(
1226                None::<u32>.visit(Scalar::of(OrderTarget::Text)),
1227                Err(ScalarError::Term(NormalizeError::NoDomain))
1228            ),
1229            "the target does not give a typed null a term either"
1230        );
1231        for (nulls, fill, label) in [
1232            (NullOrder::First, 0x00, domain::UINT32_NULLS_FIRST),
1233            (NullOrder::Last, 0xFF, domain::UINT32_NULLS_LAST),
1234        ] {
1235            let placed = Scalar::of(RuntimeNulls::new(OrderTarget::U32, nulls));
1236            let typed = None::<u32>
1237                .visit(placed)
1238                .expect("a typed null is the sentinel");
1239            assert_eq!(
1240                typed.domain(),
1241                label,
1242                "the sentinel is under the policy's label"
1243            );
1244            assert_eq!(
1245                typed.null_order(),
1246                Some(nulls),
1247                "the plaintext carries its policy"
1248            );
1249            assert_eq!(
1250                typed.bytes().risky_ref(),
1251                &[fill; 4],
1252                "a typed null is the sentinel's bytes"
1253            );
1254            // The null's own type adds nothing: an `i16` null in a `u32`
1255            // space is the same sentinel, and so is a plain null.
1256            let other = None::<i16>.visit(placed).expect("sentinel");
1257            let raw = vitaminc_aead::Raw(None, Physical::Null)
1258                .visit(placed)
1259                .expect("sentinel");
1260            assert_eq!(other.bytes().risky_ref(), typed.bytes().risky_ref());
1261            assert_eq!(raw.bytes().risky_ref(), typed.bytes().risky_ref());
1262        }
1263    }
1264}