Skip to main content

orderable_bytes/
lib.rs

1#![deny(missing_docs)]
2// Library code does not panic: it returns an error or carries the invariant
3// in a type. Tests may. Clippy has no lint for `assert!`;
4// `scripts/no-runtime-asserts.py` covers that in CI.
5#![cfg_attr(
6    not(test),
7    deny(
8        clippy::panic,
9        clippy::unwrap_used,
10        clippy::expect_used,
11        clippy::unreachable,
12        clippy::todo,
13        clippy::unimplemented,
14        clippy::panic_in_result_fn
15    )
16)]
17//! Canonical, order-preserving byte encodings for plaintext types.
18//!
19//! Each supported type implements [`OrderableBytes`], which maps a
20//! value to bytes whose byte-wise lexicographic order agrees with the
21//! type's natural total order, and whose byte equality agrees with the
22//! type's value equality. The resulting bytes are scheme-agnostic —
23//! they're intended for any comparison-as-bytes scheme that wants to
24//! preserve plaintext order on ciphertexts (e.g. `ore-rs` BlockORE, an
25//! OPE construction, an ordered hash).
26//!
27//! Every type also implements exactly one of two subtraits, which say
28//! how its bytes may be handled:
29//!
30//! - [`FixedOrderableBytes`]: every value encodes to the same length,
31//!   [`ENCODED_LEN`](FixedOrderableBytes::ENCODED_LEN), available as an
32//!   owned array. Numbers, `bool`, `char`, byte arrays `[u8; N]`,
33//!   `Decimal` and the `chrono` types.
34//! - [`VariableOrderableBytes`]: the length varies with the value, and
35//!   the bytes are borrowed from it. Strings and byte strings (see
36//!   [`variable`]).
37//!
38//! A reference `&T` implements the same traits as `T`.
39//!
40//! | Need | Bound | Call |
41//! |---|---|---|
42//! | Pad, store or return fixed-length bytes | `T: FixedOrderableBytes` | [`to_fixed_orderable_bytes`](FixedOrderableBytes::to_fixed_orderable_bytes) (owned) |
43//! | Bytes of either kind, used immediately | `T: OrderableBytes` | [`to_orderable_bytes`](OrderableBytes::to_orderable_bytes) (may borrow) |
44//! | Strings or byte strings specifically | `T: VariableOrderableBytes` | [`to_orderable_bytes`](OrderableBytes::to_orderable_bytes) (borrowed) |
45//!
46//! Padding to a block size is only safe for [`FixedOrderableBytes`];
47//! bound on [`OrderableBytes`] only when you handle both kinds correctly.
48//!
49//! The traits are sealed: only this crate implements them, so every
50//! encoding is pinned by its golden vectors, and the rules above hold
51//! for every type that implements them.
52//!
53//! Encoders are gated behind per-type feature flags so callers only pay
54//! for the dependencies they actually use.
55
56/// Implements [`OrderableBytes`] and [`FixedOrderableBytes`] for a type
57/// whose encoding is always `$len` bytes, from one body that builds the
58/// array. Writing both impls from one length keeps
59/// [`FixedOrderableBytes::ENCODED_LEN`], [`FixedOrderableBytes::Array`]
60/// and [`OrderableBytes::Bytes`] in agreement.
61macro_rules! impl_fixed_orderable_bytes {
62    ($(#[$attr:meta])* $ty:ty, $len:literal, |$value:ident| $body:block) => {
63        impl $crate::private::Sealed for $ty {}
64
65        impl $crate::OrderableBytes for $ty {
66            type Bytes<'a> = [u8; $len];
67
68            fn to_orderable_bytes(&self) -> [u8; $len] {
69                $crate::FixedOrderableBytes::to_fixed_orderable_bytes(self)
70            }
71        }
72
73        $(#[$attr])*
74        impl $crate::FixedOrderableBytes for $ty {
75            const ENCODED_LEN: usize = $len;
76
77            type Array = [u8; $len];
78
79            fn to_fixed_orderable_bytes(&self) -> [u8; $len] {
80                let $value = self;
81                $body
82            }
83        }
84    };
85}
86
87#[cfg(feature = "chrono")]
88pub mod chrono;
89#[cfg(feature = "decimal")]
90pub mod decimal;
91pub mod primitive;
92pub mod variable;
93
94#[cfg(test)]
95#[macro_use]
96extern crate quickcheck;
97
98// Runs the README's Rust examples as doctests, so the README cannot drift
99// from the API. The examples use no optional feature, so they run under
100// a plain `cargo test` as well as with `--all-features`.
101#[cfg(doctest)]
102#[doc = include_str!("../README.md")]
103struct ReadmeDoctests;
104
105mod private {
106    /// Restricts the traits below to this crate's types. See the crate
107    /// docs for why they are sealed.
108    pub trait Sealed {}
109}
110
111/// Maps a value to its canonical, order-preserving byte encoding.
112///
113/// Implementors guarantee, for any `a` and `b` of the implementing type:
114///
115/// - **Equality:** byte equality of the outputs agrees with the type's
116///   value equality (`a.to_orderable_bytes() == b.to_orderable_bytes()`
117///   iff `a == b`).
118/// - **Order:** byte-wise lexicographic comparison of the outputs agrees
119///   with the type's natural total order
120///   (`a.to_orderable_bytes() <= b.to_orderable_bytes()` iff `a <= b`).
121///
122/// Every implementor also implements exactly one of
123/// [`FixedOrderableBytes`] or [`VariableOrderableBytes`]. Code that
124/// needs a fixed length, such as code that pads to a block size, must
125/// bound on [`FixedOrderableBytes`], not on this trait.
126///
127/// The trait is sealed, so types outside this crate can't implement it:
128///
129/// ```compile_fail,E0277
130/// use orderable_bytes::OrderableBytes;
131///
132/// struct Mine(u32);
133///
134/// impl OrderableBytes for Mine {
135///     type Bytes<'a> = [u8; 4];
136///     fn to_orderable_bytes(&self) -> [u8; 4] {
137///         self.0.to_be_bytes()
138///     }
139/// }
140/// ```
141///
142/// In 0.1 this trait was called `ToOrderableBytes` and was fixed-length
143/// only. It was renamed so that 0.1 code bounded on it fails to compile
144/// rather than silently accepting variable-length types: migrate such
145/// bounds to [`FixedOrderableBytes`].
146pub trait OrderableBytes: private::Sealed {
147    /// The bytes returned by
148    /// [`to_orderable_bytes`](Self::to_orderable_bytes): a
149    /// `[u8; ENCODED_LEN]` array for fixed-length types, or a slice
150    /// borrowed from `self` for variable-length ones, so encoding a
151    /// string makes no copy of it.
152    ///
153    /// Because it may borrow from `self`, it can't outlive the value.
154    /// To keep fixed-length bytes, use
155    /// [`FixedOrderableBytes::to_fixed_orderable_bytes`].
156    type Bytes<'a>: AsRef<[u8]>
157    where
158        Self: 'a;
159
160    /// Build the canonical, order-preserving byte encoding of `self`.
161    fn to_orderable_bytes(&self) -> Self::Bytes<'_>;
162}
163
164/// An [`OrderableBytes`] encoding whose length is the same for every
165/// value of the type.
166///
167/// [`to_fixed_orderable_bytes`](Self::to_fixed_orderable_bytes) returns
168/// the same bytes as
169/// [`to_orderable_bytes`](OrderableBytes::to_orderable_bytes), as an
170/// owned [`Array`](Self::Array) of exactly
171/// [`ENCODED_LEN`](Self::ENCODED_LEN) bytes that doesn't borrow from the
172/// value. Generic code can store or return it:
173///
174/// ```
175/// use orderable_bytes::FixedOrderableBytes;
176///
177/// fn term<T: FixedOrderableBytes>(value: T) -> T::Array {
178///     value.to_fixed_orderable_bytes()
179/// }
180///
181/// assert_eq!(term(7u32), [0, 0, 0, 7]);
182/// ```
183///
184/// Fixed-length encodings may be zero-extended to a larger block, since
185/// every value is extended the same way. Bounding on this trait is what
186/// keeps variable-length types out of such code:
187///
188/// ```
189/// use orderable_bytes::FixedOrderableBytes;
190///
191/// fn pad_to_16<T: FixedOrderableBytes>(value: &T) -> [u8; 16] {
192///     let mut block = [0u8; 16];
193///     block[..T::ENCODED_LEN].copy_from_slice(value.to_fixed_orderable_bytes().as_ref());
194///     block
195/// }
196///
197/// assert!(pad_to_16(&1u32) < pad_to_16(&2u32));
198/// ```
199///
200/// ```compile_fail,E0277
201/// # use orderable_bytes::FixedOrderableBytes;
202/// # fn pad_to_16<T: FixedOrderableBytes>(value: &T) -> [u8; 16] {
203/// #     let mut block = [0u8; 16];
204/// #     block[..T::ENCODED_LEN].copy_from_slice(value.to_fixed_orderable_bytes().as_ref());
205/// #     block
206/// # }
207/// // Strings are variable-length, so padding them is rejected.
208/// pad_to_16(&String::from("a"));
209/// ```
210pub trait FixedOrderableBytes: OrderableBytes {
211    /// Length, in bytes, of the canonical encoding.
212    const ENCODED_LEN: usize;
213
214    /// The owned encoding: always `[u8; ENCODED_LEN]`.
215    type Array: AsRef<[u8]> + Copy;
216
217    /// Build the canonical, order-preserving byte encoding of `self` as
218    /// an owned array. The bytes are the same as
219    /// [`to_orderable_bytes`](OrderableBytes::to_orderable_bytes) returns.
220    fn to_fixed_orderable_bytes(&self) -> Self::Array;
221}
222
223/// An [`OrderableBytes`] encoding whose length varies with the value.
224///
225/// A value sorts before any longer value it is a prefix of. The bytes do
226/// not mark where they end, so they must never be padded or
227/// concatenated: see [`variable`] for why, and for the implementations.
228pub trait VariableOrderableBytes: OrderableBytes {}
229
230impl<T: OrderableBytes + ?Sized> private::Sealed for &T {}
231
232/// A reference encodes exactly as the value it points to.
233impl<T: OrderableBytes + ?Sized> OrderableBytes for &T {
234    type Bytes<'a>
235        = T::Bytes<'a>
236    where
237        Self: 'a;
238
239    fn to_orderable_bytes(&self) -> T::Bytes<'_> {
240        (**self).to_orderable_bytes()
241    }
242}
243
244impl<T: FixedOrderableBytes + ?Sized> FixedOrderableBytes for &T {
245    const ENCODED_LEN: usize = T::ENCODED_LEN;
246
247    type Array = T::Array;
248
249    fn to_fixed_orderable_bytes(&self) -> T::Array {
250        (**self).to_fixed_orderable_bytes()
251    }
252}
253
254impl<T: VariableOrderableBytes + ?Sized> VariableOrderableBytes for &T {}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn a_reference_encodes_as_its_value() {
262        fn any<T: OrderableBytes>(value: T) -> Vec<u8> {
263            value.to_orderable_bytes().as_ref().to_vec()
264        }
265        fn fixed<T: FixedOrderableBytes>(value: T) -> T::Array {
266            value.to_fixed_orderable_bytes()
267        }
268        fn is_variable<T: VariableOrderableBytes>(_: T) {}
269
270        // The references are the point here, so bind them to names
271        // rather than borrowing inline.
272        let n = 7u32;
273        let s = String::from("abc");
274        let (n_ref, s_ref, str_ref_ref): (&u32, &String, &&str) = (&n, &s, &"abc");
275
276        assert_eq!(any("abc"), b"abc");
277        assert_eq!(any(str_ref_ref), b"abc");
278        assert_eq!(any(n_ref), [0, 0, 0, 7]);
279        assert_eq!(fixed(n_ref), [0, 0, 0, 7]);
280        assert_eq!(<&u32 as FixedOrderableBytes>::ENCODED_LEN, 4);
281        is_variable("abc");
282        is_variable(s_ref);
283    }
284}