Skip to main content

net_wire/http2/hpack/
context.rs

1//! Connection-owned HPACK compression contexts and table-size update policy.
2//!
3//! Call [`HpackEncoderContext::set_allowed_maximum_size`] or
4//! [`HpackDecoderContext::set_allowed_maximum_size`] when the corresponding HTTP/2
5//! protocol maximum becomes effective. SETTINGS acknowledgement policy belongs to the
6//! HTTP/2 connection owner, not this HPACK layer.
7
8use super::decoder::{HpackBlockDecoder, HpackDecodeError};
9use super::encoder::{HpackBlockEncoder, HpackEncodeError};
10use super::table::HpackDynamicTable;
11
12#[derive(Clone, Copy, Debug, Default)]
13pub(super) struct PendingSizes {
14    minimum: Option<usize>,
15    final_size: Option<usize>,
16}
17
18impl PendingSizes {
19    pub(super) const fn next(self) -> Option<usize> {
20        match self.minimum {
21            Some(minimum) => Some(minimum),
22            None => self.final_size,
23        }
24    }
25
26    pub(super) fn advance(&mut self) {
27        if self.minimum.is_some() {
28            self.minimum = None;
29        } else {
30            self.final_size = None;
31        }
32    }
33
34    fn queue_initial(&mut self, table_maximum: usize, allowed: usize) {
35        if table_maximum > allowed {
36            self.minimum = Some(allowed);
37        }
38    }
39
40    fn record_change(&mut self, allowed: usize) {
41        match self.minimum {
42            None => {
43                self.minimum = Some(allowed);
44                self.final_size = None;
45            }
46            Some(minimum) if allowed <= minimum => {
47                self.minimum = Some(allowed);
48                self.final_size = None;
49            }
50            Some(minimum) => {
51                self.minimum = Some(minimum);
52                self.final_size = Some(allowed);
53            }
54        }
55    }
56}
57
58/// Connection-owned HPACK encoder state for one HTTP/2 direction.
59///
60/// A successful [`HpackBlockEncoder::finish`] establishes completion of that block.
61pub struct HpackEncoderContext<'storage, 'entries> {
62    pub(super) table: HpackDynamicTable<'storage, 'entries>,
63    allowed_maximum_size: usize,
64    pub(super) pending: PendingSizes,
65}
66
67impl<'storage, 'entries> HpackEncoderContext<'storage, 'entries> {
68    /// Creates an encoder context from caller-owned dynamic-table storage.
69    pub fn new(
70        table: HpackDynamicTable<'storage, 'entries>,
71        allowed_maximum_size: usize,
72    ) -> Result<Self, HpackEncodeError> {
73        if allowed_maximum_size > table.capacity() {
74            return Err(HpackEncodeError::AllowedMaximumExceedsCapacity {
75                allowed: allowed_maximum_size,
76                capacity: table.capacity(),
77            });
78        }
79        let mut pending = PendingSizes::default();
80        pending.queue_initial(table.maximum_size(), allowed_maximum_size);
81        Ok(Self {
82            table,
83            allowed_maximum_size,
84            pending,
85        })
86    }
87
88    /// Returns the effective protocol maximum currently allowed for this direction.
89    pub const fn allowed_maximum_size(&self) -> usize {
90        self.allowed_maximum_size
91    }
92
93    /// Returns the dynamic table without permitting policy-desynchronizing mutation.
94    pub const fn dynamic_table(&self) -> &HpackDynamicTable<'storage, 'entries> {
95        &self.table
96    }
97
98    /// Returns the exact next required leading size update, if any.
99    pub const fn next_required_size_update(&self) -> Option<usize> {
100        self.pending.next()
101    }
102
103    /// Applies an effective HTTP/2 table-size policy change for this direction.
104    pub fn set_allowed_maximum_size(&mut self, allowed: usize) -> Result<(), HpackEncodeError> {
105        if allowed > self.table.capacity() {
106            return Err(HpackEncodeError::AllowedMaximumExceedsCapacity {
107                allowed,
108                capacity: self.table.capacity(),
109            });
110        }
111        if allowed != self.allowed_maximum_size {
112            self.pending.record_change(allowed);
113            self.allowed_maximum_size = allowed;
114        }
115        Ok(())
116    }
117
118    /// Begins one HPACK header block.
119    pub fn begin_block(&mut self) -> HpackBlockEncoder<'_, 'storage, 'entries> {
120        HpackBlockEncoder::from_context(
121            &mut self.table,
122            &mut self.pending,
123            self.allowed_maximum_size,
124        )
125    }
126
127    /// Recovers the caller-owned dynamic table.
128    pub fn into_dynamic_table(self) -> HpackDynamicTable<'storage, 'entries> {
129        self.table
130    }
131}
132
133/// Connection-owned HPACK decoder state for one HTTP/2 direction.
134pub struct HpackDecoderContext<'storage, 'entries> {
135    pub(super) table: HpackDynamicTable<'storage, 'entries>,
136    allowed_maximum_size: usize,
137    pub(super) pending: PendingSizes,
138}
139
140impl<'storage, 'entries> HpackDecoderContext<'storage, 'entries> {
141    /// Creates a decoder context from caller-owned dynamic-table storage.
142    pub fn new(
143        table: HpackDynamicTable<'storage, 'entries>,
144        allowed_maximum_size: usize,
145    ) -> Result<Self, HpackDecodeError> {
146        if allowed_maximum_size > table.capacity() {
147            return Err(HpackDecodeError::AllowedMaximumExceedsCapacity {
148                allowed: allowed_maximum_size,
149                capacity: table.capacity(),
150            });
151        }
152        let mut pending = PendingSizes::default();
153        pending.queue_initial(table.maximum_size(), allowed_maximum_size);
154        Ok(Self {
155            table,
156            allowed_maximum_size,
157            pending,
158        })
159    }
160
161    /// Returns the effective protocol maximum currently allowed for this direction.
162    pub const fn allowed_maximum_size(&self) -> usize {
163        self.allowed_maximum_size
164    }
165
166    /// Returns the dynamic table without permitting policy-desynchronizing mutation.
167    pub const fn dynamic_table(&self) -> &HpackDynamicTable<'storage, 'entries> {
168        &self.table
169    }
170
171    /// Returns the exact next required leading size update, if any.
172    pub const fn next_required_size_update(&self) -> Option<usize> {
173        self.pending.next()
174    }
175
176    /// Applies an effective HTTP/2 table-size policy change for this direction.
177    pub fn set_allowed_maximum_size(&mut self, allowed: usize) -> Result<(), HpackDecodeError> {
178        if allowed > self.table.capacity() {
179            return Err(HpackDecodeError::AllowedMaximumExceedsCapacity {
180                allowed,
181                capacity: self.table.capacity(),
182            });
183        }
184        if allowed != self.allowed_maximum_size {
185            self.pending.record_change(allowed);
186            self.allowed_maximum_size = allowed;
187        }
188        Ok(())
189    }
190
191    /// Decodes one complete HPACK header block.
192    pub fn decode_block<'block>(
193        &mut self,
194        block: &'block [u8],
195    ) -> HpackBlockDecoder<'block, '_, 'storage, 'entries> {
196        HpackBlockDecoder::from_context(
197            block,
198            &mut self.table,
199            &mut self.pending,
200            self.allowed_maximum_size,
201        )
202    }
203
204    /// Recovers the caller-owned dynamic table.
205    pub fn into_dynamic_table(self) -> HpackDynamicTable<'storage, 'entries> {
206        self.table
207    }
208}