Skip to main content

vest_lib/core/
spec.rs

1//! Core specification traits for Vest combinators.
2use vstd::prelude::*;
3
4verus! {
5
6/// Parser specification.
7pub trait SpecParser {
8    /// The type of parsed values.
9    type PVal;
10
11    /// Attempts to parse a value from `ibuf`.
12    ///
13    /// Returns `Some((n, v))` on success, where `n` bytes were consumed and `v` is
14    /// the parsed value, or `None` on failure.
15    spec fn spec_parse(&self, ibuf: Seq<u8>) -> Option<(int, Self::PVal)>;
16}
17
18/// Two parsers have disjoint domains if no input can be successfully parsed by both.
19///
20/// This is the key condition for establishing unambiguity in combinator compositions.
21/// See also [`crate::combinators::disjoint`] for broadcast lemmas establishing
22/// disjointness for common compositions.
23#[verifier::opaque]
24pub open spec fn disjoint_domains<P1: SpecParser, P2: SpecParser>(p1: P1, p2: P2) -> bool {
25    forall|input: Seq<u8>| p1.spec_parse(input) is Some && p2.spec_parse(input) is Some ==> false
26}
27
28/// Combinator denotations that admit disjoint (mutually exclusive) sets of consistent values.
29///
30/// Used by [`crate::combinators::Alt`] to recover non-malleability.
31pub open spec fn disjoint_values<C1, C2>(c1: C1, c2: C2) -> bool where
32    C1: Consistency,
33    C2: Consistency<Val = C1::Val>,
34 {
35    forall|v: C1::Val| c1.consistent(v) && c2.consistent(v) ==> false
36}
37
38/// Returns `true` when parser `p` fails on input `ibuf`.
39pub open spec fn parser_fails_on<P: SpecParser>(p: P, ibuf: Seq<u8>) -> bool {
40    p.spec_parse(ibuf) is None
41}
42
43/// Parser safety.
44///
45/// Successful parses never consume bytes out of bounds.
46pub trait SafeParser: SpecParser {
47    /// Optional invariant (used by spec-function combinators; struct-based combinators
48    /// typically leave this as `true`).
49    open spec fn safe_inv(&self) -> bool {
50        true
51    }
52
53    /// For any successful parse `Some((n, _))`, `0 <= n <= ibuf.len()`.
54    broadcast proof fn lemma_parse_safe(&self, ibuf: Seq<u8>)
55        requires
56            self.safe_inv(),
57        ensures
58            #[trigger] self.spec_parse(ibuf) matches Some((n, _)) ==> 0 <= n <= ibuf.len(),
59    ;
60}
61
62/// Parser soundness.
63///
64/// This trait specifies semantic soundness w.r.t. the format spec, independent
65/// from the orthogonal safety property captured by [`SafeParser`].
66pub trait SoundParser: SpecByteLen + SpecParser<PVal = Self::T> + Consistency<Val = Self::T> {
67    /// Optional invariant (used by spec-function combinators; struct-based combinators
68    /// typically leave this as `true`).
69    open spec fn sound_inv(&self) -> bool {
70        true
71    }
72
73    /// For any successful parse `Some((n, v))`, `n == self.byte_len(v)`.
74    proof fn lemma_parse_sound_consumption(&self, ibuf: Seq<u8>)
75        requires
76            self.sound_inv(),
77        ensures
78            self.spec_parse(ibuf) matches Some((n, v)) ==> n == self.byte_len(v),
79    ;
80
81    /// For any successful parse `Some((_, v))`, `v` is consistent with the format's spec.
82    broadcast proof fn lemma_parse_sound_value(&self, ibuf: Seq<u8>)
83        requires
84            self.sound_inv(),
85        ensures
86            #[trigger] self.spec_parse(ibuf) matches Some((_, v)) ==> self.consistent(v),
87    ;
88}
89
90/// Value well-formedness according to a format's specification.
91///
92/// ## Examples
93///
94/// - [`crate::combinators::Refined`] requires the refinement predicate;
95/// - [`crate::combinators::Varied`]/[`crate::combinators::Repeat`] requires matching length/count;
96/// - [`crate::combinators::U8`] imposes trivial consistency condition (all `u8` values are consistent with the format);
97/// - [`crate::combinators::Void`] is uninhabited and thus has no consistent values.
98pub trait Consistency {
99    /// The type of values whose consistency is being checked.
100    type Val;
101
102    /// Returns `true` if `v` is well-formed w.r.t. this combinator.
103    spec fn consistent(&self, v: Self::Val) -> bool;
104}
105
106/// Combinators whose consistency admits at most one value.
107///
108/// Used by e.g., [`crate::combinators::Preceded`] and [`crate::combinators::Terminated`] to
109/// recover non-malleability when the discarded side is not checked explicitly.
110pub trait AdmitsUniqueVal: Consistency {
111    /// Any two consistent values must be equal.
112    proof fn lemma_unique_consistent_val(&self, v1: Self::Val, v2: Self::Val)
113        ensures
114            self.consistent(v1) && self.consistent(v2) ==> v1 == v2,
115    ;
116}
117
118/// Spec-level predicate abstraction.
119pub trait SpecPred<T> {
120    /// Applies the predicate to a value.
121    spec fn apply(&self, value: T) -> bool;
122}
123
124/// A spec-level predicate function type alias.
125pub type PredFnSpec<T> = spec_fn(T) -> bool;
126
127impl<T> SpecPred<T> for PredFnSpec<T> {
128    open spec fn apply(&self, value: T) -> bool {
129        self(value)
130    }
131}
132
133impl<T, P: SpecPred<T>> SpecPred<T> for &P {
134    open spec fn apply(&self, value: T) -> bool {
135        (*self).apply(value)
136    }
137}
138
139/// Destination-passing style (DPS) serializer specification.
140///
141/// See [`crate::core::proof::EquivSerializers`] for its relationship to [`SpecSerializer`].
142pub trait SpecSerializerDps {
143    /// The type of values to be serialized.
144    type SValue;
145
146    /// Serializes `v` by prepending its encoding onto `obuf`.
147    spec fn spec_serialize_dps(&self, v: Self::SValue, obuf: Seq<u8>) -> Seq<u8>;
148}
149
150/// Denotes the byte length of a value w.r.t. a combinator's format spec.
151pub trait SpecByteLen {
152    /// The type of values whose byte length is being computed.
153    type T;
154
155    /// Returns the number of bytes `v` occupies when serialized.
156    spec fn byte_len(&self, v: Self::T) -> nat;
157}
158
159/// Denotes the min/max byte length of a value w.r.t. a combinator's format spec.
160///
161/// **Combinators that do not implement `MinMaxByteLen`**
162/// * **Unbounded Sequence/Tail Combinators:**
163///   *   `Tail` (consumes the remaining buffer)
164///   *   `Star<A>` (zero or more repetitions)
165///   *   `Repeat<A, B>` (zero or more `A`s followed by terminator `B`)
166///   *   `RepeatTillEnd<A>` (sugar for `Repeat<A, Eof>`)
167/// * **Dependent and Recursive Combinators:**
168///   *   `Bind<A, B>` (the suffix parser `B` is constructed dynamically from the parsed value of `A`)
169///   *   `Implicit<Head, Tail>` (similar to `Bind`)
170///   *   `FixWith<LIMIT, Body, Param>` (recursive fixpoint)
171pub trait MinMaxByteLen: SpecByteLen + Consistency<Val = Self::T> {
172    spec fn min(&self) -> nat;
173
174    spec fn max(&self) -> nat;
175
176    proof fn lemma_min_max_byte_len(&self, v: Self::T)
177        requires
178            self.consistent(v),
179        ensures
180            self.min() <= self.byte_len(v) <= self.max(),
181    ;
182}
183
184/// Static byte length for fixed-size combinators.
185pub trait StaticByteLen: SpecByteLen + Consistency<Val = Self::T> {
186    /// The statically known serialized length.
187    spec fn static_byte_len() -> nat;
188
189    /// Bridge between the dynamic byte-length view and the static one.
190    proof fn lemma_static_len_matches_byte_len(&self, v: Self::T)
191        requires
192            self.consistent(v),
193        ensures
194            self.byte_len(v) == Self::static_byte_len(),
195    ;
196}
197
198/// Like [`SpecByteLen`], but the byte length can be computed from the value alone, without needing to refer
199/// to the combinator/format's parameters or internal states (`self`).
200pub trait ValueByteLen: SpecByteLen + Consistency<Val = Self::T> {
201    /// The byte length computed from the value alone.
202    spec fn value_byte_len(v: Self::T) -> nat;
203
204    /// Bridge between the parameterized byte-length view and the value-based one.
205    proof fn lemma_value_len_matches_byte_len(&self, v: Self::T)
206        requires
207            self.consistent(v),
208        ensures
209            self.byte_len(v) == Self::value_byte_len(v),
210    ;
211}
212
213/// Broadcast wrapper for [`ValueByteLen::lemma_value_len_matches_byte_len`].
214pub broadcast proof fn lemma_value_len_matches_byte_len<C: ValueByteLen + Consistency>(
215    c: C,
216    v: C::T,
217)
218    requires
219        c.consistent(v),
220    ensures
221        #[trigger] c.byte_len(v) == C::value_byte_len(v),
222{
223    c.lemma_value_len_matches_byte_len(v);
224}
225
226/// Marker for combinators whose corresponding values are raw bytes (`Seq<u8>`).
227pub trait BytesCombinator: SpecByteLen<T = Seq<u8>> {
228    /// Byte length equals buffer length.
229    proof fn lemma_byte_len_is_buf_len(&self, buf: Seq<u8>)
230        ensures
231            self.byte_len(buf) == buf.len(),
232    ;
233}
234
235/// Serializer specification.
236///
237/// See [`crate::core::proof::EquivSerializers`] for its relationship to [`SpecSerializerDps`].
238pub trait SpecSerializer {
239    /// The type of values to be serialized.
240    type SVal;
241
242    /// Serializes `v` into a fresh byte sequence.
243    spec fn spec_serialize(&self, v: Self::SVal) -> Seq<u8>;
244}
245
246/// A non-tail format combinator would allow for things to be serialized after itself.
247///
248/// ## Notable formats that are *not* non-tail (i.e., tail formats)
249///
250/// - [`crate::combinators::Tail`]
251/// - [`crate::combinators::Eof`]
252/// - [`crate::combinators::OptionalEnd`]
253/// - [`crate::combinators::RepeatTillEnd`]
254pub trait NonTailFmt: SpecByteLen + SpecSerializerDps<SValue = Self::T> {
255    /// Optional invariant for DPS serializer proofs.
256    open spec fn serialize_dps_inv(&self) -> bool {
257        true
258    }
259
260    /// The serializer prepends to `obuf` (so it will leave `obuf` intact, no truncation, corruption, etc.).
261    ///
262    /// Another way to think about this is that the format allows for trailing bytes after itself, whereas a tail format
263    /// would only allow for leading bytes before itself.
264    proof fn lemma_serialize_dps_prepend(&self, v: Self::SValue, obuf: Seq<u8>)
265        requires
266            self.serialize_dps_inv(),
267        ensures
268            exists|new_buf: Seq<u8>| self.spec_serialize_dps(v, obuf) == new_buf + obuf,
269    ;
270
271    /// number of bytes prepended equals `byte_len(v)`.
272    proof fn lemma_serialize_dps_len(&self, v: Self::SValue, obuf: Seq<u8>)
273        requires
274            self.serialize_dps_inv(),
275        ensures
276            self.spec_serialize_dps(v, obuf).len() - obuf.len() == self.byte_len(v),
277    ;
278}
279
280/// A well-behaved serializer.
281pub trait GoodSerializer: SpecByteLen + SpecSerializer<SVal = Self::T> {
282    /// Optional invariant for serializer-length proofs.
283    open spec fn serialize_inv(&self) -> bool {
284        true
285    }
286
287    /// serialized byte sequence has the expected length.
288    broadcast proof fn lemma_serialize_len(&self, v: Self::SVal)
289        requires
290            self.serialize_inv(),
291        ensures
292            #![trigger self.spec_serialize(v)]
293            #![trigger self.byte_len(v)]
294            self.spec_serialize(v).len() == self.byte_len(v),
295    ;
296}
297
298/// Marker trait for all specification traits bundled together.
299pub trait SpecCombinator: SpecByteLen + Consistency<Val = Self::T> + SpecParser<
300    PVal = Self::T,
301> + SpecSerializer<SVal = Self::T> + SpecSerializerDps<SValue = Self::T> {
302
303}
304
305impl<T> SpecCombinator for T where
306    T: SpecByteLen + Consistency<Val = Self::T> + SpecParser<PVal = Self::T> + SpecSerializer<
307        SVal = Self::T,
308    > + SpecSerializerDps<SValue = Self::T>,
309 {
310
311}
312
313} // verus!
314pub use crate::core::fns::{ByteLenFnSpec, ParserFnSpec, SerializerDPSFnSpec, SerializerFnSpec};