ls_qpack/
encoder.rs

1// Copyright 2022 Biagio Festa
2
3//! Module for encoding operations.
4//!
5//! The main struct of this module is [`Encoder`].
6//!
7//! # Examples
8//!
9//! ## Only Static Table
10//! ```
11//! use ls_qpack::encoder::Encoder;
12//! use ls_qpack::StreamId;
13//!
14//! let (enc_hdr, enc_stream) = Encoder::new()
15//!     .encode_all(
16//!         StreamId::new(0),
17//!         [(":status", "404"), (":method", "connect")],
18//!     )
19//!     .unwrap()
20//!     .into();
21//!
22//! // Using only static table. We don't expect stream data.
23//! assert_eq!(enc_stream.len(), 0);
24//! println!("Encoded data: {:?}", enc_hdr);
25//! ```
26use crate::header::TryIntoHeader;
27use crate::StreamId;
28use std::collections::HashMap;
29use std::fmt::Debug;
30use std::fmt::Display;
31use std::marker::PhantomPinned;
32use std::pin::Pin;
33
34/// Error during encoding operations.
35pub struct EncoderError;
36
37/// A QPACK encoder.
38pub struct Encoder {
39    inner: Pin<Box<InnerEncoder>>,
40    seqnos: HashMap<StreamId, u32>,
41}
42
43impl Encoder {
44    /// Creates a new encoder.
45    ///
46    /// If not configured, this encoder will only make use of a static table.
47    ///
48    /// Once peer's settings has been received, you might want to allocate
49    /// the dynamic table by means of [`Self::configure`].
50    #[inline]
51    pub fn new() -> Self {
52        Self {
53            inner: InnerEncoder::new(),
54            seqnos: HashMap::new(),
55        }
56    }
57
58    /// Sets dynamic table size and it applies peer's settings.
59    ///
60    /// # Returns
61    /// SDTC instruction (Set Dynamic Table Capacity) data. This should be
62    /// transmitted to the peer via encoder stream.
63    ///
64    /// # Notes
65    ///   * `dyn_table_size` can be `0` to avoid dynamic table.
66    ///   * `dyn_table_size` cannot be larger than `max_table_size`.
67    #[inline]
68    pub fn configure(
69        &mut self,
70        max_table_size: u32,
71        dyn_table_size: u32,
72        max_blocked_streams: u32,
73    ) -> Result<SDTCInstruction, EncoderError> {
74        self.inner
75            .as_mut()
76            .init(max_table_size, dyn_table_size, max_blocked_streams)
77            .map(SDTCInstruction)
78    }
79
80    /// Encodes an entire header block (a list of headers).
81    ///
82    /// # Returns
83    /// The encoded data (see [`BuffersEncoded`]).
84    ///
85    /// # Examples
86    /// ```
87    /// use ls_qpack::encoder::Encoder;
88    /// use ls_qpack::StreamId;
89    ///
90    /// let mut encoder = Encoder::new();
91    /// let (enc_hdr, enc_stream) = encoder
92    ///     .encode_all(
93    ///         StreamId::new(0),
94    ///         [(":status", "404"), (":method", "connect")],
95    ///     )
96    ///     .unwrap()
97    ///     .into();
98    /// ```
99    pub fn encode_all<I, H>(
100        &mut self,
101        stream_id: StreamId,
102        headers: I,
103    ) -> Result<BuffersEncoded, EncoderError>
104    where
105        I: IntoIterator<Item = H>,
106        H: TryIntoHeader,
107    {
108        let mut encoding = self.encoding(stream_id);
109
110        for header in headers {
111            encoding.append(header)?;
112        }
113
114        encoding.encode()
115    }
116
117    /// Encodes a list of headers in a sequential fashion way.
118    ///
119    /// This method is similar to [`Self::encode_all`]. However, instead of
120    /// providing the entire list of header, it is possible to append a header step by step.
121    ///
122    /// See [`EncodingBlock`].
123    ///
124    /// # Examples
125    /// ```
126    /// use ls_qpack::encoder::Encoder;
127    /// use ls_qpack::StreamId;
128    ///
129    /// let mut encoder = Encoder::new();
130    /// let mut encoding_block = encoder.encoding(StreamId::new(0));
131    ///
132    /// encoding_block.append((":status", "404"));
133    /// encoding_block.append((":method", "connect"));
134    ///
135    /// let (enc_hdr, enc_stream) = encoding_block.encode().unwrap().into();
136    /// ```
137    #[inline]
138    pub fn encoding(&mut self, stream_id: StreamId) -> EncodingBlock<'_> {
139        let seqno = {
140            let seqno_ref = self.seqnos.entry(stream_id).or_default();
141            std::mem::replace(seqno_ref, seqno_ref.wrapping_add(1))
142        };
143
144        EncodingBlock::new(self, stream_id, seqno)
145    }
146
147    /// Return estimated compression ratio until this point.
148    ///
149    /// Compression ratio is defined as size of the output divided by the size of the
150    /// input, where output includes both header blocks and instructions sent
151    /// on the encoder stream.
152    #[inline]
153    pub fn ratio(&self) -> f32 {
154        self.inner.as_ref().ratio()
155    }
156
157    #[inline]
158    fn inner_mut(&mut self) -> Pin<&mut InnerEncoder> {
159        self.inner.as_mut()
160    }
161}
162
163impl Default for Encoder {
164    fn default() -> Self {
165        Self::new()
166    }
167}
168
169/// SDTC instruction
170///
171/// *Set Dynamic Table Capacity* data.
172/// It is a buffer of data to be fed to the peer's decoder.
173#[derive(Debug)]
174pub struct SDTCInstruction(Box<[u8]>);
175
176impl SDTCInstruction {
177    /// Returns the buffer data.
178    #[inline]
179    pub fn data(&self) -> &[u8] {
180        &self.0
181    }
182
183    /// Takes the ownership returning the inner buffer data.
184    #[inline]
185    pub fn take(self) -> Box<[u8]> {
186        self.0
187    }
188}
189
190impl AsRef<[u8]> for SDTCInstruction {
191    #[inline]
192    fn as_ref(&self) -> &[u8] {
193        self.data()
194    }
195}
196
197impl From<SDTCInstruction> for Box<[u8]> {
198    fn from(sdtc_instruction: SDTCInstruction) -> Self {
199        sdtc_instruction.0
200    }
201}
202
203/// An encoding operation for a headers block.
204///
205/// This is the result of [`Encoder::encoding`] method.
206pub struct EncodingBlock<'a>(&'a mut Encoder);
207
208impl<'a> EncodingBlock<'a> {
209    fn new(encoder: &'a mut Encoder, stream_id: StreamId, seqno: u32) -> Self {
210        encoder
211            .inner_mut()
212            .start_header_block(stream_id, seqno)
213            .map(|()| Self(encoder))
214            .unwrap() // unwrap is safe here because no other start-block can happen
215    }
216
217    /// Appends a header to encode.
218    pub fn append<H>(&mut self, header: H) -> Result<&mut Self, EncoderError>
219    where
220        H: TryIntoHeader,
221    {
222        self.0.inner_mut().encode(header).map(|()| self)
223    }
224
225    /// Encodes the header block.
226    pub fn encode(self) -> Result<BuffersEncoded, EncoderError> {
227        self.0
228            .inner_mut()
229            .end_header_block()
230            .map(|(header, stream)| BuffersEncoded {
231                header: header.into_boxed_slice(),
232                stream: stream.into_boxed_slice(),
233            })
234    }
235}
236
237/// The result of the encoding operation.
238///
239/// This is the result of [`Encoder::encode_all`] or [`EncodingBlock::encode`].
240pub struct BuffersEncoded {
241    header: Box<[u8]>,
242    stream: Box<[u8]>,
243}
244
245impl BuffersEncoded {
246    /// The data buffer of encoded headers.
247    pub fn header(&self) -> &[u8] {
248        &self.header
249    }
250
251    /// The buffer of the stream data for the decoder.
252    pub fn stream(&self) -> &[u8] {
253        &self.stream
254    }
255
256    pub fn take(self) -> (Box<[u8]>, Box<[u8]>) {
257        self.into()
258    }
259}
260
261impl From<BuffersEncoded> for (Box<[u8]>, Box<[u8]>) {
262    fn from(buffers_encoded: BuffersEncoded) -> Self {
263        (buffers_encoded.header, buffers_encoded.stream)
264    }
265}
266
267struct InnerEncoder {
268    encoder: ls_qpack_sys::lsqpack_enc,
269    enc_buffer: Vec<u8>,
270    hdr_buffer: Vec<u8>,
271    _marker: PhantomPinned,
272}
273
274impl InnerEncoder {
275    fn new() -> Pin<Box<Self>> {
276        let mut this = Box::new(Self {
277            encoder: ls_qpack_sys::lsqpack_enc::default(),
278            enc_buffer: Vec::new(),
279            hdr_buffer: Vec::new(),
280            _marker: PhantomPinned,
281        });
282
283        unsafe {
284            ls_qpack_sys::lsqpack_enc_preinit(&mut this.encoder, std::ptr::null_mut());
285        }
286
287        Box::into_pin(this)
288    }
289
290    fn init(
291        self: Pin<&mut Self>,
292        max_table_size: u32,
293        dyn_table_size: u32,
294        max_blocked_streams: u32,
295    ) -> Result<Box<[u8]>, EncoderError> {
296        let this = unsafe { self.get_unchecked_mut() };
297
298        let mut buffer = vec![0; ls_qpack_sys::LSQPACK_LONGEST_SDTC as usize];
299        let mut sdtc_buffer_size = buffer.len();
300
301        let result = unsafe {
302            ls_qpack_sys::lsqpack_enc_init(
303                &mut this.encoder,
304                std::ptr::null_mut(),
305                max_table_size,
306                dyn_table_size,
307                max_blocked_streams,
308                ls_qpack_sys::lsqpack_enc_opts_LSQPACK_ENC_OPT_STAGE_2,
309                buffer.as_mut_ptr(),
310                &mut sdtc_buffer_size,
311            )
312        };
313
314        if result == 0 {
315            buffer.truncate(sdtc_buffer_size);
316            Ok(buffer.into_boxed_slice())
317        } else {
318            Err(EncoderError)
319        }
320    }
321
322    /// Returns error if another block is started before completing the previous.
323    fn start_header_block(
324        self: Pin<&mut Self>,
325        stream_id: StreamId,
326        seqno: u32,
327    ) -> Result<(), EncoderError> {
328        let this = unsafe { self.get_unchecked_mut() };
329
330        let result = unsafe {
331            ls_qpack_sys::lsqpack_enc_start_header(&mut this.encoder, stream_id.value(), seqno)
332        };
333
334        if result == 0 {
335            this.enc_buffer.clear();
336            this.hdr_buffer.clear();
337
338            Ok(())
339        } else {
340            Err(EncoderError)
341        }
342    }
343
344    fn encode<H>(self: Pin<&mut Self>, header: H) -> Result<(), EncoderError>
345    where
346        H: TryIntoHeader,
347    {
348        const BUFFER_SIZE: usize = 1024;
349
350        let mut header = header.try_into_header().map_err(|_| EncoderError)?;
351
352        let this = unsafe { self.get_unchecked_mut() };
353
354        // TODO(bfesta): we want to better handle capacity.
355        // In particular, if the encoding op fails because of buffer too small we want report that, at least.
356
357        let enc_buffer_offset = this.enc_buffer.len();
358        this.enc_buffer.resize(enc_buffer_offset + BUFFER_SIZE, 0);
359
360        let hdr_buffer_offset = this.hdr_buffer.len();
361        this.hdr_buffer.resize(hdr_buffer_offset + BUFFER_SIZE, 0);
362
363        let mut enc_buffer_size = this.enc_buffer.len() - enc_buffer_offset;
364        let mut hdr_buffer_size = this.hdr_buffer.len() - hdr_buffer_offset;
365
366        let result = unsafe {
367            ls_qpack_sys::lsqpack_enc_encode(
368                &mut this.encoder,
369                this.enc_buffer.as_mut_ptr().add(enc_buffer_offset),
370                &mut enc_buffer_size,
371                this.hdr_buffer.as_mut_ptr().add(hdr_buffer_offset),
372                &mut hdr_buffer_size,
373                header.build_lsxpack_header().as_ref(),
374                0,
375            )
376        };
377
378        if result == ls_qpack_sys::lsqpack_enc_status_LQES_OK {
379            this.enc_buffer
380                .truncate(enc_buffer_offset + enc_buffer_size);
381            this.hdr_buffer
382                .truncate(hdr_buffer_offset + hdr_buffer_size);
383
384            Ok(())
385        } else {
386            this.enc_buffer.truncate(enc_buffer_offset);
387            this.hdr_buffer.truncate(hdr_buffer_offset);
388
389            Err(EncoderError)
390        }
391    }
392
393    /// Finalize the encoded header block.
394    ///
395    /// It computes the header prefix and return the encoded buffers.
396    /// It returns a buffer pair:
397    ///   * Buffer of encoded header
398    ///   * Buffer of encoded bytes to write on the encoder stream.
399    fn end_header_block(self: Pin<&mut Self>) -> Result<(Vec<u8>, Vec<u8>), EncoderError> {
400        let this = unsafe { self.get_unchecked_mut() };
401
402        let max_prefix_len =
403            unsafe { ls_qpack_sys::lsqpack_enc_header_block_prefix_size(&this.encoder) };
404
405        let mut hdr_block = vec![0; max_prefix_len + this.hdr_buffer.len()];
406
407        let hdr_prefix_len = unsafe {
408            ls_qpack_sys::lsqpack_enc_end_header(
409                &mut this.encoder,
410                hdr_block.as_mut_ptr(),
411                max_prefix_len,
412                std::ptr::null_mut(),
413            )
414        };
415
416        if hdr_prefix_len > 0 {
417            hdr_block.truncate(hdr_prefix_len as usize);
418            hdr_block.extend_from_slice(&this.hdr_buffer);
419
420            Ok((hdr_block, std::mem::take(&mut this.enc_buffer)))
421        } else {
422            Err(EncoderError)
423        }
424    }
425
426    fn ratio(self: Pin<&Self>) -> f32 {
427        unsafe { ls_qpack_sys::lsqpack_enc_ratio(&self.encoder) }
428    }
429}
430
431impl Drop for InnerEncoder {
432    fn drop(&mut self) {
433        unsafe { ls_qpack_sys::lsqpack_enc_cleanup(&mut self.encoder) }
434    }
435}
436
437impl Debug for EncoderError {
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        f.debug_struct("EncoderError").finish()
440    }
441}
442
443impl Display for EncoderError {
444    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445        Debug::fmt(self, f)
446    }
447}
448
449impl std::error::Error for EncoderError {}
450
451#[cfg(test)]
452mod tests {
453    use super::Encoder;
454    use super::StreamId;
455
456    #[test]
457    fn test_encoder_determinism_static() {
458        let mut encoder = Encoder::new();
459
460        let results = (0..1024)
461            .map(|_| {
462                encoder
463                    .encode_all(StreamId::new(0), utilities::HEADERS_LIST_1)
464                    .unwrap()
465            })
466            .collect::<Vec<_>>();
467
468        assert!(results.iter().all(|b| b.header() == results[0].header()));
469        assert!(results.iter().all(|b| b.stream().is_empty()));
470    }
471
472    mod utilities {
473        pub(super) const HEADERS_LIST_1: [(&str, &str); 2] =
474            [(":status", "404"), (":method", "connect")];
475    }
476}