Skip to main content

vest_lib/core/exec/
output.rs

1//! Output abstractions for executable serializers.
2#[cfg(feature = "alloc")]
3use alloc::vec::Vec;
4use vstd::prelude::*;
5
6verus! {
7
8/// Specification for `core`'s unchecked slice split.
9///
10/// `vstd` specifies the checked `split_at_mut` but not this variant.
11pub assume_specification<T>[ <[T]>::split_at_mut_unchecked ](slice: &mut [T], mid: usize) -> (ret: (
12    &mut [T],
13    &mut [T],
14))
15    requires
16        mid <= old(slice)@.len(),
17    ensures
18        ret.0@ == old(slice)@[0..mid],
19        ret.1@ == old(slice)@[mid..],
20        final(slice)@ == final(ret.0)@ + final(ret.1)@,
21;
22
23/// An abstraction for append-oriented output buffer.
24///
25/// The view is the sequence already present in, or written through, the output. Capacity is
26/// abstract: bounded and unbounded outputs expose the same `fits` interface.
27pub trait OutputBuf: View<V = Seq<u8>> {
28    /// Whether the output can accept `len` additional bytes.
29    spec fn fits(&self, len: nat) -> bool;
30
31    /// Write capacity is monotone: accepting a larger write implies accepting every prefix.
32    broadcast proof fn lemma_fits_mono(&self, shorter: nat, longer: nat)
33        requires
34            shorter <= longer,
35            self.fits(longer),
36        ensures
37            #![all_triggers]
38            self.fits(shorter),
39    ;
40
41    /// Whether two states write to the same final destination.
42    ///
43    /// This is vacuously true for outputs without (re-)borrowed backing storage. For a borrowed
44    /// output, it relates the prophetic final contents of the backing storage across states.
45    #[verifier::prophetic]
46    spec fn same_destination(&self, other: &Self) -> bool;
47
48    /// Destination identity is reflexive.
49    broadcast proof fn lemma_same_destination_reflexive(&self)
50        ensures
51            #[trigger] self.same_destination(self),
52    ;
53
54    /// Destination identity is transitive.
55    broadcast proof fn lemma_same_destination_transitive(&self, middle: &Self, last: &Self)
56        requires
57            self.same_destination(middle),
58            middle.same_destination(last),
59        ensures
60            #![all_triggers]
61            self.same_destination(last),
62    ;
63
64    /// Appends one byte to the logical output.
65    fn write_byte(&mut self, byte: u8)
66        requires
67            old(self).fits(1),
68        ensures
69            final(self)@ == old(self)@.push(byte),
70            forall|n| old(self).fits(1 + n) <==> #[trigger] final(self).fits(n),
71            old(self).same_destination(final(self)),
72    ;
73
74    /// Appends all bytes in `bytes` to the logical output.
75    fn write_bytes(&mut self, bytes: &[u8])
76        requires
77            old(self).fits(bytes@.len()),
78        ensures
79            final(self)@ == old(self)@ + bytes@,
80            forall|n| old(self).fits(bytes@.len() + n) <==> #[trigger] final(self).fits(n),
81            old(self).same_destination(final(self)),
82    {
83        broadcast use OutputBuf::lemma_same_destination_reflexive;
84
85        let ghost initial_view = self@;
86        for byte in iter: bytes.iter()
87            invariant
88                self@ == initial_view + bytes@[0..iter.index()],
89                old(self).fits(bytes@.len()),
90                forall|n| old(self).fits(iter.index() as nat + n) <==> #[trigger] self.fits(n),
91                old(self).same_destination(self),
92        {
93            broadcast use OutputBuf::lemma_same_destination_transitive;
94
95            proof {
96                old(self).lemma_fits_mono(iter.index() as nat + 1, bytes@.len());
97                assert(self.fits(1));
98            }
99            self.write_byte(*byte);
100        }
101    }
102
103    /// Appends a fixed-width group of bytes.
104    ///
105    /// Identical to [`write_bytes`](Self::write_bytes) except that the width is
106    /// a const generic, which can help with the compiler optimization.
107    fn write_array<const N: usize>(&mut self, bytes: &[u8; N])
108        requires
109            old(self).fits(bytes@.len()),
110        ensures
111            final(self)@ == old(self)@ + bytes@,
112            forall|n| old(self).fits(bytes@.len() + n) <==> #[trigger] final(self).fits(n),
113            old(self).same_destination(final(self)),
114    {
115        self.write_bytes(bytes)
116    }
117}
118
119/// A non-allocating append sink backed by a caller-provided slice.
120///
121/// Its logical view is the prefix written so far (given by `pos`).
122pub struct OutputSlice<'a> {
123    pub obuf: &'a mut [u8],
124    pub pos: usize,
125}
126
127impl View for OutputSlice<'_> {
128    type V = Seq<u8>;
129
130    open spec fn view(&self) -> Self::V {
131        self.obuf@[0..self.pos]
132    }
133}
134
135impl<'a> OutputSlice<'a> {
136    /// The prophetic final contents of the caller-provided backing slice.
137    #[verifier::prophetic]
138    pub open spec fn final_destination(&self) -> Seq<u8> {
139        final(self.obuf)@
140    }
141
142    /// Creates an empty logical output over `obuf` without allocating.
143    pub fn new(obuf: &'a mut [u8]) -> (output: Self)
144        ensures
145            output@ == Seq::empty(),
146            output.fits(old(obuf)@.len()),
147            forall|len: nat| #[trigger] output.fits(len) == (len <= old(obuf)@.len()),
148            output.final_destination() == final(obuf)@,
149    {
150        Self { obuf, pos: 0 }
151    }
152}
153
154impl OutputBuf for OutputSlice<'_> {
155    open spec fn fits(&self, len: nat) -> bool {
156        self.pos as nat + len <= self.obuf@.len()
157    }
158
159    proof fn lemma_fits_mono(&self, shorter: nat, longer: nat) {
160    }
161
162    #[verifier::prophetic]
163    open spec fn same_destination(&self, other: &Self) -> bool {
164        self.final_destination() == other.final_destination()
165    }
166
167    proof fn lemma_same_destination_reflexive(&self) {
168    }
169
170    proof fn lemma_same_destination_transitive(&self, _middle: &Self, _last: &Self) {
171    }
172
173    fn write_byte(&mut self, byte: u8) {
174        assert(self.pos < self.obuf.len());
175        self.obuf[self.pos] = byte;
176        self.pos += 1;
177    }
178
179    #[inline(always)]
180    fn write_array<const N: usize>(&mut self, bytes: &[u8; N]) {
181        let ghost old_view = self@;
182        let old_pos = self.pos;
183        assert(old_pos + N <= self.obuf.len());
184        {
185            let (_prefix, rest) = unsafe { self.obuf.split_at_mut_unchecked(old_pos) };
186            let (destination, _suffix) = unsafe { rest.split_at_mut_unchecked(N) };
187            destination.copy_from_slice(bytes);
188        }
189        self.pos = old_pos + N;
190        assert(self@ == old_view + bytes@);
191    }
192
193    #[inline(always)]
194    fn write_bytes(&mut self, bytes: &[u8]) {
195        let ghost old_view = self@;
196        let old_pos = self.pos;
197        let len = bytes.len();
198        assert(old_pos + len <= self.obuf.len());
199        {
200            // `split_at_mut` bounds-checks both halves of each split at run
201            // time, which dominates serialization of formats made of many
202            // small fixed-width fields.
203            // SAFETY:
204            // We already requires `fits(bytes@.len())`, so it is sound to use the unchecked variant here.
205            let (_prefix, rest) = unsafe { self.obuf.split_at_mut_unchecked(old_pos) };
206            let (destination, _suffix) = unsafe { rest.split_at_mut_unchecked(len) };
207            destination.copy_from_slice(bytes);
208        }
209        self.pos = old_pos + len;
210        assert(self@ == old_view + bytes@);
211    }
212}
213
214#[cfg(feature = "alloc")]
215impl OutputBuf for Vec<u8> {
216    open spec fn fits(&self, _len: nat) -> bool {
217        true
218    }
219
220    proof fn lemma_fits_mono(&self, _shorter: nat, _longer: nat) {
221    }
222
223    #[verifier::prophetic]
224    open spec fn same_destination(&self, _other: &Self) -> bool {
225        true
226    }
227
228    proof fn lemma_same_destination_reflexive(&self) {
229    }
230
231    proof fn lemma_same_destination_transitive(&self, _middle: &Self, _last: &Self) {
232    }
233
234    fn write_byte(&mut self, byte: u8) {
235        self.push(byte);
236    }
237
238    fn write_bytes(&mut self, bytes: &[u8]) {
239        self.extend_from_slice(bytes);
240    }
241}
242
243pub broadcast group outbuf_lemmas {
244    OutputBuf::lemma_fits_mono,
245    OutputBuf::lemma_same_destination_reflexive,
246    OutputBuf::lemma_same_destination_transitive,
247}
248
249} // verus!