Skip to main content

ls_qpack_rs/
decoder.rs

1// Copyright 2022 Biagio Festa
2
3//! Module for decoding operations.
4//!
5//! The main struct of this module is [`Decoder`].
6//!
7//! # Example
8//!
9//! ## Only Static Table
10//! ```
11//! use ls_qpack_rs::decoder::Decoder;
12//! use ls_qpack_rs::encoder::Encoder;
13//! use ls_qpack_rs::StreamId;
14//!
15//! let hdr_encoded = Encoder::new()
16//!     .encode_all(StreamId::new(0), [(":status", "404")])
17//!     .unwrap()
18//!     .take()
19//!     .0;
20//!
21//! let header = Decoder::new(0, 0)
22//!     .decode(StreamId::new(0), hdr_encoded)
23//!     .unwrap()
24//!     .take();
25//!
26//! println!("Headers: {:?}", header);
27//! ```
28use crate::Header;
29use crate::StreamId;
30use std::collections::hash_map;
31use std::collections::HashMap;
32use std::fmt::Debug;
33use std::fmt::Display;
34use std::marker::PhantomPinned;
35use std::pin::Pin;
36
37/// The kind of decoder error that occurred.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[non_exhaustive]
40pub enum DecoderErrorKind {
41    /// The stream ID already has a pending header block.
42    DuplicateStreamId,
43    /// The C decoder returned an error during decoding.
44    DecodeFailed,
45    /// The C decoder returned an error when processing encoder stream data.
46    FeedFailed,
47    /// An error occurred while processing a decoded header (e.g., invalid UTF-8).
48    InvalidHeader,
49}
50
51/// Error during decoding operations.
52pub struct DecoderError {
53    kind: DecoderErrorKind,
54}
55
56impl DecoderError {
57    /// Returns the kind of decoder error.
58    pub fn kind(&self) -> DecoderErrorKind {
59        self.kind
60    }
61
62    fn new(kind: DecoderErrorKind) -> Self {
63        Self { kind }
64    }
65}
66
67/// The result of the decoding operation.
68///
69/// This is the result of [`Decoder::decode`] when decoding completes successfully.
70#[derive(Debug)]
71pub struct BuffersDecoded {
72    headers: Vec<Header>,
73    stream: Box<[u8]>,
74}
75
76impl BuffersDecoded {
77    /// The data buffer of decoded headers.
78    pub fn headers(&self) -> &Vec<Header> {
79        &self.headers
80    }
81
82    /// The buffer of the stream data for the encoder.
83    pub fn stream(&self) -> &[u8] {
84        &self.stream
85    }
86}
87
88/// The result of a decode operation.
89///
90/// Generally, this is function's output for [`Decoder::decode`].
91///
92/// When header data are decoded,
93#[derive(Debug)]
94pub enum DecoderOutput {
95    /// The header block has been correctly decoded.
96    Done(BuffersDecoded),
97
98    /// The decoding stream is blocked.
99    /// More data are needed in order to proceed with decoding operation.
100    /// Generally, you need to feed the encoder via [`Decoder::feed`].
101    BlockedStream,
102}
103
104impl DecoderOutput {
105    /// If the result is unblocked, it will return `Some(Vec<header>)`.
106    /// Otherwise `None`.
107    pub fn take(self) -> Option<BuffersDecoded> {
108        match self {
109            Self::Done(v) => Some(v),
110            Self::BlockedStream => None,
111        }
112    }
113
114    /// Checks whether the result is blocked or not.
115    pub fn is_blocked(&self) -> bool {
116        matches!(self, Self::BlockedStream)
117    }
118}
119
120/// A QPACK decoder.
121pub struct Decoder {
122    inner: Pin<Box<InnerDecoder>>,
123}
124
125impl Decoder {
126    /// Creates a new decoder.
127    ///
128    /// Specify the size of the dynamic table (it might be `0`).
129    /// And the max number of blocked streams.
130    pub fn new(dyn_table_size: u32, max_blocked_streams: u32) -> Self {
131        Self {
132            inner: InnerDecoder::new(dyn_table_size, max_blocked_streams),
133        }
134    }
135
136    /// Decodes header data.
137    ///
138    /// It produces an output, see [`DecoderOutput`].
139    ///
140    /// It might happen that the data provided to this method are not sufficient in order
141    /// to complete the decoding operation.
142    /// In that case, more data are needed from the encoder stream (via [`Decoder::feed`]).
143    ///
144    /// # Examples
145    /// ```
146    /// use ls_qpack_rs::decoder::Decoder;
147    /// use ls_qpack_rs::StreamId;
148    ///
149    /// # use ls_qpack_rs::TryIntoHeader;
150    /// # let (data, _) = ls_qpack_rs::encoder::Encoder::new().encode_all(0.into(), [("foo", "bar")]).unwrap().into();
151    ///
152    ///
153    /// let mut decoder = Decoder::new(0, 0);
154    /// let output = decoder.decode(StreamId::new(0), data).unwrap();
155    /// ```
156    pub fn decode<D>(&mut self, stream_id: StreamId, data: D) -> Result<DecoderOutput, DecoderError>
157    where
158        D: AsRef<[u8]>,
159    {
160        self.inner
161            .as_mut()
162            .feed_header_data(stream_id, data.as_ref())
163    }
164
165    /// Feeds data from encoder's buffer stream.
166    pub fn feed<D>(&mut self, data: D) -> Result<(), DecoderError>
167    where
168        D: AsRef<[u8]>,
169    {
170        self.inner.as_mut().feed_encoder_data(data.as_ref())
171    }
172
173    /// Checks whether a header block for a `StreamId` has become unblocked.
174    ///
175    /// # Returns
176    ///   * `None` if the `StreamId` has never been fed.
177    ///   * `Some` if the `StreamId` produced an [`DecoderOutput`].
178    pub fn unblocked(
179        &mut self,
180        stream_id: StreamId,
181    ) -> Option<Result<DecoderOutput, DecoderError>> {
182        self.inner.as_mut().process_decoded_data(stream_id)
183    }
184}
185
186struct InnerDecoder {
187    decoder: ls_qpack_rs_sys::lsqpack_dec,
188    header_blocks: HashMap<StreamId, Pin<Box<callbacks::HeaderBlockCtx>>>,
189    _marker: PhantomPinned,
190}
191
192impl InnerDecoder {
193    fn new(dyn_table_size: u32, max_blocked_streams: u32) -> Pin<Box<Self>> {
194        let mut this = Box::new(Self {
195            decoder: ls_qpack_rs_sys::lsqpack_dec::default(),
196            header_blocks: HashMap::new(),
197            _marker: PhantomPinned,
198        });
199
200        // SAFETY: `this.decoder` is a valid, default-initialized `lsqpack_dec` struct.
201        // The null second argument indicates no logging context. `this` is a live Box
202        // allocation so `&mut this.decoder` is a valid pointer. The callback table
203        // `HSET_IF_CALLBACKS` is a static reference with the correct function signatures.
204        unsafe {
205            ls_qpack_rs_sys::lsqpack_dec_init(
206                &mut this.decoder,
207                std::ptr::null_mut(),
208                dyn_table_size,
209                max_blocked_streams,
210                &callbacks::HSET_IF_CALLBACKS,
211                0,
212            );
213        }
214
215        Box::into_pin(this)
216    }
217
218    fn feed_header_data(
219        self: Pin<&mut Self>,
220        stream_id: StreamId,
221        data: &[u8],
222    ) -> Result<DecoderOutput, DecoderError> {
223        // SAFETY: We only access plain data fields (decoder, header_blocks) through the
224        // unpinned reference — none of which are address-sensitive. PhantomPinned is never moved.
225        let this = unsafe { self.get_unchecked_mut() };
226
227        if this.header_blocks.contains_key(&stream_id) {
228            return Err(DecoderError::new(DecoderErrorKind::DuplicateStreamId));
229        }
230
231        let mut hblock_ctx =
232            callbacks::HeaderBlockCtx::new(&mut this.decoder, data.to_vec().into_boxed_slice());
233
234        let encoded_cursor = hblock_ctx.as_ref().encoded_cursor();
235        let encoded_cursor_len = encoded_cursor.len();
236        let header_block_len = encoded_cursor.len();
237        let mut cursor_after = encoded_cursor.as_ptr();
238
239        let mut buffer = vec![0; ls_qpack_rs_sys::LSQPACK_LONGEST_SDTC as usize];
240        let mut sdtc_buffer_size = buffer.len();
241
242        // SAFETY: `this.decoder` is initialized (via `lsqpack_dec_init` in `new()`).
243        // `hblock_ctx` is a valid pinned allocation cast to `*mut c_void` as the C
244        // library's opaque context. `cursor_after` points into `hblock_ctx.encoded_data`.
245        // `buffer` is a freshly allocated Vec with `LSQPACK_LONGEST_SDTC` bytes of space.
246        let result = unsafe {
247            ls_qpack_rs_sys::lsqpack_dec_header_in(
248                &mut this.decoder,
249                hblock_ctx.as_mut().as_mut_ptr() as *mut libc::c_void,
250                stream_id.value(),
251                header_block_len,
252                &mut cursor_after,
253                encoded_cursor_len,
254                buffer.as_mut_ptr(),
255                &mut sdtc_buffer_size,
256            )
257        };
258
259        match result {
260            ls_qpack_rs_sys::lsqpack_read_header_status_LQRHS_DONE => {
261                debug_assert!(!hblock_ctx.as_ref().is_blocked());
262                debug_assert!(!hblock_ctx.as_ref().is_error());
263
264                // SAFETY: Decoding is complete (LQRHS_DONE), so the C library no longer holds
265                // references into hblock_ctx. It is safe to unpin and consume it.
266                let hblock_ctx = unsafe { Pin::into_inner_unchecked(hblock_ctx) };
267
268                buffer.truncate(sdtc_buffer_size);
269                Ok(DecoderOutput::Done(BuffersDecoded {
270                    headers: hblock_ctx.decoded_headers(),
271                    stream: buffer.into_boxed_slice(),
272                }))
273            }
274
275            ls_qpack_rs_sys::lsqpack_read_header_status_LQRHS_BLOCKED
276            | ls_qpack_rs_sys::lsqpack_read_header_status_LQRHS_NEED => {
277                // SAFETY: Both `cursor_after` and the base pointer come from the same
278                // allocation (`hblock_ctx.encoded_data`). `cursor_after` was advanced by
279                // the C library and is guaranteed to be >= the base pointer.
280                let offset = unsafe {
281                    cursor_after.offset_from(hblock_ctx.as_ref().encoded_cursor().as_ptr())
282                };
283
284                hblock_ctx.as_mut().advance_cursor(offset as usize);
285                hblock_ctx.as_mut().set_blocked(true);
286                this.header_blocks.insert(stream_id, hblock_ctx);
287
288                Ok(DecoderOutput::BlockedStream)
289            }
290
291            _ => Err(DecoderError::new(DecoderErrorKind::DecodeFailed)),
292        }
293    }
294
295    fn feed_encoder_data(self: Pin<&mut Self>, data: &[u8]) -> Result<(), DecoderError> {
296        // SAFETY: We only access plain data fields — none are address-sensitive.
297        let this = unsafe { self.get_unchecked_mut() };
298
299        // SAFETY: `this.decoder` is initialized. `data.as_ptr()` and `data.len()` describe
300        // a valid byte slice. The C function reads exactly `data.len()` bytes.
301        let result = unsafe {
302            ls_qpack_rs_sys::lsqpack_dec_enc_in(&mut this.decoder, data.as_ptr(), data.len())
303        };
304
305        if result == 0 {
306            Ok(())
307        } else {
308            Err(DecoderError::new(DecoderErrorKind::FeedFailed))
309        }
310    }
311
312    fn process_decoded_data(
313        self: Pin<&mut Self>,
314        stream_id: StreamId,
315    ) -> Option<Result<DecoderOutput, DecoderError>> {
316        // SAFETY: We only access plain data fields (header_blocks) — none are address-sensitive.
317        let this = unsafe { self.get_unchecked_mut() };
318
319        match this.header_blocks.entry(stream_id) {
320            hash_map::Entry::Occupied(hdbk) => {
321                if hdbk.get().as_ref().is_blocked() {
322                    debug_assert!(!hdbk.get().as_ref().is_error());
323                    return Some(Ok(DecoderOutput::BlockedStream));
324                }
325
326                let hdbk = hdbk.remove();
327
328                if hdbk.as_ref().is_error() {
329                    debug_assert!(!hdbk.as_ref().is_blocked());
330                    return Some(Err(DecoderError::new(DecoderErrorKind::InvalidHeader)));
331                }
332
333                // SAFETY: The header block is neither blocked nor in error, meaning the C
334                // library has finished processing it and no longer holds references to it.
335                // It is safe to unpin and consume it.
336                let hdbk = unsafe { Pin::into_inner_unchecked(hdbk) };
337                Some(Ok(DecoderOutput::Done(BuffersDecoded {
338                    headers: hdbk.decoded_headers(),
339                    stream: hdbk.stream_data().into_boxed_slice(),
340                })))
341            }
342
343            hash_map::Entry::Vacant(_) => None,
344        }
345    }
346}
347
348impl Drop for InnerDecoder {
349    fn drop(&mut self) {
350        // SAFETY: `self.decoder` was initialized by `lsqpack_dec_init` in `new()`.
351        // `lsqpack_dec_cleanup` frees all resources owned by the C decoder.
352        // This is called exactly once during drop.
353        unsafe { ls_qpack_rs_sys::lsqpack_dec_cleanup(&mut self.decoder) }
354    }
355}
356
357// SAFETY: The C `lsqpack_dec` struct is a self-contained state machine. It uses no
358// thread-local storage, no global mutable state, and no thread-affine resources. All
359// internal raw pointers reference memory owned by the struct itself (allocated during
360// init, freed during cleanup). The `header_blocks` HashMap owns all HeaderBlockCtx
361// values, which only contain pointers back into the decoder or into their own buffers.
362// It is safe to move an InnerDecoder to another thread.
363unsafe impl Send for InnerDecoder {}
364
365// SAFETY: All access to InnerDecoder goes through Pin<&mut Self> methods, meaning Rust's
366// borrow checker guarantees exclusive access. The public Decoder API only exposes &mut self
367// methods, so no concurrent &self access is possible.
368unsafe impl Sync for InnerDecoder {}
369
370const _: () = {
371    fn _assert_send<T: Send>() {}
372    fn _assert_sync<T: Sync>() {}
373    fn _assert_all() {
374        _assert_send::<Decoder>();
375        _assert_sync::<Decoder>();
376    }
377};
378
379mod callbacks {
380    use crate::header::HeaderError;
381    use crate::Header;
382    use std::ffi::c_char;
383    use std::marker::PhantomPinned;
384    use std::pin::Pin;
385
386    pub(super) static HSET_IF_CALLBACKS: ls_qpack_rs_sys::lsqpack_dec_hset_if =
387        ls_qpack_rs_sys::lsqpack_dec_hset_if {
388            dhi_unblocked: Some(dhi_unblocked),
389            dhi_prepare_decode: Some(dhi_prepare_decode),
390            dhi_process_header: Some(dhi_process_header),
391        };
392
393    #[derive(Debug)]
394    pub(super) struct HeaderBlockCtx {
395        decoder: *mut ls_qpack_rs_sys::lsqpack_dec,
396        encoded_data: Box<[u8]>,
397        encoded_data_offset: usize,
398        decoding_buffer: Vec<u8>,
399        header: ls_qpack_rs_sys::lsxpack_header,
400        blocked: bool,
401        error: bool,
402        stream_data: Vec<u8>,
403        decoded_headers: Vec<Header>,
404        _marker: PhantomPinned,
405    }
406
407    impl HeaderBlockCtx {
408        pub(super) fn new(
409            decoder: *mut ls_qpack_rs_sys::lsqpack_dec,
410            encoded_data: Box<[u8]>,
411        ) -> Pin<Box<Self>> {
412            debug_assert!(!decoder.is_null());
413
414            Box::pin(Self {
415                decoder,
416                encoded_data,
417                encoded_data_offset: 0,
418                decoding_buffer: Vec::new(),
419                stream_data: Vec::new(),
420                header: Default::default(),
421                blocked: false,
422                error: false,
423                decoded_headers: Default::default(),
424                _marker: PhantomPinned,
425            })
426        }
427
428        /// # Safety
429        /// Caller must ensure the returned pointer is only used while `self` remains pinned
430        /// and alive. The pointer must not be used to violate pin guarantees.
431        pub(super) unsafe fn as_mut_ptr(mut self: Pin<&mut Self>) -> *mut HeaderBlockCtx {
432            // SAFETY: We are returning a raw pointer to the pinned data. The caller
433            // (C FFI) will pass this pointer back to us in callbacks, where we re-pin it.
434            self.as_mut().get_unchecked_mut()
435        }
436
437        pub(super) fn encoded_cursor(self: Pin<&Self>) -> &[u8] {
438            debug_assert!(self.encoded_data_offset < self.encoded_data.len());
439            &self.get_ref().encoded_data[self.encoded_data_offset..]
440        }
441
442        pub(super) fn advance_cursor(self: Pin<&mut Self>, offset: usize) {
443            debug_assert!(offset <= self.encoded_data.len());
444            // SAFETY: `encoded_data_offset` is a plain usize — not address-sensitive.
445            let this = unsafe { self.get_unchecked_mut() };
446            this.encoded_data_offset += offset;
447        }
448
449        pub(super) fn set_blocked(self: Pin<&mut Self>, blocked: bool) {
450            // SAFETY: `blocked` is a plain bool — not address-sensitive.
451            let this = unsafe { self.get_unchecked_mut() };
452            this.blocked = blocked;
453        }
454
455        pub(super) fn set_stream_data(self: Pin<&mut Self>, data: &[u8]) {
456            // SAFETY: `stream_data` is an owned Vec — not address-sensitive.
457            let this = unsafe { self.get_unchecked_mut() };
458            this.stream_data = data.to_vec();
459        }
460
461        pub(super) fn enable_error(self: Pin<&mut Self>) {
462            // SAFETY: `error` is a plain bool — not address-sensitive.
463            let this = unsafe { self.get_unchecked_mut() };
464            debug_assert!(!this.error);
465            this.error = true;
466        }
467
468        pub(super) fn is_blocked(self: Pin<&Self>) -> bool {
469            self.blocked
470        }
471
472        pub(super) fn is_error(self: Pin<&Self>) -> bool {
473            self.error
474        }
475
476        pub(super) fn decoded_headers(&self) -> Vec<Header> {
477            self.decoded_headers.clone()
478        }
479
480        pub(super) fn stream_data(&self) -> Vec<u8> {
481            self.stream_data.clone()
482        }
483
484        /// Converts a `*mut c_void` (from C callbacks) back to a `Pin<&mut Self>`.
485        ///
486        /// # Safety
487        /// - `ptr` must be a valid, non-null pointer to a `HeaderBlockCtx` that was
488        ///   originally obtained from `as_mut_ptr()` on a pinned `HeaderBlockCtx`.
489        /// - The `HeaderBlockCtx` must still be alive (i.e., stored in
490        ///   `InnerDecoder::header_blocks`).
491        /// - The returned reference must not outlive the current callback invocation.
492        ///
493        /// # Lifetime
494        /// The `'static` lifetime is technically incorrect — the true lifetime is
495        /// bounded by the owning `InnerDecoder`. This is a known limitation of
496        /// `extern "C"` callbacks that receive opaque `*mut c_void` context pointers.
497        /// It is mitigated by the fact that the returned `Pin<&mut Self>` never
498        /// escapes any callback function body.
499        unsafe fn from_void_ptr(ptr: *mut libc::c_void) -> Pin<&'static mut Self> {
500            debug_assert!(!ptr.is_null());
501            // SAFETY: The pointer was obtained from `as_mut_ptr()` on a pinned Box<Self>.
502            // The C library passes it back in callbacks while the HeaderBlockCtx is alive.
503            // We re-pin it to restore the pin invariant.
504            Pin::new_unchecked(&mut *(ptr as *mut Self))
505        }
506
507        fn reset_header(self: Pin<&mut Self>) {
508            // SAFETY: `header` is a plain C struct — not address-sensitive (its internal
509            // `buf` pointer is only meaningful when set by `resize_header`).
510            let this = unsafe { self.get_unchecked_mut() };
511            this.header = Default::default()
512        }
513
514        fn resize_header(self: Pin<&mut Self>, space: u16) {
515            // SAFETY: We modify `decoding_buffer` (an owned Vec) and `header` (a plain C
516            // struct). Neither is address-sensitive. After resize, we update `header.buf`
517            // to point to the new buffer allocation.
518            let this = unsafe { self.get_unchecked_mut() };
519            this.decoding_buffer
520                .resize(space as usize, Default::default());
521
522            this.header.buf = this.decoding_buffer.as_mut_ptr() as *mut c_char;
523            this.header.val_len = space;
524        }
525
526        fn header_mut(self: Pin<&mut Self>) -> &mut ls_qpack_rs_sys::lsxpack_header {
527            // SAFETY: `header` is a plain C struct — not address-sensitive.
528            let this = unsafe { self.get_unchecked_mut() };
529            &mut this.header
530        }
531
532        fn process_header(self: Pin<&mut Self>) -> Result<(), HeaderError> {
533            // SAFETY: We access `decoding_buffer`, `header`, and `decoded_headers` — all
534            // owned data fields that are not address-sensitive.
535            let this = unsafe { self.get_unchecked_mut() };
536
537            let header = Header::with_buffer(
538                std::mem::take(&mut this.decoding_buffer).into_boxed_slice(),
539                this.header.name_offset as usize,
540                this.header.name_len as usize,
541                this.header.val_offset as usize,
542                this.header.val_len as usize,
543            )?;
544
545            this.decoded_headers.push(header);
546
547            this.header = Default::default();
548
549            Ok(())
550        }
551    }
552
553    // SAFETY: HeaderBlockCtx is a self-contained context object. Its raw pointer
554    // `decoder: *mut lsqpack_dec` points to the parent InnerDecoder's C struct,
555    // which is always valid for the lifetime of HeaderBlockCtx (it is stored in
556    // InnerDecoder's HashMap and dropped before the decoder is cleaned up). All
557    // other fields are owned Rust types. No thread-local or thread-affine state.
558    unsafe impl Send for HeaderBlockCtx {}
559
560    // SAFETY: HeaderBlockCtx is only accessed through Pin<&mut Self> or via
561    // exclusive access patterns in C callbacks (single-threaded callback dispatch).
562    // No concurrent shared access is possible.
563    unsafe impl Sync for HeaderBlockCtx {}
564
565    extern "C" fn dhi_unblocked(hblock_ctx: *mut libc::c_void) {
566        // SAFETY: The C library passes back the pointer we gave it via `as_mut_ptr()`.
567        // The HeaderBlockCtx is still alive (stored in InnerDecoder::header_blocks).
568        let mut hblock_ctx = unsafe { HeaderBlockCtx::from_void_ptr(hblock_ctx) };
569
570        debug_assert!(hblock_ctx.as_ref().is_blocked());
571        hblock_ctx.as_mut().set_blocked(false);
572
573        let encoded_cursor = hblock_ctx.as_ref().encoded_cursor();
574        let encoded_cursor_len = encoded_cursor.len();
575        let mut cursor_after = encoded_cursor.as_ptr();
576
577        let mut buffer = vec![0; ls_qpack_rs_sys::LSQPACK_LONGEST_SDTC as usize];
578        let mut sdtc_buffer_size = buffer.len();
579
580        // SAFETY: `hblock_ctx.decoder` is the initialized C decoder. The hblock_ctx
581        // pointer (cast to void) is valid and pinned. `cursor_after` points into the
582        // encoded data owned by hblock_ctx. `buffer` has LSQPACK_LONGEST_SDTC bytes.
583        let result = unsafe {
584            ls_qpack_rs_sys::lsqpack_dec_header_read(
585                hblock_ctx.decoder,
586                hblock_ctx.as_mut().as_mut_ptr() as *mut libc::c_void,
587                &mut cursor_after,
588                encoded_cursor_len,
589                buffer.as_mut_ptr(),
590                &mut sdtc_buffer_size,
591            )
592        };
593
594        match result {
595            ls_qpack_rs_sys::lsqpack_read_header_status_LQRHS_DONE => {
596                buffer.truncate(sdtc_buffer_size);
597                hblock_ctx.as_mut().set_stream_data(&buffer);
598            }
599
600            ls_qpack_rs_sys::lsqpack_read_header_status_LQRHS_BLOCKED
601            | ls_qpack_rs_sys::lsqpack_read_header_status_LQRHS_NEED => {
602                // SAFETY: Both `cursor_after` and the base pointer come from the same
603                // allocation (`hblock_ctx.encoded_data`). `cursor_after` was advanced by
604                // the C library and is guaranteed to be >= the base pointer.
605                let offset = unsafe {
606                    cursor_after.offset_from(hblock_ctx.as_ref().encoded_cursor().as_ptr())
607                };
608
609                debug_assert!(offset >= 0);
610
611                hblock_ctx.as_mut().advance_cursor(offset as usize);
612                hblock_ctx.as_mut().set_blocked(true);
613            }
614
615            _ => {
616                hblock_ctx.as_mut().enable_error();
617            }
618        }
619    }
620
621    extern "C" fn dhi_prepare_decode(
622        hblock_ctx: *mut libc::c_void,
623        header: *mut ls_qpack_rs_sys::lsxpack_header,
624        space: libc::size_t,
625    ) -> *mut ls_qpack_rs_sys::lsxpack_header {
626        const MAX_SPACE: usize = u16::MAX as usize;
627
628        let mut hblock_ctx = unsafe {
629            // SAFETY: The C library passes back the pointer we gave it via `as_mut_ptr()`.
630            // The HeaderBlockCtx is still alive (stored in InnerDecoder::header_blocks).
631            HeaderBlockCtx::from_void_ptr(hblock_ctx)
632        };
633
634        if space > MAX_SPACE {
635            return std::ptr::null_mut();
636        }
637
638        let space = space as u16;
639
640        if header.is_null() {
641            hblock_ctx.as_mut().reset_header();
642        } else {
643            assert!(std::ptr::eq(&hblock_ctx.header, header));
644            assert!(space > hblock_ctx.header.val_len);
645        }
646
647        hblock_ctx.as_mut().resize_header(space);
648        hblock_ctx.as_mut().header_mut()
649    }
650
651    extern "C" fn dhi_process_header(
652        hblock_ctx: *mut libc::c_void,
653        header: *mut ls_qpack_rs_sys::lsxpack_header,
654    ) -> libc::c_int {
655        // SAFETY: The C library passes back the pointer we gave it via `as_mut_ptr()`.
656        // The HeaderBlockCtx is still alive (stored in InnerDecoder::header_blocks).
657        let mut hblock_ctx = unsafe { HeaderBlockCtx::from_void_ptr(hblock_ctx) };
658
659        debug_assert!(!hblock_ctx.blocked);
660        debug_assert_eq!(header as *const _, &hblock_ctx.header);
661
662        match hblock_ctx.as_mut().process_header() {
663            Ok(()) => 0,
664            Err(_) => {
665                hblock_ctx.as_mut().enable_error();
666                -1
667            }
668        }
669    }
670}
671
672impl Debug for DecoderError {
673    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
674        f.debug_struct("DecoderError")
675            .field("kind", &self.kind)
676            .finish()
677    }
678}
679
680impl Display for DecoderError {
681    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
682        match self.kind {
683            DecoderErrorKind::DuplicateStreamId => {
684                write!(f, "stream ID already has a pending header block")
685            }
686            DecoderErrorKind::DecodeFailed => write!(f, "decoding operation failed"),
687            DecoderErrorKind::FeedFailed => write!(f, "failed to process encoder stream data"),
688            DecoderErrorKind::InvalidHeader => {
689                write!(f, "invalid header encountered during decoding")
690            }
691        }
692    }
693}
694
695impl std::error::Error for DecoderError {}
696
697#[cfg(test)]
698mod tests {
699    use super::*;
700    use crate::encoder::Encoder;
701
702    /// Basic round-trip: encode headers with static table, then decode them.
703    #[test]
704    fn test_decode_static_table_round_trip() {
705        let headers = [(":status", "200"), ("content-type", "text/html")];
706
707        let encoded = Encoder::new()
708            .encode_all(StreamId::new(0), headers)
709            .unwrap();
710
711        let (hdr_data, stream_data) = encoded.into();
712        assert!(
713            stream_data.is_empty(),
714            "static table should produce no stream data"
715        );
716
717        let output = Decoder::new(0, 0)
718            .decode(StreamId::new(0), hdr_data)
719            .unwrap();
720
721        let decoded = output.take().expect("should not be blocked");
722        let hdrs = decoded.headers();
723        assert_eq!(hdrs.len(), 2);
724        assert_eq!(hdrs[0].name(), ":status");
725        assert_eq!(hdrs[0].value(), "200");
726        assert_eq!(hdrs[1].name(), "content-type");
727        assert_eq!(hdrs[1].value(), "text/html");
728    }
729
730    /// Decode multiple distinct stream IDs sequentially.
731    #[test]
732    fn test_decode_multiple_streams() {
733        let mut encoder = Encoder::new();
734        let mut decoder = Decoder::new(0, 0);
735
736        for stream_id in 0..5u64 {
737            let encoded = encoder
738                .encode_all(StreamId::new(stream_id), [(":method", "GET")])
739                .unwrap();
740
741            let (hdr_data, _) = encoded.into();
742
743            let output = decoder.decode(StreamId::new(stream_id), hdr_data).unwrap();
744
745            let decoded = output.take().expect("should not be blocked");
746            assert_eq!(decoded.headers()[0].name(), ":method");
747            assert_eq!(decoded.headers()[0].value(), "GET");
748        }
749    }
750
751    /// Duplicate stream ID should return DuplicateStreamId error.
752    /// To trigger this, we need a blocked stream. We encode many unique headers
753    /// to force dynamic table usage, then feed header data without encoder stream
754    /// data to make the decoder block on that stream.
755    #[test]
756    fn test_decode_duplicate_stream_id_error() {
757        let mut encoder = Encoder::new();
758        let sdtc = encoder.configure(4096, 4096, 100).unwrap();
759
760        let mut decoder = Decoder::new(4096, 100);
761        decoder.feed(sdtc.data()).unwrap();
762
763        // Encode several rounds to populate the dynamic table on the encoder side.
764        // The first few encodes may not produce encoder stream data, but subsequent
765        // ones that reference the dynamic table will.
766        let mut hdr_data_blocked = None;
767        let mut enc_stream_blocked = None;
768
769        for i in 0..10u64 {
770            let name = format!("x-unique-{}", i);
771            let value = format!("value-{}", i);
772            let encoded = encoder
773                .encode_all(StreamId::new(i), [(name.as_str(), value.as_str())])
774                .unwrap();
775
776            let (hdr_data, enc_stream) = encoded.into();
777
778            if !enc_stream.is_empty() && hdr_data_blocked.is_none() {
779                // Found an encoding that uses the dynamic table. Don't feed the
780                // encoder stream so the decoder blocks on this stream.
781                hdr_data_blocked = Some((i, hdr_data));
782                enc_stream_blocked = Some(enc_stream);
783                continue;
784            }
785
786            // Feed encoder stream data for all other streams
787            if !enc_stream.is_empty() {
788                decoder.feed(&enc_stream).unwrap();
789            }
790            let _ = decoder.decode(StreamId::new(i), hdr_data);
791        }
792
793        if let Some((stream_id, hdr_data)) = hdr_data_blocked {
794            let output = decoder.decode(StreamId::new(stream_id), &hdr_data).unwrap();
795            assert!(
796                output.is_blocked(),
797                "should be blocked without encoder stream"
798            );
799
800            // Now try to decode the same stream ID again — should get DuplicateStreamId
801            let err = decoder
802                .decode(StreamId::new(stream_id), &hdr_data)
803                .unwrap_err();
804            assert_eq!(err.kind(), DecoderErrorKind::DuplicateStreamId);
805
806            // Clean up: feed the encoder stream to unblock
807            if let Some(enc_stream) = enc_stream_blocked {
808                decoder.feed(&enc_stream).unwrap();
809            }
810        } else {
811            // If the encoder never used the dynamic table, we can still test the
812            // error by manually creating a scenario. Use a direct duplicate.
813            // Encode two headers for the same stream.
814            let encoded = encoder
815                .encode_all(StreamId::new(100), [(":status", "200")])
816                .unwrap();
817            let (hdr_data, enc_stream) = encoded.into();
818            if !enc_stream.is_empty() {
819                decoder.feed(&enc_stream).unwrap();
820            }
821            // This should succeed
822            let _ = decoder.decode(StreamId::new(100), &hdr_data).unwrap();
823            // The stream completed, so we can't duplicate it. Skip this test path.
824        }
825    }
826
827    /// Dynamic table round-trip: encode with dynamic table, feed encoder stream
828    /// to decoder, then decode.
829    #[test]
830    fn test_dynamic_table_round_trip() {
831        let mut encoder = Encoder::new();
832        let sdtc = encoder.configure(4096, 4096, 100).unwrap();
833
834        let mut decoder = Decoder::new(4096, 100);
835        decoder.feed(sdtc.data()).unwrap();
836
837        let headers = [
838            (":status", "200"),
839            ("x-custom", "hello"),
840            ("x-another", "world"),
841        ];
842
843        let encoded = encoder.encode_all(StreamId::new(0), headers).unwrap();
844
845        let (hdr_data, enc_stream) = encoded.into();
846
847        // Feed encoder stream data to decoder first
848        decoder.feed(&enc_stream).unwrap();
849
850        let output = decoder.decode(StreamId::new(0), hdr_data).unwrap();
851        let decoded = output
852            .take()
853            .expect("should not be blocked after feeding encoder stream");
854
855        let hdrs = decoded.headers();
856        assert_eq!(hdrs.len(), 3);
857        assert_eq!(hdrs[0].name(), ":status");
858        assert_eq!(hdrs[0].value(), "200");
859        assert_eq!(hdrs[1].name(), "x-custom");
860        assert_eq!(hdrs[1].value(), "hello");
861        assert_eq!(hdrs[2].name(), "x-another");
862        assert_eq!(hdrs[2].value(), "world");
863    }
864
865    /// Dynamic table round-trip with blocked stream that gets unblocked.
866    #[test]
867    fn test_dynamic_table_blocked_then_unblocked() {
868        let mut encoder = Encoder::new();
869        let sdtc = encoder.configure(4096, 4096, 100).unwrap();
870
871        let mut decoder = Decoder::new(4096, 100);
872        decoder.feed(sdtc.data()).unwrap();
873
874        // Encode multiple rounds to force dynamic table usage. We look for a stream
875        // that produces encoder stream data (indicating dynamic table insertion).
876        let mut blocked_info = None;
877
878        for i in 0..10u64 {
879            let name = format!("x-blocked-test-{}", i);
880            let value = format!("value-{}", i);
881            let encoded = encoder
882                .encode_all(StreamId::new(i), [(name.as_str(), value.as_str())])
883                .unwrap();
884
885            let (hdr_data, enc_stream) = encoded.into();
886
887            if !enc_stream.is_empty() && blocked_info.is_none() {
888                // Don't feed encoder stream — try to decode to see if it blocks
889                let output = decoder.decode(StreamId::new(i), &hdr_data).unwrap();
890                if output.is_blocked() {
891                    blocked_info = Some((i, enc_stream, name, value));
892                    continue;
893                }
894                // If it didn't block, feed the stream and move on
895            }
896
897            if !enc_stream.is_empty() {
898                decoder.feed(&enc_stream).unwrap();
899            }
900            let _ = decoder.decode(StreamId::new(i), hdr_data);
901        }
902
903        if let Some((stream_id, enc_stream, name, value)) = blocked_info {
904            // Now feed the encoder stream data to unblock
905            decoder.feed(&enc_stream).unwrap();
906
907            // Check if the stream is now unblocked
908            let result = decoder.unblocked(StreamId::new(stream_id));
909            let output = result
910                .expect("should have result for blocked stream")
911                .unwrap();
912            let decoded = output.take().expect("should be unblocked now");
913
914            assert_eq!(decoded.headers().len(), 1);
915            assert_eq!(decoded.headers()[0].name(), name);
916            assert_eq!(decoded.headers()[0].value(), value);
917        }
918        // If no stream ever blocked, the test passes vacuously — the encoder chose
919        // not to use the dynamic table in a way that causes blocking.
920    }
921
922    /// Error kind Display messages should be descriptive.
923    #[test]
924    fn test_decoder_error_display() {
925        let err = DecoderError::new(DecoderErrorKind::DuplicateStreamId);
926        assert_eq!(
927            err.to_string(),
928            "stream ID already has a pending header block"
929        );
930
931        let err = DecoderError::new(DecoderErrorKind::DecodeFailed);
932        assert_eq!(err.to_string(), "decoding operation failed");
933
934        let err = DecoderError::new(DecoderErrorKind::FeedFailed);
935        assert_eq!(err.to_string(), "failed to process encoder stream data");
936
937        let err = DecoderError::new(DecoderErrorKind::InvalidHeader);
938        assert_eq!(
939            err.to_string(),
940            "invalid header encountered during decoding"
941        );
942    }
943
944    /// Error kind Debug output should include the kind field.
945    #[test]
946    fn test_decoder_error_debug() {
947        let err = DecoderError::new(DecoderErrorKind::DecodeFailed);
948        let debug = format!("{:?}", err);
949        assert!(debug.contains("DecoderError"));
950        assert!(debug.contains("DecodeFailed"));
951    }
952
953    /// DecoderOutput::is_blocked and take() work correctly.
954    #[test]
955    fn test_decoder_output_variants() {
956        let blocked = DecoderOutput::BlockedStream;
957        assert!(blocked.is_blocked());
958        assert!(blocked.take().is_none());
959
960        let done = DecoderOutput::Done(BuffersDecoded {
961            headers: vec![],
962            stream: vec![].into_boxed_slice(),
963        });
964        assert!(!done.is_blocked());
965        assert!(done.take().is_some());
966    }
967
968    /// Decoding a single header and checking stream data.
969    #[test]
970    fn test_decode_produces_stream_data() {
971        let encoded = Encoder::new()
972            .encode_all(StreamId::new(0), [(":method", "GET")])
973            .unwrap();
974
975        let (hdr_data, _) = encoded.into();
976
977        let output = Decoder::new(0, 0)
978            .decode(StreamId::new(0), hdr_data)
979            .unwrap();
980
981        let decoded = output.take().expect("should decode");
982        // With static table only, stream data is just the SDTC (possibly empty or minimal)
983        // The important thing is that we get valid data back
984        assert_eq!(decoded.headers().len(), 1);
985        assert_eq!(decoded.headers()[0].name(), ":method");
986        assert_eq!(decoded.headers()[0].value(), "GET");
987    }
988}