Skip to main content

sark_http/field/metadata/block/
framing.rs

1use core::num::NonZero;
2
3use crate::payload::response;
4
5/// Message framing values admitted before the immutable field section is published.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct Framing {
8    length: u64,
9    status: Option<NonZero<u16>>,
10    method: Option<response::Method>,
11    present: bool,
12}
13
14impl Framing {
15    pub(super) const fn new(
16        length: u64,
17        status: Option<NonZero<u16>>,
18        method: Option<response::Method>,
19        present: bool,
20    ) -> Self {
21        Self {
22            length,
23            status,
24            method,
25            present,
26        }
27    }
28
29    pub const fn content_length(self) -> Option<u64> {
30        if self.present {
31            Some(self.length)
32        } else {
33            None
34        }
35    }
36
37    pub const fn status(self) -> Option<NonZero<u16>> {
38        self.status
39    }
40
41    pub const fn method(self) -> Option<response::Method> {
42        self.method
43    }
44
45    /// Carries the admitted message framing into incremental content accounting.
46    pub fn content(self, method: response::Method) -> Result<(Content, u64), ContentError> {
47        let length = self.content_length();
48        if let Some(status) = self.status {
49            match response::Content::new(method, status.get()) {
50                response::Content::Tunnel => return Ok((Content::Tunnel, 0)),
51                response::Content::Forbidden => {
52                    if let Some(length) = length {
53                        return Err(ContentError::ForbiddenLength { length });
54                    }
55                    return Ok((Content::Empty, 0));
56                }
57                response::Content::Suppressed => return Ok((Content::Empty, 0)),
58                response::Content::Empty => {
59                    if let Some(length) = length.filter(|&length| length != 0) {
60                        return Err(ContentError::NonemptyLength { length });
61                    }
62                    return Ok((Content::Exact, 0));
63                }
64                response::Content::Allowed => {}
65            }
66        } else if self.method == Some(response::Method::Connect) {
67            return Ok((Content::Tunnel, 0));
68        }
69        Ok(match length {
70            Some(length) => (Content::Exact, length),
71            None => (Content::Unbounded, 0),
72        })
73    }
74}
75
76/// Content mode kept separately from the remaining count to preserve compact stream state.
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
78pub enum Content {
79    #[default]
80    Unbounded,
81    Exact,
82    Empty,
83    Tunnel,
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub enum ContentError {
88    ForbiddenLength { length: u64 },
89    NonemptyLength { length: u64 },
90    Exceeded { remaining: u64, actual: usize },
91    Incomplete { remaining: u64 },
92    Trailers,
93}
94
95impl Content {
96    pub fn receive(
97        self,
98        remaining: u64,
99        bytes: usize,
100        finished: bool,
101    ) -> Result<u64, ContentError> {
102        let next = match self {
103            Self::Unbounded | Self::Tunnel => return Ok(remaining),
104            Self::Empty if bytes == 0 => return Ok(0),
105            Self::Empty => {
106                return Err(ContentError::Exceeded {
107                    remaining: 0,
108                    actual: bytes,
109                });
110            }
111            Self::Exact => remaining
112                .checked_sub(bytes as u64)
113                .ok_or(ContentError::Exceeded {
114                    remaining,
115                    actual: bytes,
116                })?,
117        };
118        if finished && next != 0 {
119            return Err(ContentError::Incomplete { remaining: next });
120        }
121        Ok(next)
122    }
123
124    pub fn trailers(self, remaining: u64) -> Result<(), ContentError> {
125        match self {
126            Self::Unbounded => Ok(()),
127            Self::Exact if remaining == 0 => Ok(()),
128            Self::Exact => Err(ContentError::Incomplete { remaining }),
129            Self::Empty | Self::Tunnel => Err(ContentError::Trailers),
130        }
131    }
132}
133
134const _: () = {
135    assert!(core::mem::size_of::<Framing>() == 16);
136    assert!(core::mem::size_of::<Option<Framing>>() == 16);
137};