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};