Skip to main content

vitaminc_aead/
encrypt_sink.rs

1//! Encryption as a sink.
2//!
3//! [`EncryptSink`] turns each scalar into tagged leaf plaintext and owns
4//! every container rule in one place:
5//!
6//! - a sequence element is sealed under
7//!   [`Context::for_sequence_element`], a map value under
8//!   [`Context::for_map_entry`] of its key;
9//! - an empty sequence or map is an authenticated empty marker under
10//!   [`Context::for_empty_sequence`] or [`Context::for_empty_map`];
11//! - a passthrough subtree is framed, in the clear;
12//! - the value keeps the [`shape`](crate::shape) rules: no duplicate keys,
13//!   at least one leaf sealed in every container with children, no child
14//!   sink dropped without a value (as
15//!   [`FrameWriter`](crate::frame::FrameWriter) refuses it, so a tee of the
16//!   two agrees on every walk), and nothing deeper than
17//!   [`MAX_DEPTH`](crate::frame::MAX_DEPTH), counted as the transport
18//!   decoder counts it, so every ciphertext planned here can be read back.
19//!
20//! A refusal is a [`Refusal`]: the rule, and the
21//! [`Path`](crate::plaintext::Path) to the node that broke it. Each child
22//! sink holds only a borrow of its parent's place and its own index or
23//! key, so the path is built only when a value is refused.
24//!
25//! The sink never calls a cipher. It plans a tree of [`Pending`] leaves,
26//! each with its plaintext and the AAD its position derives, and
27//! [`seal_all`] or [`seal_all_async`] seals them afterwards. So the walk
28//! is synchronous whatever the cipher is, and terms collected by a sink
29//! teed with this one are complete before the first await.
30
31use core::future::Future;
32use core::marker::PhantomData;
33
34use vitaminc_protected::{Controlled, Protected};
35
36use crate::frame::PassthroughFrame;
37use crate::leaf::{self, ScalarError};
38use crate::plaintext::{
39    Encrypt, Error, FixedType, Logical, MapSink, Physical, SeqSink, Sink, SourceError, Step,
40};
41use crate::seal::{
42    seal_all, seal_all_async, AsyncCipher, Cipher, LeafCount, Node, Pending, SealError,
43};
44use crate::shape::{
45    self, refuse, Locate, MapRules, Refusal, Root, SeqRules, Settle, Unencryptable,
46};
47use crate::{CipherText, Context, IntoAad};
48
49/// Why encryption failed.
50///
51/// A cipher's own error is printed in the message rather than chained as
52/// the `source()`, so a cipher error type need not implement `Error`.
53///
54/// Every failure is an error, never a panic, including the ones that look
55/// like contract violations: a value too long for the frame's `u32` length
56/// prefixes ([`Unencryptable::Unwritable`],
57/// [`Unencryptable::UnframablePassthrough`]) and a cipher that returns the
58/// wrong number of leaves ([`EncryptError::Count`]). The value often comes
59/// from a host (a JavaScript object, a Go frame) and the cipher may be a
60/// remote key provider, so neither is under the caller's control. A panic
61/// there would let hostile input or a bad response abort the process: in
62/// the Node binding, a panic aborts Node.
63///
64/// A type that derives `Encrypt` cannot hit most of these. The derive
65/// refuses a duplicate key, a struct that seals nothing and a type too deep
66/// for [`MAX_DEPTH`](crate::frame::MAX_DEPTH) at compile time, and
67/// `Timestamp` (with the `chrono` feature) is a time
68/// that cannot be a leap second. What remains is a value deeper than
69/// `MAX_DEPTH` through a field whose type does not fix its depth (a
70/// recursive type, a frame, a hand-written source), a value too long for
71/// the frame, a source's own error (such as a leap second in a
72/// `DateTime<Utc>` field, or a passthrough frame that does not parse), a
73/// `Raw` leaf whose value does not fit its logical type, and the cipher's
74/// own errors.
75#[derive(Debug, thiserror::Error)]
76pub enum EncryptError<E> {
77    /// The cipher refused to seal a leaf.
78    #[error("cipher failed: {0}")]
79    Cipher(E),
80    /// The source stopped the walk, or a sink teed beside this one did
81    /// (an index sink refusing a value, say); the error inside says which.
82    #[error("source failed: {0}")]
83    Source(#[source] SourceError),
84    /// The value cannot be encrypted as given: why, and where in the
85    /// value. See [`Refusal`].
86    #[error("cannot encrypt: {0}")]
87    Shape(Refusal),
88    /// The cipher returned the wrong number of leaves: a cipher bug, not a
89    /// problem with the value.
90    #[error("cipher returned {0}")]
91    Count(LeafCount),
92}
93
94impl<E> Error for EncryptError<E> {
95    fn source(error: SourceError) -> Self {
96        Self::Source(error)
97    }
98}
99
100impl<E> From<Refusal> for EncryptError<E> {
101    fn from(refusal: Refusal) -> Self {
102        Self::Shape(refusal)
103    }
104}
105
106impl<E> From<SealError<E>> for EncryptError<E> {
107    fn from(error: SealError<E>) -> Self {
108        match error {
109            SealError::Cipher(e) => Self::Cipher(e),
110            SealError::Count(count) => Self::Count(count),
111        }
112    }
113}
114
115/// Walk a source into a tree of pending leaves. No cipher is involved:
116/// this is the whole of the data-model work, and it is synchronous.
117pub fn plan<'a, P, E, A>(value: P, aad: A) -> Result<Node<Pending>, EncryptError<E>>
118where
119    P: Encrypt,
120    A: IntoAad<'a>,
121{
122    value.visit(EncryptSink::root(aad))
123}
124
125/// Encrypt any source.
126pub fn encrypt<'a, P, C, A>(
127    value: P,
128    cipher: &C,
129    aad: A,
130) -> Result<Node<C::Leaf>, EncryptError<C::Error>>
131where
132    P: Encrypt,
133    C: Cipher,
134    A: IntoAad<'a>,
135{
136    Ok(seal_all(cipher, plan(value, aad)?)?)
137}
138
139/// Encrypt any source through an async cipher. A cipher with a batch API
140/// seals the document in one round trip.
141///
142/// The walk happens here, before this returns, and the future holds only
143/// the planned leaves. So the future is `Send` even when the source is not
144/// (a JavaScript object behind a host binding).
145pub fn encrypt_async<'a, 'c, P, C, A>(
146    value: P,
147    cipher: &'c C,
148    aad: A,
149) -> impl Future<Output = Result<Node<C::Leaf>, EncryptError<C::Error>>> + Send + 'c
150where
151    P: Encrypt,
152    C: AsyncCipher,
153    A: IntoAad<'a>,
154{
155    let planned = plan(value, aad);
156    async move { Ok(seal_all_async(cipher, planned?).await?) }
157}
158
159/// Where a finished node goes: back to the caller, or into a parent. It
160/// also knows where the node sits, for a refusal.
161pub trait Slot: Locate {
162    type Ok;
163    fn put(self, node: Node<Pending>) -> Self::Ok;
164}
165
166impl Slot for Root {
167    type Ok = Node<Pending>;
168    fn put(self, node: Node<Pending>) -> Node<Pending> {
169        node
170    }
171}
172
173/// Tell a container's rules how its child ended.
174fn settle(settle: Settle<'_>, node: &Node<Pending>) {
175    if matches!(node, CipherText::Passthrough(_)) {
176        settle.passthrough();
177    } else {
178        settle.sealed();
179    }
180}
181
182/// An element slot in a parent sequence: the parent's items, this
183/// element's hold on the parent's rules, and the parent's place.
184pub struct Push<'s> {
185    items: &'s mut Vec<Node<Pending>>,
186    settle: Settle<'s>,
187    parent: &'s dyn Locate,
188}
189
190impl Locate for Push<'_> {
191    fn locate(&self, steps: &mut Vec<Step>) {
192        self.settle.locate_under(self.parent, steps);
193    }
194}
195
196impl Slot for Push<'_> {
197    type Ok = ();
198    fn put(self, node: Node<Pending>) {
199        settle(self.settle, &node);
200        self.items.push(node);
201    }
202}
203
204/// An entry slot in a parent map: the parent's entries, this entry's hold
205/// on the parent's rules, which also lends it the key, and the parent's
206/// place.
207pub struct Entry<'s> {
208    entries: &'s mut Vec<(String, Node<Pending>)>,
209    settle: Settle<'s>,
210    parent: &'s dyn Locate,
211}
212
213impl Locate for Entry<'_> {
214    fn locate(&self, steps: &mut Vec<Step>) {
215        self.settle.locate_under(self.parent, steps);
216    }
217}
218
219impl Slot for Entry<'_> {
220    type Ok = ();
221    /// The key stays in the parent's rules until the map ends, and the
222    /// entry holds an empty string in its place, which does not allocate.
223    fn put(self, node: Node<Pending>) {
224        settle(self.settle, &node);
225        self.entries.push((String::new(), node));
226    }
227}
228
229/// The encrypting sink.
230///
231/// `E` is the cipher's error type, so this sink can be teed with others
232/// before any cipher is involved; the sink itself never produces
233/// `EncryptError::Cipher`.
234pub struct EncryptSink<E, O = Root> {
235    aad: Context<'static>,
236    out: O,
237    /// How far below the root this node sits.
238    depth: usize,
239    _error: PhantomData<E>,
240}
241
242impl<E> EncryptSink<E, Root> {
243    /// A sink for a whole value under `aad`. Its `Ok` is the planned tree.
244    pub fn root<'a>(aad: impl IntoAad<'a>) -> Self {
245        Self {
246            aad: aad.into_aad().into_owned(),
247            out: Root,
248            depth: 0,
249            _error: PhantomData,
250        }
251    }
252}
253
254impl<E, O: Slot> EncryptSink<E, O> {
255    /// Plan one scalar as one leaf: its tag, then its payload.
256    fn leaf(self, ty: Option<Logical>, v: Physical<'_>) -> Result<O::Ok, EncryptError<E>> {
257        shape::scalar(self.depth, ty, &v, &self.out)?;
258        // Sized once, so the buffer never regrows and leaves a copy behind.
259        let mut plaintext = Protected::new(Vec::with_capacity(leaf::max_len(&v)));
260        leaf::write_leaf(ty, v, plaintext.risky_inner_mut()).map_err(|e| {
261            refuse(
262                &self.out,
263                match e {
264                    ScalarError::Invalid => Unencryptable::InvalidValue,
265                    ScalarError::TooLong | ScalarError::Malformed => Unencryptable::Unwritable,
266                },
267            )
268        })?;
269        Ok(self.out.put(CipherText::Single(Pending {
270            plaintext,
271            aad: self.aad,
272        })))
273    }
274
275    /// Plan an absent value of a fixed-width type as one leaf: a typed
276    /// null, as long as a leaf holding a value of the type.
277    fn typed_null_leaf(self, ty: FixedType) -> Result<O::Ok, EncryptError<E>> {
278        shape::scalar(self.depth, None, &Physical::Null, &self.out)?;
279        let mut plaintext = Protected::new(Vec::with_capacity(leaf::typed_null_len(ty)));
280        leaf::write_typed_null(ty, plaintext.risky_inner_mut());
281        Ok(self.out.put(CipherText::Single(Pending {
282            plaintext,
283            aad: self.aad,
284        })))
285    }
286}
287
288fn marker(aad: Context<'static>) -> Pending {
289    Pending {
290        plaintext: Protected::new(Vec::new()),
291        aad,
292    }
293}
294
295impl<E, O: Slot> Sink for EncryptSink<E, O> {
296    type Ok = O::Ok;
297    type Error = EncryptError<E>;
298    type Seq = EncryptSeq<E, O>;
299    type Map = EncryptMap<E, O>;
300
301    fn scalar(self, ty: Option<Logical>, v: Physical<'_>) -> Result<O::Ok, Self::Error> {
302        self.leaf(ty, v)
303    }
304
305    fn typed_null(self, ty: FixedType) -> Result<O::Ok, Self::Error> {
306        self.typed_null_leaf(ty)
307    }
308
309    fn seq(self, len: Option<usize>) -> Result<Self::Seq, Self::Error> {
310        shape::container(self.depth, &self.out)?;
311        Ok(EncryptSeq {
312            element_aad: self.aad.for_sequence_element(),
313            aad: self.aad,
314            items: Vec::with_capacity(len.unwrap_or(0).min(1024)),
315            rules: SeqRules::new(),
316            out: self.out,
317            depth: self.depth,
318            _error: PhantomData,
319        })
320    }
321
322    fn map(self, _len: Option<usize>) -> Result<Self::Map, Self::Error> {
323        shape::container(self.depth, &self.out)?;
324        Ok(EncryptMap {
325            aad: self.aad,
326            entries: Vec::new(),
327            rules: MapRules::new(),
328            out: self.out,
329            depth: self.depth,
330            _error: PhantomData,
331        })
332    }
333
334    /// Passthrough is non-sensitive by design: it is framed, not sealed.
335    fn passthrough<P: Encrypt + Clone>(self, value: P) -> Result<O::Ok, Self::Error> {
336        let frame = shape::frame_passthrough(value, self.depth, &self.out, EncryptError::Shape)?;
337        Ok(self.out.put(CipherText::Passthrough(PassthroughFrame(
338            frame.risky_ref().to_vec(),
339        ))))
340    }
341}
342
343pub struct EncryptSeq<E, O> {
344    aad: Context<'static>,
345    element_aad: Context<'static>,
346    items: Vec<Node<Pending>>,
347    rules: SeqRules,
348    out: O,
349    depth: usize,
350    _error: PhantomData<E>,
351}
352
353impl<E, O: Slot> SeqSink for EncryptSeq<E, O> {
354    type Ok = O::Ok;
355    type Error = EncryptError<E>;
356    type Element<'s>
357        = EncryptSink<E, Push<'s>>
358    where
359        Self: 's;
360
361    fn element(&mut self) -> Result<Self::Element<'_>, Self::Error> {
362        Ok(EncryptSink {
363            aad: self.element_aad.clone(),
364            out: Push {
365                settle: self.rules.element(&self.out)?,
366                items: &mut self.items,
367                parent: &self.out,
368            },
369            depth: self.depth + 1,
370            _error: PhantomData,
371        })
372    }
373
374    fn end(self) -> Result<O::Ok, Self::Error> {
375        self.rules.end(&self.out)?;
376        let node = if self.items.is_empty() {
377            CipherText::EmptySequence(marker(self.aad.for_empty_sequence()))
378        } else {
379            CipherText::Sequence(self.items)
380        };
381        Ok(self.out.put(node))
382    }
383}
384
385pub struct EncryptMap<E, O> {
386    aad: Context<'static>,
387    /// Each entry's node, in order, with an empty key until the map ends.
388    entries: Vec<(String, Node<Pending>)>,
389    /// The only copy of each key, which `end` moves into `entries`.
390    rules: MapRules,
391    out: O,
392    depth: usize,
393    _error: PhantomData<E>,
394}
395
396impl<E, O: Slot> MapSink for EncryptMap<E, O> {
397    type Ok = O::Ok;
398    type Error = EncryptError<E>;
399    type Entry<'s>
400        = EncryptSink<E, Entry<'s>>
401    where
402        Self: 's;
403
404    fn entry(&mut self, key: &str) -> Result<Self::Entry<'_>, Self::Error> {
405        Ok(EncryptSink {
406            aad: self.aad.for_map_entry(key),
407            out: Entry {
408                settle: self.rules.entry(key, &self.out)?,
409                entries: &mut self.entries,
410                parent: &self.out,
411            },
412            depth: self.depth + 1,
413            _error: PhantomData,
414        })
415    }
416
417    fn end(mut self) -> Result<O::Ok, Self::Error> {
418        self.rules.end(&self.out)?;
419        for (key, at) in self.rules.into_keys() {
420            if let Some((slot, _)) = self.entries.get_mut(at) {
421                *slot = key;
422            }
423        }
424        let node = if self.entries.is_empty() {
425            CipherText::EmptyMap(marker(self.aad.for_empty_map()))
426        } else {
427            CipherText::Map(self.entries)
428        };
429        Ok(self.out.put(node))
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436    use crate::frame::{FrameWriteError, MAX_DEPTH};
437    use crate::plaintext::{render, visit_seq, Int, Raw};
438    use crate::seal::leaves_with_aad;
439    use crate::seal::mock::{Mock, Refused};
440    use crate::Tee;
441    use std::collections::BTreeMap;
442
443    #[test]
444    fn a_source_error_inside_a_passthrough_stays_a_source_error() {
445        #[derive(Clone)]
446        struct Throws;
447        impl Encrypt for Throws {
448            fn visit<S: Sink>(self, _: S) -> Result<S::Ok, S::Error> {
449                Err(S::Error::source(SourceError::new(core::fmt::Error)))
450            }
451        }
452        struct Doc;
453        impl Encrypt for Doc {
454            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
455                sink.passthrough(Throws)
456            }
457        }
458        assert!(matches!(
459            plan::<_, (), _>(Doc, "ctx"),
460            Err(EncryptError::Source(_))
461        ));
462    }
463
464    fn planned<P: Encrypt>(p: P) -> Result<Node<Pending>, EncryptError<Refused>> {
465        plan(p, "ctx")
466    }
467
468    /// Why a plan was refused and where, the path written out by
469    /// [`render`], or `None` if it was not refused for its shape.
470    fn refusal<T, E>(planned: Result<T, EncryptError<E>>) -> Option<(Unencryptable, String)> {
471        match planned {
472            Err(EncryptError::Shape(refusal)) => Some((refusal.reason(), render(refusal.path()))),
473            _ => None,
474        }
475    }
476
477    fn refused(reason: Unencryptable, at: &str) -> Option<(Unencryptable, String)> {
478        Some((reason, at.to_owned()))
479    }
480
481    #[test]
482    fn a_scalar_is_one_tagged_leaf_under_the_bare_aad() {
483        let node = planned(34u32).expect("plans");
484        let leaves = leaves_with_aad(node, &"ctx".into_aad());
485        assert_eq!(leaves.len(), 1);
486        assert_eq!(leaves[0].0.plaintext.risky_ref(), &[0x06, 34, 0, 0, 0]);
487        assert_eq!(leaves[0].1, "ctx".into_aad());
488    }
489
490    #[test]
491    fn none_is_a_null_leaf_not_a_marker() {
492        let node = planned(None::<String>).expect("plans");
493        assert!(matches!(node, CipherText::Single(_)));
494        let leaves = leaves_with_aad(node, &"ctx".into_aad());
495        assert_eq!(leaves[0].0.plaintext.risky_ref(), &[0x00]);
496        assert_eq!(leaves[0].1, "ctx".into_aad());
497    }
498
499    #[test]
500    fn a_fixed_width_none_is_a_typed_null_as_long_as_a_some() {
501        let node = planned(None::<u32>).expect("plans");
502        assert!(matches!(node, CipherText::Single(_)));
503        let leaves = leaves_with_aad(node, &"ctx".into_aad());
504        assert_eq!(leaves[0].0.plaintext.risky_ref(), &[0x15, 0x06, 0, 0, 0]);
505        assert_eq!(leaves[0].1, "ctx".into_aad());
506        let some = leaves_with_aad(planned(Some(34u32)).expect("plans"), &"ctx".into_aad());
507        assert_eq!(some[0].0.plaintext.risky_ref().len(), 5);
508    }
509
510    #[test]
511    fn a_typed_null_too_deep_is_refused_like_a_scalar() {
512        use crate::plaintext::{FixedType, Width};
513        let sink = EncryptSink::<Refused, Root> {
514            aad: "ctx".into_aad().into_owned(),
515            out: Root,
516            depth: crate::frame::MAX_DEPTH + 1,
517            _error: PhantomData,
518        };
519        let err = sink
520            .typed_null(FixedType::plain(Width::I8).expect("fixed"))
521            .err()
522            .expect("too deep");
523        assert!(matches!(err, EncryptError::Shape(_)), "{err:?}");
524    }
525
526    #[test]
527    fn empty_containers_are_markers() {
528        let node = planned(Vec::<u32>::new()).expect("plans");
529        assert!(matches!(node, CipherText::EmptySequence(_)));
530        let node = planned(BTreeMap::<&str, u32>::new()).expect("plans");
531        assert!(matches!(node, CipherText::EmptyMap(_)));
532    }
533
534    #[test]
535    fn duplicate_keys_are_refused() {
536        // The path names the entry under the repeated key.
537        assert_eq!(
538            refusal(planned(Dup)),
539            refused(Unencryptable::DuplicateKey, ".k")
540        );
541    }
542
543    #[test]
544    fn an_all_passthrough_container_is_refused() {
545        struct Pass;
546        impl Encrypt for Pass {
547            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
548                sink.passthrough("public")
549            }
550        }
551        assert_eq!(
552            refusal(planned(vec![Pass])),
553            refused(Unencryptable::SequenceHasNothingSealed, "")
554        );
555        let mut map = BTreeMap::new();
556        map.insert("note", Pass);
557        assert_eq!(
558            refusal(planned(map)),
559            refused(Unencryptable::MapHasNothingSealed, "")
560        );
561        // One sealed sibling is enough.
562        struct Mixed;
563        impl Encrypt for Mixed {
564            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
565                let mut seq = sink.seq(Some(2))?;
566                Pass.visit(seq.element()?)?;
567                1u32.visit(seq.element()?)?;
568                seq.end()
569            }
570        }
571        let node = planned(Mixed).expect("plans");
572        match node {
573            CipherText::Sequence(items) => {
574                assert!(
575                    matches!(items[0], CipherText::Passthrough(ref f) if f.0 == [0x0A, 6, 0, 0, 0, b'p', b'u', b'b', b'l', b'i', b'c'])
576                );
577                assert!(matches!(items[1], CipherText::Single(_)));
578            }
579            _ => panic!("expected a sequence"),
580        }
581    }
582
583    #[test]
584    fn an_invalid_logical_value_is_refused() {
585        let bad = Raw(Some(Logical::Uuid), Physical::Bytes(&[0; 3]));
586        assert_eq!(
587            refusal(planned(bad)),
588            refused(Unencryptable::InvalidValue, "")
589        );
590    }
591
592    #[test]
593    fn a_source_error_stops_the_walk() {
594        struct Failing;
595        impl Encrypt for Failing {
596            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
597                let mut seq = sink.seq(None)?;
598                1u32.visit(seq.element()?)?;
599                Err(S::Error::source(SourceError::msg("getter threw")))
600            }
601        }
602        match encrypt(Failing, &Mock, ()) {
603            Err(EncryptError::Source(e)) => assert_eq!(e.to_string(), "getter threw"),
604            other => panic!("{other:?}"),
605        }
606    }
607
608    /// A sink generic over its error type, as a terms sink would be, so it
609    /// can adopt the encrypt sink's.
610    struct Count<'c, E>(&'c mut usize, PhantomData<E>);
611
612    impl<E: Error> Sink for Count<'_, E> {
613        type Ok = ();
614        type Error = E;
615        type Seq = Self;
616        type Map = Self;
617        fn scalar(self, _: Option<Logical>, _: Physical<'_>) -> Result<(), E> {
618            *self.0 += 1;
619            Ok(())
620        }
621        fn typed_null(self, _: FixedType) -> Result<(), E> {
622            *self.0 += 1;
623            Ok(())
624        }
625        fn seq(self, _: Option<usize>) -> Result<Self, E> {
626            Ok(self)
627        }
628        fn map(self, _: Option<usize>) -> Result<Self, E> {
629            Ok(self)
630        }
631        fn passthrough<P: Encrypt + Clone>(self, _: P) -> Result<(), E> {
632            Ok(())
633        }
634    }
635
636    impl<E: Error> SeqSink for Count<'_, E> {
637        type Ok = ();
638        type Error = E;
639        type Element<'s>
640            = Count<'s, E>
641        where
642            Self: 's;
643        fn element(&mut self) -> Result<Count<'_, E>, E> {
644            Ok(Count(self.0, PhantomData))
645        }
646        fn end(self) -> Result<(), E> {
647            Ok(())
648        }
649    }
650
651    impl<E: Error> MapSink for Count<'_, E> {
652        type Ok = ();
653        type Error = E;
654        type Entry<'s>
655            = Count<'s, E>
656        where
657            Self: 's;
658        fn entry(&mut self, _: &str) -> Result<Count<'_, E>, E> {
659            Ok(Count(self.0, PhantomData))
660        }
661        fn end(self) -> Result<(), E> {
662            Ok(())
663        }
664    }
665
666    #[test]
667    fn the_sink_can_be_teed_before_any_cipher_exists() {
668        let mut scalars = 0;
669        let (node, ()) = vec![1u32, 2]
670            .visit(Tee(
671                EncryptSink::<Refused>::root("ctx"),
672                Count(&mut scalars, PhantomData),
673            ))
674            .expect("walks");
675        assert_eq!(scalars, 2);
676        assert_eq!(leaves_with_aad(node, &"ctx".into_aad()).len(), 2);
677    }
678
679    #[test]
680    fn a_length_hint_is_only_a_hint() {
681        struct Unsized;
682        impl Encrypt for Unsized {
683            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
684                visit_seq(
685                    sink,
686                    None,
687                    vec![Int::U8(1), Int::U8(2)]
688                        .into_iter()
689                        .map(|i| Raw(None, Physical::Int(i))),
690                )
691            }
692        }
693        let node = planned(Unsized).expect("plans");
694        assert_eq!(leaves_with_aad(node, &"ctx".into_aad()).len(), 2);
695    }
696
697    /// A source that asks for a child sink and drops it unused.
698    #[derive(Clone)]
699    struct DropsAChild {
700        map: bool,
701    }
702
703    impl Encrypt for DropsAChild {
704        fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
705            if self.map {
706                let mut map = sink.map(None)?;
707                let _ = map.entry("a")?;
708                1u16.visit(map.entry("b")?)?;
709                map.end()
710            } else {
711                let mut seq = sink.seq(None)?;
712                1u16.visit(seq.element()?)?;
713                let _ = seq.element()?;
714                seq.end()
715            }
716        }
717    }
718
719    #[test]
720    fn a_dropped_child_sink_is_refused_as_the_frame_writer_refuses_it() {
721        for map in [false, true] {
722            assert_eq!(
723                refusal(planned(DropsAChild { map })),
724                refused(Unencryptable::DroppedChild, "")
725            );
726            assert!(matches!(
727                crate::frame::to_frame(DropsAChild { map }),
728                Err(FrameWriteError::Unwritten)
729            ));
730        }
731        // A dropped element is caught at the next element too.
732        struct DropsThenWrites;
733        impl Encrypt for DropsThenWrites {
734            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
735                let mut seq = sink.seq(None)?;
736                let _ = seq.element()?;
737                1u16.visit(seq.element()?)?;
738                seq.end()
739            }
740        }
741        assert_eq!(
742            refusal(planned(DropsThenWrites)),
743            refused(Unencryptable::DroppedChild, "")
744        );
745    }
746
747    /// A passthrough that opens a container and never ends it.
748    #[derive(Clone)]
749    struct Unclosed;
750
751    impl Encrypt for Unclosed {
752        fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
753            let mut outer = sink.seq(None)?;
754            let mut inner = outer.element()?.seq(None)?;
755            1u32.visit(inner.element()?)?;
756            // Dropped without `end`, so its count is never written.
757            drop(inner);
758            outer.end()
759        }
760    }
761
762    #[test]
763    fn a_passthrough_that_does_not_frame_is_refused_at_encryption() {
764        struct Doc;
765        impl Encrypt for Doc {
766            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
767                let mut seq = sink.seq(Some(2))?;
768                1u32.visit(seq.element()?)?;
769                seq.element()?.passthrough(Unclosed)?;
770                seq.end()
771            }
772        }
773        assert!(matches!(
774            crate::frame::to_frame(Unclosed),
775            Err(FrameWriteError::Unfinished)
776        ));
777        // The path names the passthrough, not a node inside it.
778        assert_eq!(
779            refusal(planned(Doc)),
780            refused(Unencryptable::UnframablePassthrough, "[1]")
781        );
782    }
783
784    #[derive(Clone, Copy)]
785    enum Kind {
786        Seq,
787        Map,
788        Pass,
789        Scalar,
790    }
791
792    /// `n` levels of one kind of nesting around an innermost value at
793    /// depth `n`: an empty sequence, or for `Scalar`, an int inside
794    /// sequences.
795    #[derive(Clone, Copy)]
796    struct Nest(Kind, usize);
797
798    impl Encrypt for Nest {
799        fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
800            let Nest(kind, n) = self;
801            if n == 0 {
802                return match kind {
803                    Kind::Scalar => 1u16.visit(sink),
804                    _ => sink.seq(Some(0))?.end(),
805                };
806            }
807            let inner = Nest(kind, n - 1);
808            match kind {
809                Kind::Seq | Kind::Scalar => {
810                    let mut seq = sink.seq(Some(1))?;
811                    inner.visit(seq.element()?)?;
812                    seq.end()
813                }
814                Kind::Map => {
815                    let mut map = sink.map(Some(1))?;
816                    inner.visit(map.entry("k")?)?;
817                    map.end()
818                }
819                Kind::Pass => sink.passthrough(inner),
820            }
821        }
822    }
823
824    #[test]
825    fn depth_is_bounded_as_the_transport_decoder_bounds_it() {
826        // The path names the node one level too deep; a passthrough that
827        // nests too deep is refused as a whole, at its own place.
828        for (kind, step) in [
829            (Kind::Seq, "[0]"),
830            (Kind::Map, ".k"),
831            (Kind::Pass, ""),
832            (Kind::Scalar, "[0]"),
833        ] {
834            assert!(planned(Nest(kind, MAX_DEPTH)).is_ok());
835            assert_eq!(
836                refusal(planned(Nest(kind, MAX_DEPTH + 1))),
837                refused(Unencryptable::TooDeep, &step.repeat(MAX_DEPTH + 1))
838            );
839        }
840        // A passthrough below the top counts from the top: one level of
841        // sequence around a frame that is itself `MAX_DEPTH` deep is one
842        // too many.
843        struct Wrapped(usize);
844        impl Encrypt for Wrapped {
845            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
846                let mut seq = sink.seq(Some(2))?;
847                1u16.visit(seq.element()?)?;
848                seq.element()?.passthrough(Nest(Kind::Seq, self.0))?;
849                seq.end()
850            }
851        }
852        assert!(planned(Wrapped(MAX_DEPTH - 2)).is_ok());
853        assert_eq!(
854            refusal(planned(Wrapped(MAX_DEPTH - 1))),
855            refused(Unencryptable::TooDeep, "[1]")
856        );
857    }
858
859    /// A document whose `users` entry is a sequence of two values, the
860    /// second of them `bad`, beside a well-formed `id`.
861    struct Users<P>(P);
862
863    impl<P: Encrypt> Encrypt for Users<P> {
864        fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
865            let mut map = sink.map(Some(2))?;
866            1u32.visit(map.entry("id")?)?;
867            let mut users = map.entry("users")?.seq(Some(2))?;
868            "ada".visit(users.element()?)?;
869            self.0.visit(users.element()?)?;
870            users.end()?;
871            map.end()
872        }
873    }
874
875    /// A map that gives `k` twice.
876    struct Dup;
877
878    impl Encrypt for Dup {
879        fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
880            let mut map = sink.map(None)?;
881            1u32.visit(map.entry("k")?)?;
882            2u32.visit(map.entry("k")?)?;
883            map.end()
884        }
885    }
886
887    /// A container with one passthrough in it and nothing sealed.
888    #[derive(Clone)]
889    struct OnlyPass {
890        map: bool,
891    }
892
893    impl Encrypt for OnlyPass {
894        fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
895            if self.map {
896                let mut map = sink.map(Some(1))?;
897                map.entry("note")?.passthrough("public")?;
898                map.end()
899            } else {
900                let mut seq = sink.seq(Some(1))?;
901                seq.element()?.passthrough("public")?;
902                seq.end()
903            }
904        }
905    }
906
907    #[test]
908    fn a_refusal_names_the_node_below_the_root() {
909        let invalid = Raw(Some(Logical::Uuid), Physical::Bytes(&[0; 3]));
910        assert_eq!(
911            refusal(planned(Users(invalid))),
912            refused(Unencryptable::InvalidValue, ".users[1]")
913        );
914        assert_eq!(
915            refusal(planned(Users(Dup))),
916            refused(Unencryptable::DuplicateKey, ".users[1].k")
917        );
918        assert_eq!(
919            refusal(planned(Users(OnlyPass { map: false }))),
920            refused(Unencryptable::SequenceHasNothingSealed, ".users[1]")
921        );
922        assert_eq!(
923            refusal(planned(Users(OnlyPass { map: true }))),
924            refused(Unencryptable::MapHasNothingSealed, ".users[1]")
925        );
926        for map in [false, true] {
927            assert_eq!(
928                refusal(planned(Users(DropsAChild { map }))),
929                refused(Unencryptable::DroppedChild, ".users[1]")
930            );
931        }
932        assert_eq!(
933            refusal(planned(Users(Nest(Kind::Seq, MAX_DEPTH - 1)))),
934            refused(
935                Unencryptable::TooDeep,
936                &format!(".users[1]{}", "[0]".repeat(MAX_DEPTH - 1))
937            )
938        );
939        // Teed with another sink, the refusal and its path are the same.
940        let mut scalars = 0;
941        let invalid = Raw(Some(Logical::Uuid), Physical::Bytes(&[0; 3]));
942        let teed = Users(invalid).visit(Tee(
943            EncryptSink::<Refused>::root("ctx"),
944            Count(&mut scalars, PhantomData),
945        ));
946        assert_eq!(
947            refusal(teed),
948            refused(Unencryptable::InvalidValue, ".users[1]")
949        );
950    }
951
952    #[test]
953    fn a_refusal_shows_its_path_only_on_request() {
954        let err = plan::<_, &str, _>(Users(Dup), "ctx")
955            .err()
956            .expect("refused");
957        // Neither the message nor `Debug` names a key; `Debug` keeps the
958        // path's shape.
959        assert_eq!(err.to_string(), "cannot encrypt: duplicate key");
960        let debug = format!("{err:?}");
961        assert!(!debug.contains("users"), "{debug}");
962        assert!(debug.contains("DuplicateKey"), "{debug}");
963        assert!(debug.contains("Index(1)"), "{debug}");
964        assert!(debug.contains("Key(\"***\")"), "{debug}");
965        let EncryptError::Shape(refusal) = err else {
966            panic!("{err:?}")
967        };
968        assert_eq!(
969            refusal.path().steps(),
970            [
971                Step::Key("users".into()),
972                Step::Index(1),
973                Step::Key("k".into())
974            ]
975        );
976    }
977
978    #[test]
979    fn errors_say_what_failed_without_the_value() {
980        struct Secret;
981        impl Locate for Secret {
982            fn locate(&self, steps: &mut Vec<Step>) {
983                steps.push(Step::Key("secret".into()));
984            }
985        }
986        let shape = |reason| EncryptError::<&str>::Shape(refuse(&Secret, reason)).to_string();
987        assert_eq!(
988            shape(Unencryptable::DuplicateKey),
989            "cannot encrypt: duplicate key"
990        );
991        assert_eq!(
992            shape(Unencryptable::SequenceHasNothingSealed),
993            "cannot encrypt: sequence has nothing sealed"
994        );
995        assert_eq!(
996            shape(Unencryptable::MapHasNothingSealed),
997            "cannot encrypt: map has nothing sealed"
998        );
999        assert_eq!(
1000            shape(Unencryptable::InvalidValue),
1001            "cannot encrypt: value is not valid for its logical type"
1002        );
1003        assert_eq!(
1004            shape(Unencryptable::Unwritable),
1005            "cannot encrypt: value cannot be written as a leaf"
1006        );
1007        assert_eq!(
1008            shape(Unencryptable::UnframablePassthrough),
1009            "cannot encrypt: passthrough subtree cannot be framed"
1010        );
1011        assert_eq!(
1012            shape(Unencryptable::DroppedChild),
1013            "cannot encrypt: a child sink was dropped without a value"
1014        );
1015        assert_eq!(
1016            shape(Unencryptable::TooDeep),
1017            "cannot encrypt: value nests deeper than 128 levels"
1018        );
1019        assert_eq!(
1020            EncryptError::<&str>::Count(LeafCount::TooFew).to_string(),
1021            "cipher returned too few leaves"
1022        );
1023        assert_eq!(
1024            EncryptError::<&str>::Cipher("down").to_string(),
1025            "cipher failed: down"
1026        );
1027        assert_eq!(
1028            EncryptError::<&str>::Source(SourceError::msg("getter threw")).to_string(),
1029            "source failed: getter threw"
1030        );
1031        fn is_error<E: core::error::Error>(_: &E) {}
1032        is_error(&EncryptError::<&str>::Cipher("down"));
1033        // A source's failure is the chained cause; a cipher's is only in
1034        // the message, so its type need not be an error.
1035        use core::error::Error as _;
1036        let sourced = EncryptError::<&str>::Source(SourceError::msg("getter threw"));
1037        assert_eq!(
1038            sourced.source().map(|cause| cause.to_string()),
1039            Some("getter threw".into())
1040        );
1041        assert!(EncryptError::<&str>::Cipher("down").source().is_none());
1042    }
1043}