Skip to main content

sark_http/h1/
framing.rs

1//! HTTP/1 message framing and final admission plans.
2
3use crate::{
4    field::text::Ascii,
5    h1::{
6        field::{self, expect},
7        switch, target,
8    },
9    payload::response::Method,
10    request::{self, span},
11};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Error {
15    ContentLengthWithTransferEncoding,
16    UnframedRequestTransferEncoding,
17    Http10TransferEncoding,
18    ForbiddenResponseFraming,
19    SuccessfulConnectIsTransition,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Response {
24    Framing(Error),
25    Transition(switch::Error),
26}
27
28#[derive(Clone, Copy, PartialEq, Eq, Debug)]
29pub enum RequestError {
30    Authority(target::Error),
31    Framing(Error),
32    Expectation(expect::Error),
33}
34
35#[derive(Clone, Copy, PartialEq, Eq)]
36#[repr(u8)]
37pub enum Transfer {
38    None,
39    Chunked,
40    Other,
41}
42
43#[derive(Clone, Copy, PartialEq, Eq)]
44pub struct Message {
45    content_length: Option<usize>,
46    transfer: Transfer,
47}
48
49impl Message {
50    pub const fn new(length: Option<usize>, transfer: Transfer) -> Self {
51        Self {
52            content_length: length,
53            transfer,
54        }
55    }
56}
57
58/// Borrow-free state for fields that determine message framing.
59#[derive(Clone, Copy, Default, PartialEq, Eq)]
60pub struct Fields {
61    content_length: Option<usize>,
62    transfer: field::Transfer,
63}
64
65impl Fields {
66    pub fn try_length(mut self, value: &[u8]) -> Result<Self, field::Error> {
67        if self.content_length.is_some() {
68            return Err(field::Error::DuplicateContentLength);
69        }
70        if self.transfer.has_encoding() {
71            return Err(field::Error::ContentLengthWithTransferEncoding);
72        }
73        let length = Ascii::new(crate::field::Error::trim_ows(value))
74            .parse_usize()
75            .map_err(field::Error::InvalidContentLength)?;
76        self.content_length = Some(length);
77        Ok(self)
78    }
79
80    /// Commits a Content-Length already parsed by a scanner's specialized
81    /// integer fast path.
82    pub fn try_set(mut self, length: usize) -> Result<Self, field::Error> {
83        if self.content_length.is_some() {
84            return Err(field::Error::DuplicateContentLength);
85        }
86        if self.transfer.has_encoding() {
87            return Err(field::Error::ContentLengthWithTransferEncoding);
88        }
89        self.content_length = Some(length);
90        Ok(self)
91    }
92
93    pub fn try_coding(mut self, value: &[u8]) -> Result<Self, field::Error> {
94        if self.content_length.is_some() {
95            return Err(field::Error::ContentLengthWithTransferEncoding);
96        }
97        let transfer = self
98            .transfer
99            .parse(value)
100            .map_err(field::Error::InvalidTransferEncoding)?;
101        self.transfer = transfer;
102        Ok(self)
103    }
104
105    /// Commits one field value already validated by a scanner's specialized
106    /// Transfer-Encoding path.
107    pub fn try_append(self, value: field::Transfer) -> Result<Self, field::Error> {
108        if self.content_length.is_some() {
109            return Err(field::Error::ContentLengthWithTransferEncoding);
110        }
111        let transfer = self
112            .transfer
113            .try_join(value)
114            .map_err(field::Error::InvalidTransferEncoding)?;
115        Ok(Self {
116            content_length: self.content_length,
117            transfer,
118        })
119    }
120
121    pub const fn length(self) -> Option<usize> {
122        self.content_length
123    }
124
125    pub const fn has_coding(self) -> bool {
126        self.transfer.has_encoding()
127    }
128
129    pub const fn is_chunked(self) -> bool {
130        self.transfer.is_chunked()
131    }
132
133    pub(in crate::h1) const fn has_other(self) -> bool {
134        self.transfer.has_other()
135    }
136
137    pub const fn finish(self) -> Message {
138        Message::new(self.content_length, self.transfer.framing())
139    }
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum RequestBody {
144    Empty,
145    Fixed(usize),
146    Chunked,
147}
148
149/// The protocol-semantic result required before routing may commit an HTTP/1
150/// response. It owns only compact metadata; all byte views remain derived from
151/// the retained request owner through [`target::Target`] spans.
152#[derive(Clone, Copy, PartialEq, Eq)]
153pub struct Admission {
154    target: target::Target,
155    authority: target::Plan,
156    body: RequestBody,
157    expectation: expect::Decision,
158}
159
160impl Admission {
161    pub const fn target(self) -> target::Target {
162        self.target
163    }
164
165    pub const fn authority(self) -> target::Plan {
166        self.authority
167    }
168
169    pub const fn body(self) -> RequestBody {
170        self.body
171    }
172
173    pub const fn expectation(self) -> expect::Decision {
174        self.expectation
175    }
176
177    pub fn layout(
178        self,
179        target: span::Span,
180        host: Option<span::Span>,
181    ) -> Result<request::Layout, target::Error> {
182        let span = self.authority.span(self.target, host)?;
183        self.target.layout(target, span)
184    }
185
186    pub fn admit(
187        fields: Fields,
188        auth: target::Request,
189        expect: expect::State,
190        target: target::Target,
191        version: &[u8],
192        policy: target::Policy,
193    ) -> Result<Self, RequestError> {
194        let plan = auth
195            .finish_plan(target, version)
196            .and_then(|plan| match policy {
197                target::Policy::AllowEmpty => Ok(plan),
198                target::Policy::Require => plan.require(),
199            })
200            .map_err(RequestError::Authority)?;
201        let body = fields
202            .finish()
203            .try_version(version)
204            .and_then(Message::request)
205            .map_err(RequestError::Framing)?;
206        let decision = if version == b"HTTP/1.0" {
207            expect::Decision::None
208        } else {
209            expect.finish(body).map_err(RequestError::Expectation)?
210        };
211        Ok(Self {
212            target,
213            authority: plan,
214            body,
215            expectation: decision,
216        })
217    }
218}
219
220#[derive(Clone, Copy, PartialEq, Eq)]
221pub enum ResponseBody {
222    None,
223    Fixed(usize),
224    Chunked,
225    UntilEof,
226    Empty(Empty),
227}
228
229/// Borrow-free transport decision produced at the same boundary as response
230/// framing. Consumers never need to traverse materialized headers again.
231#[derive(Clone, Copy, PartialEq, Eq)]
232#[repr(C)]
233pub struct Control {
234    keep_alive_timeout_secs: u64,
235    connection: field::Connection,
236    reuse: Reuse,
237}
238
239#[derive(Clone, Copy, PartialEq, Eq)]
240#[repr(u8)]
241enum Reuse {
242    Close = 1,
243    Reusable = 2,
244    Timed = 3,
245}
246
247impl Control {
248    pub const fn new(
249        version: u8,
250        status: u16,
251        body: ResponseBody,
252        conn: field::Connection,
253        peer: field::KeepAlive,
254    ) -> Self {
255        let persist = version == 1 || version == 0 && conn.is_keep();
256        let (secs, reuse) = if status == 101
257            || !persist
258            || conn.is_close()
259            || conn.is_upgrade()
260            || matches!(
261                body,
262                ResponseBody::UntilEof | ResponseBody::Empty(Empty::UntilEof)
263            ) {
264            (0, Reuse::Close)
265        } else {
266            match peer.timeout_secs() {
267                Some(timeout) => (timeout, Reuse::Timed),
268                None => (0, Reuse::Reusable),
269            }
270        };
271        Self {
272            keep_alive_timeout_secs: secs,
273            connection: conn,
274            reuse,
275        }
276    }
277
278    pub const fn connection(self) -> field::Connection {
279        self.connection
280    }
281
282    pub const fn is_reusable(self) -> bool {
283        matches!(self.reuse, Reuse::Reusable | Reuse::Timed)
284    }
285
286    pub const fn timeout(self) -> Option<u64> {
287        match self.reuse {
288            Reuse::Timed => Some(self.keep_alive_timeout_secs),
289            Reuse::Close | Reuse::Reusable => None,
290        }
291    }
292}
293
294/// Complete semantic result of admitting one HTTP/1 response head.
295/// A protocol switch is not an HTTP message body and therefore cannot be
296/// accidentally consumed by a normal body decoder.
297#[derive(Clone, Copy, PartialEq, Eq)]
298pub enum Outcome {
299    Informational,
300    Message(ResponseBody),
301    Switch(switch::Kind),
302}
303
304/// Wire framing that still has to be consumed for a response whose status
305/// semantics require the decoded content to remain empty (currently 205).
306#[derive(Clone, Copy, PartialEq, Eq)]
307pub enum Empty {
308    Fixed,
309    Chunked,
310    UntilEof,
311}
312
313impl Message {
314    pub(in crate::h1) fn try_version(self, version: &[u8]) -> Result<Self, Error> {
315        if version == b"HTTP/1.0" && !matches!(self.transfer, Transfer::None) {
316            return Err(Error::Http10TransferEncoding);
317        }
318        Ok(self)
319    }
320
321    const fn is_unframed(self) -> bool {
322        self.content_length.is_none() && matches!(self.transfer, Transfer::None)
323    }
324
325    pub const fn request(self) -> Result<RequestBody, Error> {
326        if !matches!(self.transfer, Transfer::None) {
327            if self.content_length.is_some() {
328                return Err(Error::ContentLengthWithTransferEncoding);
329            }
330            return match self.transfer {
331                Transfer::Chunked => Ok(RequestBody::Chunked),
332                Transfer::Other => Err(Error::UnframedRequestTransferEncoding),
333                Transfer::None => Ok(RequestBody::Empty),
334            };
335        }
336        match self.content_length {
337            None | Some(0) => Ok(RequestBody::Empty),
338            Some(length) => Ok(RequestBody::Fixed(length)),
339        }
340    }
341
342    #[inline]
343    pub const fn response(self, request: Method, status: u16) -> Result<ResponseBody, Error> {
344        self.for_content(crate::payload::response::Content::new(request, status))
345    }
346
347    pub(in crate::h1) const fn for_content(
348        self,
349        content: crate::payload::response::Content,
350    ) -> Result<ResponseBody, Error> {
351        if self.content_length.is_some() && !matches!(self.transfer, Transfer::None) {
352            return Err(Error::ContentLengthWithTransferEncoding);
353        }
354        use crate::payload::response::Content;
355        match content {
356            Content::Forbidden => {
357                if !self.is_unframed() {
358                    return Err(Error::ForbiddenResponseFraming);
359                }
360                return Ok(ResponseBody::None);
361            }
362            Content::Suppressed => return Ok(ResponseBody::None),
363            Content::Tunnel => return Err(Error::SuccessfulConnectIsTransition),
364            Content::Empty => {
365                if !matches!(self.content_length, None | Some(0)) {
366                    return Err(Error::ForbiddenResponseFraming);
367                }
368                let empty = match self.transfer {
369                    Transfer::None if matches!(self.content_length, Some(0)) => Empty::Fixed,
370                    Transfer::None => Empty::UntilEof,
371                    Transfer::Chunked => Empty::Chunked,
372                    Transfer::Other => Empty::UntilEof,
373                };
374                return Ok(ResponseBody::Empty(empty));
375            }
376            Content::Allowed => {}
377        }
378        if !matches!(self.transfer, Transfer::None) {
379            return Ok(match self.transfer {
380                Transfer::Chunked => ResponseBody::Chunked,
381                Transfer::Other => ResponseBody::UntilEof,
382                Transfer::None => ResponseBody::UntilEof,
383            });
384        }
385        match self.content_length {
386            Some(length) => Ok(ResponseBody::Fixed(length)),
387            None => Ok(ResponseBody::UntilEof),
388        }
389    }
390}
391
392/// Single parser-neutral response commit point for body framing and protocol
393/// transitions. Callers must carry the returned plan through their transport
394/// submission barrier before entering raw duplex mode.
395impl Outcome {
396    pub const fn admit(
397        request: Method,
398        intent: switch::Intent,
399        status: u16,
400        framing: Message,
401        conn: field::Connection,
402        upgrade: Option<switch::Selection>,
403    ) -> Result<Self, Response> {
404        if status == 101 {
405            return match intent {
406                switch::Intent::Upgrade => {
407                    if !conn.is_upgrade() {
408                        Err(Response::Transition(
409                            switch::Error::MissingConnectionUpgrade,
410                        ))
411                    } else if upgrade.is_none() {
412                        Err(Response::Transition(switch::Error::MissingSelection))
413                    } else if !framing.is_unframed() {
414                        Err(Response::Framing(Error::ForbiddenResponseFraming))
415                    } else {
416                        Ok(Self::Switch(switch::Kind::Upgrade))
417                    }
418                }
419                switch::Intent::None | switch::Intent::Connect => {
420                    Err(Response::Transition(switch::Error::UnexpectedSwitch))
421                }
422            };
423        }
424
425        if status < 200 {
426            if !framing.is_unframed() {
427                return Err(Response::Framing(Error::ForbiddenResponseFraming));
428            }
429            return Ok(Self::Informational);
430        }
431
432        if matches!(intent, switch::Intent::Connect) && status < 300 {
433            return Ok(Self::Switch(switch::Kind::Connect));
434        }
435
436        match framing.response(request, status) {
437            Ok(body) => Ok(Self::Message(body)),
438            Err(error) => Err(Response::Framing(error)),
439        }
440    }
441}
442
443const _: () = assert!(core::mem::size_of::<Fields>() <= 3 * core::mem::size_of::<usize>());
444const _: () = assert!(core::mem::size_of::<Admission>() <= 7 * core::mem::size_of::<usize>());
445const _: () = assert!(core::mem::size_of::<Control>() == 16);
446const _: () = assert!(core::mem::size_of::<Option<Control>>() == 16);