vest/properties.rs
1pub use crate::errors::*;
2pub use crate::utils::*;
3use vstd::prelude::*;
4use vstd::*;
5
6verus! {
7
8/// Specification for parser and serializer [`Combinator`]s. All Vest combinators must implement this
9/// trait.
10pub trait SpecCombinator {
11 /// The view of [`Combinator::Result`].
12 type SpecResult;
13
14 /// The specification of [`Combinator::parse`].
15 spec fn spec_parse(&self, s: Seq<u8>) -> Result<(usize, Self::SpecResult), ()>;
16
17 /// The specification of [`Combinator::serialize`].
18 spec fn spec_serialize(&self, v: Self::SpecResult) -> Result<Seq<u8>, ()>;
19
20 /// A helper fact to ensure that the result of parsing is within the input bounds.
21 proof fn spec_parse_wf(&self, s: Seq<u8>)
22 ensures
23 self.spec_parse(s).is_ok() ==> self.spec_parse(s).unwrap().0 <= s.len(),
24 ;
25}
26
27/// Theorems and lemmas that must be proven for a combinator to be considered correct and secure.
28pub trait SecureSpecCombinator: SpecCombinator {
29 /// Like an associated constant, denotes whether the combinator is prefix-secure.
30 spec fn is_prefix_secure() -> bool;
31
32 /// One of the top-level roundtrip properties
33 proof fn theorem_serialize_parse_roundtrip(&self, v: Self::SpecResult)
34 ensures
35 self.spec_serialize(v) matches Ok(b) ==> self.spec_parse(b) == Ok::<_, ()>(
36 (b.len() as usize, v),
37 ),
38 ;
39
40 /// One of the top-level roundtrip properties
41 proof fn theorem_parse_serialize_roundtrip(&self, buf: Seq<u8>)
42 requires
43 buf.len() <= usize::MAX,
44 ensures
45 self.spec_parse(buf) matches Ok((n, v)) ==> self.spec_serialize(v) == Ok::<_, ()>(
46 buf.subrange(0, n as int),
47 ),
48 ;
49
50 /// This prefix-secure lemma is used in the proof of the roundtrip properties for sequencing
51 /// combinators.
52 proof fn lemma_prefix_secure(&self, s1: Seq<u8>, s2: Seq<u8>)
53 requires
54 s1.len() + s2.len() <= usize::MAX,
55 ensures
56 Self::is_prefix_secure() ==> self.spec_parse(s1).is_ok() ==> self.spec_parse(s1.add(s2))
57 == self.spec_parse(s1),
58 ;
59}
60
61/// Implementation for parser and serializer combinators. A combinator's view must be a
62/// [`SecureSpecCombinator`].
63pub trait Combinator: View where
64 Self::V: SecureSpecCombinator<SpecResult = <Self::Owned as View>::V>,
65 {
66 /// The result type of parsing and the input type of serialization.
67 type Result<'a>: View<V = <Self::Owned as View>::V>;
68
69 /// The owned parsed type. This is currently a hack to avoid lifetime bindings in [`SpecCombinator::SpecResult`]
70 /// , but it can be useful if we want to have functions that return owned values (e.g. [`Vec<T>`]).
71 type Owned: View;
72
73 /// Spec version of [`Self::length`].
74 spec fn spec_length(&self) -> Option<usize>;
75
76 /// The length of the output buffer, if known.
77 /// This can be used to optimize serialization by pre-allocating the buffer.
78 fn length(&self) -> (res: Option<usize>)
79 ensures
80 res == self.spec_length(),
81 ;
82
83 /// Pre-condition for parsing.
84 open spec fn parse_requires(&self) -> bool {
85 true
86 }
87
88 /// The parsing function.
89 fn parse<'a>(&self, s: &'a [u8]) -> (res: Result<(usize, Self::Result<'a>), ParseError>)
90 requires
91 self.parse_requires(),
92 ensures
93 res matches Ok((n, v)) ==> {
94 &&& self@.spec_parse(s@) is Ok
95 &&& self@.spec_parse(s@) matches Ok((m, w)) ==> n == m && v@ == w && n <= s@.len()
96 },
97 res is Err ==> self@.spec_parse(s@) is Err,
98 ;
99
100 /// Pre-condition for serialization.
101 open spec fn serialize_requires(&self) -> bool {
102 true
103 }
104
105 /// The serialization function.
106 fn serialize(&self, v: Self::Result<'_>, data: &mut Vec<u8>, pos: usize) -> (res: Result<
107 usize,
108 SerializeError,
109 >)
110 requires
111 self.serialize_requires(),
112 ensures
113 data@.len() == old(data)@.len(),
114 res matches Ok(n) ==> {
115 &&& self@.spec_serialize(v@) is Ok
116 &&& self@.spec_serialize(v@) matches Ok(b) ==> {
117 n == b.len() && data@ == seq_splice(old(data)@, pos, b)
118 }
119 },
120 ;
121}
122
123impl<C: SpecCombinator> SpecCombinator for &C {
124 type SpecResult = C::SpecResult;
125
126 open spec fn spec_parse(&self, s: Seq<u8>) -> Result<(usize, Self::SpecResult), ()> {
127 (*self).spec_parse(s)
128 }
129
130 open spec fn spec_serialize(&self, v: Self::SpecResult) -> Result<Seq<u8>, ()> {
131 (*self).spec_serialize(v)
132 }
133
134 proof fn spec_parse_wf(&self, s: Seq<u8>) {
135 (*self).spec_parse_wf(s)
136 }
137}
138
139impl<C: SecureSpecCombinator> SecureSpecCombinator for &C {
140 open spec fn is_prefix_secure() -> bool {
141 C::is_prefix_secure()
142 }
143
144 proof fn theorem_serialize_parse_roundtrip(&self, v: Self::SpecResult) {
145 (*self).theorem_serialize_parse_roundtrip(v)
146 }
147
148 proof fn theorem_parse_serialize_roundtrip(&self, buf: Seq<u8>) {
149 (*self).theorem_parse_serialize_roundtrip(buf)
150 }
151
152 proof fn lemma_prefix_secure(&self, s1: Seq<u8>, s2: Seq<u8>) {
153 (*self).lemma_prefix_secure(s1, s2)
154 }
155}
156
157impl<C: Combinator> Combinator for &C where
158 C::V: SecureSpecCombinator<SpecResult = <C::Owned as View>::V>,
159 {
160 type Result<'a> = C::Result<'a>;
161
162 type Owned = C::Owned;
163
164 open spec fn spec_length(&self) -> Option<usize> {
165 (*self).spec_length()
166 }
167
168 fn length(&self) -> Option<usize> {
169 (*self).length()
170 }
171
172 open spec fn parse_requires(&self) -> bool {
173 (*self).parse_requires()
174 }
175
176 fn parse<'a>(&self, s: &'a [u8]) -> (res: Result<(usize, Self::Result<'a>), ParseError>) {
177 (*self).parse(s)
178 }
179
180 open spec fn serialize_requires(&self) -> bool {
181 (*self).serialize_requires()
182 }
183
184 fn serialize(&self, v: Self::Result<'_>, data: &mut Vec<u8>, pos: usize) -> (res: Result<
185 usize,
186 SerializeError,
187 >) {
188 (*self).serialize(v, data, pos)
189 }
190}
191
192// The following is an attempt to support `Fn`s as combinators.
193// I started implementing it because Verus doesn't support existential types (impl Trait) yet,
194// which is required to be able to put `Depend` combinator in the function's return type,
195// but more generally, I think it's probably also good to have a way in our framework to define
196// combinators as a group of (parsing and serializing) functions.
197//
198// Overall this is doable, but it's a bit tricky to implement because of the current limitations of Verus:
199// - Verus doesn't have general support for struct invariants yet, which means we have to add
200// pre-conditions to the security properties (i.e. the struct containing a pair of parsing and
201// serializing functions must be "well-formed" in some sense).
202// - Verus doesn't have general support for `&mut` types yet, which means for serialization we
203// cannot freely pass around the `&mut Vec<u8>`
204//
205// In theory, we could factor out all the lifetime parameters in `Combinator` trait and use generic
206// type parameters instead for both parsing and serialization, but that would require another
207// entire-codebase refactoring, which I think is not worth it at this point.
208//
209// #[verifier::reject_recursive_types(SpecResult)]
210// pub struct SpecCombinatorFn<SpecResult, const Prefix: u8> {
211// pub parse: spec_fn(Seq<u8>) -> Result<(usize, SpecResult), ()>,
212// pub serialize: spec_fn(SpecResult) -> Result<Seq<u8>, ()>,
213// }
214//
215// impl<SpecResult, const Prefix: u8> SpecCombinatorFn<SpecResult, Prefix> {
216// pub open spec fn new(
217// parse: spec_fn(Seq<u8>) -> Result<(usize, SpecResult), ()>,
218// serialize: spec_fn(SpecResult) -> Result<Seq<u8>, ()>,
219// ) -> (o: Self)
220// recommends
221// forall|v| Self::theorem_serialize_parse_roundtrip(parse, serialize, v),
222// forall|buf| Self::theorem_parse_serialize_roundtrip(parse, serialize, buf),
223// forall|s1, s2| Self::lemma_prefix_secure(parse, s1, s2),
224// {
225// Self { parse, serialize }
226// }
227//
228// pub open spec fn theorem_serialize_parse_roundtrip(
229// parse: spec_fn(Seq<u8>) -> Result<(usize, SpecResult), ()>,
230// serialize: spec_fn(SpecResult) -> Result<Seq<u8>, ()>,
231// v: SpecResult,
232// ) -> bool {
233// serialize(v) is Ok ==> parse(serialize(v).unwrap()) == Ok::<_, ()>(
234// (serialize(v).unwrap().len() as usize, v),
235// )
236// }
237//
238// pub open spec fn theorem_parse_serialize_roundtrip(
239// parse: spec_fn(Seq<u8>) -> Result<(usize, SpecResult), ()>,
240// serialize: spec_fn(SpecResult) -> Result<Seq<u8>, ()>,
241// buf: Seq<u8>,
242// ) -> bool {
243// buf.len() <= usize::MAX ==> parse(buf) is Ok ==> serialize(parse(buf).unwrap().1) == Ok::<
244// _,
245// (),
246// >(buf.subrange(0, parse(buf).unwrap().0 as int))
247// }
248//
249// pub open spec fn lemma_prefix_secure(
250// parse: spec_fn(Seq<u8>) -> Result<(usize, SpecResult), ()>,
251// s1: Seq<u8>,
252// s2: Seq<u8>,
253// ) -> bool {
254// if s1.len() + s2.len() <= usize::MAX {
255// (Prefix == 1) ==> parse(s1) is Ok ==> parse(s1.add(s2)) == parse(s1)
256// } else {
257// true
258// }
259// }
260// }
261//
262//
263// impl<SpecResult, const Prefix: u8> SpecCombinator for SpecCombinatorFn<SpecResult, Prefix> {
264// type SpecResult = SpecResult;
265//
266// open spec fn spec_parse(&self, s: Seq<u8>) -> Result<(usize, Self::SpecResult), ()> {
267// if let Ok((n, v)) = (self.parse)(s) {
268// if n <= s.len() {
269// Ok((n, v))
270// } else {
271// Err(())
272// }
273// } else {
274// Err(())
275// }
276// }
277//
278// open spec fn spec_serialize(&self, v: Self::SpecResult) -> Result<Seq<u8>, ()> {
279// (self.serialize)(v)
280// }
281//
282// proof fn spec_parse_wf(&self, s: Seq<u8>) {
283// }
284// }
285//
286// impl<SpecResult, const Prefix: u8> SecureSpecCombinator for SpecCombinatorFn<SpecResult, Prefix> {
287// open spec fn spec_is_prefix_secure() -> bool {
288// Prefix == 1
289// }
290//
291// proof fn theorem_serialize_parse_roundtrip(&self, v: Self::SpecResult) {
292// assume(Self::theorem_serialize_parse_roundtrip(self.parse, self.serialize, v));
293// }
294//
295// proof fn theorem_parse_serialize_roundtrip(&self, buf: Seq<u8>) {
296// assume(Self::theorem_parse_serialize_roundtrip(self.parse, self.serialize, buf));
297// }
298//
299// proof fn lemma_prefix_secure(&self, s1: Seq<u8>, s2: Seq<u8>) {
300// assume(Self::lemma_prefix_secure(self.parse, s1, s2));
301// }
302// }
303//
304// pub struct CombinatorFn<'a, 'b, R, P, S, const Prefix: u8> where
305// R: View,
306// P: Fn(&'a [u8]) -> Result<(usize, R), ()>,
307// S: Fn(R, &'b mut Vec<u8>, usize) -> Result<usize, ()>,
308// {
309// pub parse: P,
310// pub serialize: S,
311// pub spec_parse: Ghost<spec_fn(Seq<u8>) -> Result<(usize, R::V), ()>>,
312// pub spec_serialize: Ghost<spec_fn(R::V) -> Result<Seq<u8>, ()>>,
313// pub _phantom: std::marker::PhantomData<&'b &'a R>,
314// }
315//
316// impl<'a, 'b, R, P, S, const Prefix: u8> View for CombinatorFn<'a, 'b, R, P, S, Prefix> where
317// R: View,
318// P: Fn(&'a [u8]) -> Result<(usize, R), ()>,
319// S: Fn(R, &'b mut Vec<u8>, usize) -> Result<usize, ()>,
320// {
321// type V = SpecCombinatorFn<R::V, Prefix>;
322//
323// open spec fn view(&self) -> Self::V {
324// let Ghost(parse) = self.spec_parse;
325// let Ghost(serialize) = self.spec_serialize;
326// SpecCombinatorFn {
327// parse,
328// serialize,
329// }
330// }
331// }
332//
333// impl<'a, 'b, R, P, S, const Prefix: u8> Combinator for CombinatorFn<'a, 'b, R, P, S, Prefix> where
334// R: View,
335// P: Fn(&'a [u8]) -> Result<(usize, R), ()>,
336// S: Fn(R, &'b mut Vec<u8>, usize) -> Result<usize, ()>,
337// Self::V: SecureSpecCombinator<SpecResult = R::V>,
338// {
339// type Result<'c> = R;
340//
341// type Owned = R;
342//
343// open spec fn spec_length(&self) -> Option<usize> {
344// None
345// }
346//
347// fn length(&self) -> Option<usize> {
348// None
349// }
350//
351// fn exec_is_prefix_secure() -> bool {
352// Prefix == 1
353// }
354//
355// fn parse<'c>(&self, s: &'c [u8]) -> Result<(usize, Self::Result<'c>), ()> where 'c: 'a {
356// (self.parse)(s)
357// }
358//
359// fn serialize(&self, v: Self::Result<'_>, data: &mut Vec<u8>, pos: usize) -> Result<usize, ()> {
360// (self.serialize)(v, data, pos)
361// }
362// }
363} // verus!