Skip to main content

lace_sans_io/iltp/
stream_encoding.rs

1//! ILTP stream encodings.
2//!
3//! These let each direction of a logical session carry its ILTP items
4//! compressed. The direction's encoding is selected from the two raw prefaces
5//! and covers every byte after the sender's preface. A compressed encoding ends
6//! with its end mark, after which the decoder leaves raw bytes untouched for
7//! the carrier: a fresh preface or EOF.
8//!
9//! Encoders and decoders are sans-IO and per direction. Callers own all wire
10//! buffering: both read a caller slice and write into a caller-bounded output
11//! slice.
12
13#[cfg(feature = "deflate")]
14mod deflate;
15#[cfg(feature = "zstd")]
16mod zstd;
17
18use std::fmt;
19
20/// Raw preset dictionary priming every fresh compressed encoder and decoder.
21///
22/// It holds ILTP item vocabulary: the operand body and generic rule
23/// fragments, then proof, policy announcement and answers, listing, response,
24/// change, request and fence text. The most frequent items come last, where
25/// deflate's 4 KiB window keeps them longest. libzstd needs a raw-content
26/// dictionary of at least 512 bytes; shorter ones degrade compression for the
27/// whole stream.
28pub const STREAM_ENCODING_DICTIONARY: &[u8] = concat!(
29    // Operand body and the generic parts of compiled 040 rules.
30    "::policy-body\nšŸ–§: B.",
31    ".H3\nData-Length: ",
32    "\n\n",
33    "ExposeRecord(",
34    ") :- Have(",
35    "), Field(",
36    ",'By','0','V.",
37    ".H3'), not Field(",
38    ",'By','1',_).\n",
39    "SelectAdvertisedCandidate(",
40    ") :- Advertised(",
41    "), AdvertisedField(",
42    "','0','",
43    "'), not AdvertisedField(",
44    ",'1',_), PeerConfidential(",
45    "SelectStoredRecord(",
46    // Proof: none, or a Seal over the empty Blob.
47    "::as unknown\n",
48    "::as\nšŸ–§: S.",
49    ".H3\nBy: V.",
50    ".H3\nMark: ",
51    "\nšŸ–§: P.",
52    ".H3\nChallenge-side0: ",
53    "\nChallenge-side1: ",
54    "\nChannel-Binding: ",
55    "\nProof-Profile: lace-iltp-peer-proof-v3\nProof-Role: side0\n",
56    "šŸ–§: B.svyLzSM7ffc91i~XDbkMnuOsdjsw_6GrXpTSckqHlpO.H3\nData-Length: 0\n\n\n",
57    // Policy announcement and its answers.
58    "::policy B.",
59    ".H3\n::policy-known\n",
60    "::policy-missing\n",
61    // Listings, projected fields and symbols.
62    "\n::offers\nS.",
63    ".H3\n\tBy: V.",
64    ".H3\n\t",
65    ": ",
66    "\nP.",
67    ".H3\n\t:Length: ",
68    "\n\t:Type: S\n\tData-Length: ",
69    "\n\tMark: ",
70    "\n\n::offers-unshared\n",
71    "::symbols\n",
72    " 1\n\n",
73    // Responses.
74    "::response\nšŸ–§: S.",
75    ".H3\nBy: V.",
76    ".H3\nMark: ",
77    "\nšŸ–§: P.",
78    ".H3\n",
79    "\nšŸ–§: B.",
80    ".H3\nData-Length: ",
81    "\n\n",
82    // Later-round changes and requests.
83    "::offers-changed\n+ S.",
84    ".H3\n- S.",
85    ".H3\n\n",
86    "::want\nS.",
87    ".H3\nP.",
88    ".H3\n\n",
89    // Fence and an unchanged round.
90    "\n::round-end\n::offers-changed\n\n::want\n\n",
91)
92.as_bytes();
93
94/// One direction's stream encoding, named by its preface token.
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96pub enum StreamEncoding {
97    /// Plain ILTP bytes. Always available and implied by every offer.
98    Identity,
99    /// Raw deflate (RFC 1951) with a 4 KiB window, sync flushes and the
100    /// preset dictionary.
101    #[cfg(feature = "deflate")]
102    Deflate12c,
103    /// Magicless zstd (RFC 8878) with at most a 1 MiB window, no checksum,
104    /// content size or dictionary ID, and the raw-content preset dictionary.
105    #[cfg(feature = "zstd")]
106    Zstd20c,
107}
108
109impl StreamEncoding {
110    /// Compiled-in compressed encodings, most preferred first.
111    pub const COMPILED: &'static [Self] = &[
112        #[cfg(feature = "zstd")]
113        Self::Zstd20c,
114        #[cfg(feature = "deflate")]
115        Self::Deflate12c,
116    ];
117
118    pub fn token(self) -> &'static str {
119        match self {
120            Self::Identity => "identity",
121            #[cfg(feature = "deflate")]
122            Self::Deflate12c => "deflate12c",
123            #[cfg(feature = "zstd")]
124            Self::Zstd20c => "zstd20c",
125        }
126    }
127
128    /// Parse a token; tokens unknown to this build return `None`.
129    pub fn from_token(token: &str) -> Option<Self> {
130        match token {
131            "identity" => Some(Self::Identity),
132            #[cfg(feature = "deflate")]
133            "deflate12c" => Some(Self::Deflate12c),
134            #[cfg(feature = "zstd")]
135            "zstd20c" => Some(Self::Zstd20c),
136            _ => None,
137        }
138    }
139
140    /// Parse one configured token list: the encode preference and decode set
141    /// of a carrier. Tokens unknown to this build, `identity`, and repeats are
142    /// errors.
143    pub fn parse_list<'a>(
144        tokens: impl IntoIterator<Item = &'a str>,
145    ) -> Result<Vec<Self>, &'static str> {
146        let encodings = tokens
147            .into_iter()
148            .map(|token| {
149                Self::from_token(token)
150                    .ok_or("stream encoding token is unknown or not compiled into this build")
151            })
152            .collect::<Result<Vec<_>, _>>()?;
153        StreamOffers::new(&encodings)?;
154        Ok(encodings)
155    }
156
157    /// The encoding of one direction: the first entry of the sender's
158    /// `Encode` list that the receiver's `Decode` list contains, else
159    /// `Identity`, which both lists imply.
160    pub fn select(sender_encode: &[Self], receiver_decode: &[Self]) -> Self {
161        sender_encode
162            .iter()
163            .copied()
164            .find(|encoding| *encoding == Self::Identity || receiver_decode.contains(encoding))
165            .unwrap_or(Self::Identity)
166    }
167}
168
169/// The 065 offers one preface carries, restricted to encodings this build
170/// implements. `identity` ends both lists implicitly and is never listed.
171#[derive(Debug, Clone, Default, PartialEq, Eq)]
172pub struct StreamOffers {
173    /// Encodings the sender decodes, in ascending token order.
174    decode: Vec<StreamEncoding>,
175    /// Encodings the sender produces, most preferred first.
176    encode: Vec<StreamEncoding>,
177}
178
179impl StreamOffers {
180    /// The offers of a carrier configured with one ordered list, used as its
181    /// encode preference and its decode set.
182    pub fn new(encodings: &[StreamEncoding]) -> Result<Self, &'static str> {
183        if encodings.contains(&StreamEncoding::Identity) {
184            return Err("stream encodings must not list identity, which is always implied");
185        }
186        for (index, encoding) in encodings.iter().enumerate() {
187            if encodings[..index].contains(encoding) {
188                return Err("stream encodings must not repeat a token");
189            }
190        }
191        let mut decode = encodings.to_vec();
192        decode.sort_by_key(|encoding| encoding.token());
193        Ok(Self {
194            decode,
195            encode: encodings.to_vec(),
196        })
197    }
198
199    /// The encodings of validated preface tokens. Tokens this build does not
200    /// implement cannot match a local list, so dropping them keeps selection.
201    pub(super) fn known(tokens: Vec<&str>) -> Vec<StreamEncoding> {
202        tokens
203            .into_iter()
204            .filter_map(StreamEncoding::from_token)
205            .collect()
206    }
207
208    pub(super) fn from_lists(decode: Vec<StreamEncoding>, encode: Vec<StreamEncoding>) -> Self {
209        Self { decode, encode }
210    }
211
212    /// Encodings the sender decodes: its preface's `Decode` field.
213    pub fn decode(&self) -> &[StreamEncoding] {
214        &self.decode
215    }
216
217    /// Encodings the sender produces, most preferred first: its preface's
218    /// `Encode` field.
219    pub fn encode(&self) -> &[StreamEncoding] {
220        &self.encode
221    }
222
223    /// The encoding of the direction from `sender` to `receiver`.
224    pub fn select(sender: &Self, receiver: &Self) -> StreamEncoding {
225        StreamEncoding::select(&sender.encode, &receiver.decode)
226    }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub enum StreamEncodingError {
231    /// Malformed encoded peer input, including a declared window above the
232    /// token's maximum or a back-reference beyond retained history: the
233    /// window plus, for zlib, output already written by the same decode
234    /// call. Maps to `PeerProtocol`.
235    Malformed(String),
236    /// Local encoder or decoder failure: `Integration`.
237    Local(String),
238}
239
240impl fmt::Display for StreamEncodingError {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        match self {
243            Self::Malformed(detail) => write!(f, "malformed ILTP stream encoding: {detail}"),
244            Self::Local(detail) => write!(f, "ILTP stream encoding failed: {detail}"),
245        }
246    }
247}
248
249impl std::error::Error for StreamEncodingError {}
250
251/// Encoder for one direction of one logical session.
252pub struct StreamEncoder(Encoder);
253
254enum Encoder {
255    Identity,
256    #[cfg(feature = "deflate")]
257    Deflate(deflate::Encoder),
258    #[cfg(feature = "zstd")]
259    Zstd(zstd::Encoder),
260}
261
262// Without compressed encodings, levels have no use.
263#[cfg_attr(
264    not(any(feature = "deflate", feature = "zstd")),
265    allow(unused_variables)
266)]
267impl StreamEncoder {
268    /// Fresh encoder at the default level: deflate 9, zstd 3.
269    pub fn new(encoding: StreamEncoding) -> Result<Self, StreamEncodingError> {
270        let level = match encoding {
271            StreamEncoding::Identity => 0,
272            #[cfg(feature = "deflate")]
273            StreamEncoding::Deflate12c => deflate::DEFAULT_LEVEL,
274            #[cfg(feature = "zstd")]
275            StreamEncoding::Zstd20c => zstd::DEFAULT_LEVEL,
276        };
277        Self::with_level(encoding, level)
278    }
279
280    /// Fresh encoder at a sender-chosen level. The decoder needs only the
281    /// encoding. `Identity` ignores the level.
282    pub fn with_level(encoding: StreamEncoding, level: i32) -> Result<Self, StreamEncodingError> {
283        Ok(Self(match encoding {
284            StreamEncoding::Identity => Encoder::Identity,
285            #[cfg(feature = "deflate")]
286            StreamEncoding::Deflate12c => Encoder::Deflate(deflate::Encoder::new(level)?),
287            #[cfg(feature = "zstd")]
288            StreamEncoding::Zstd20c => Encoder::Zstd(zstd::Encoder::new(level)?),
289        }))
290    }
291
292    /// Encode a prefix of `input` into at most `output.len()` bytes.
293    ///
294    /// `Write` takes plaintext, `Flush` completes all plaintext written so far
295    /// so the peer can decode it from the wire bytes alone (every batch end),
296    /// and `End` writes the end mark, after which the encoder takes nothing
297    /// more. Callers repeat a step with fresh output room until it is done;
298    /// a flushing step needs at least [`ENCODE_MIN_OUTPUT`] bytes of room.
299    pub fn encode(
300        &mut self,
301        input: &[u8],
302        mode: EncodeMode,
303        output: &mut [u8],
304    ) -> Result<EncodeProgress, StreamEncodingError> {
305        match &mut self.0 {
306            Encoder::Identity => {
307                let count = match mode {
308                    EncodeMode::Write => input.len().min(output.len()),
309                    EncodeMode::Flush | EncodeMode::End => 0,
310                };
311                output[..count].copy_from_slice(&input[..count]);
312                Ok(EncodeProgress {
313                    consumed: count,
314                    produced: count,
315                    done: mode != EncodeMode::Write || count == input.len(),
316                })
317            }
318            #[cfg(feature = "deflate")]
319            Encoder::Deflate(encoder) => encoder.encode(input, mode, output),
320            #[cfg(feature = "zstd")]
321            Encoder::Zstd(encoder) => encoder.encode(input, mode, output),
322        }
323    }
324
325    /// Whether this encoder passes plaintext through unchanged.
326    pub fn is_identity(&self) -> bool {
327        matches!(self.0, Encoder::Identity)
328    }
329}
330
331/// The step one [`StreamEncoder::encode`] call takes.
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub enum EncodeMode {
334    Write,
335    Flush,
336    End,
337}
338
339/// Output room a flushing [`StreamEncoder::encode`] step needs: zlib repeats
340/// sync-flush markers when a flush ends with no room left.
341pub const ENCODE_MIN_OUTPUT: usize = 16;
342
343/// Bytes one [`StreamEncoder::encode`] call took and wrote, and whether its
344/// step is complete: all input taken for `Write`, everything flushed or ended
345/// for `Flush` and `End`.
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347pub struct EncodeProgress {
348    pub consumed: usize,
349    pub produced: usize,
350    pub done: bool,
351}
352
353/// Bytes one [`StreamDecoder::decode`] call took from its input and wrote to
354/// its output.
355#[derive(Debug, Clone, Copy, PartialEq, Eq)]
356pub struct DecodeProgress {
357    pub consumed: usize,
358    pub produced: usize,
359}
360
361/// Decoder for one direction of one logical session.
362pub struct StreamDecoder(Decoder);
363
364enum Decoder {
365    Identity,
366    #[cfg(feature = "deflate")]
367    Deflate(deflate::Decoder),
368    #[cfg(feature = "zstd")]
369    Zstd(zstd::Decoder),
370}
371
372impl StreamDecoder {
373    pub fn new(encoding: StreamEncoding) -> Result<Self, StreamEncodingError> {
374        Ok(Self(match encoding {
375            StreamEncoding::Identity => Decoder::Identity,
376            #[cfg(feature = "deflate")]
377            StreamEncoding::Deflate12c => Decoder::Deflate(deflate::Decoder::new()?),
378            #[cfg(feature = "zstd")]
379            StreamEncoding::Zstd20c => Decoder::Zstd(zstd::Decoder::new()?),
380        }))
381    }
382
383    /// Decode a prefix of `input` into at most `output.len()` bytes.
384    ///
385    /// Bytes not consumed stay with the caller. Zero progress means the
386    /// decoder needs more input or more output space. After the end mark the
387    /// decoder consumes nothing, so the input following it remains raw.
388    pub fn decode(
389        &mut self,
390        input: &[u8],
391        output: &mut [u8],
392    ) -> Result<DecodeProgress, StreamEncodingError> {
393        match &mut self.0 {
394            Decoder::Identity => {
395                let count = input.len().min(output.len());
396                output[..count].copy_from_slice(&input[..count]);
397                Ok(DecodeProgress {
398                    consumed: count,
399                    produced: count,
400                })
401            }
402            #[cfg(feature = "deflate")]
403            Decoder::Deflate(decoder) => decoder.decode(input, output),
404            #[cfg(feature = "zstd")]
405            Decoder::Zstd(decoder) => decoder.decode(input, output),
406        }
407    }
408
409    /// Whether the encoding is at its end: always for `Identity`, after the
410    /// end mark for a compressed encoding.
411    pub fn is_ended(&self) -> bool {
412        match &self.0 {
413            Decoder::Identity => true,
414            #[cfg(feature = "deflate")]
415            Decoder::Deflate(decoder) => decoder.is_ended(),
416            #[cfg(feature = "zstd")]
417            Decoder::Zstd(decoder) => decoder.is_ended(),
418        }
419    }
420}