1use core::mem;
4
5use crate::{
6 field::{self, scan::Match},
7 h1::{
8 field::Connection,
9 head::{line::Version, request::offers},
10 },
11 payload::response::Method,
12};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum Error {
16 IncompleteUpgradeRequest,
17 UnexpectedSwitch,
18 MissingConnectionUpgrade,
19 MissingSelection,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum UpgradeError {
24 Empty,
25 InvalidProtocol,
26 MultipleSelections,
27 NotOffered,
28 Overflow { value: usize, added: usize },
29 TooLarge { actual: usize, maximum: usize },
30}
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
34#[repr(u8)]
35pub enum Intent {
36 #[default]
37 None,
38 Connect,
39 Upgrade,
40}
41
42impl Intent {
43 pub(in crate::h1) const fn from_request(
46 request: Method,
47 conn: Connection,
48 offers: Offers,
49 version: Version,
50 ) -> Result<Self, Error> {
51 if matches!(request, Method::Connect) {
52 return Ok(Self::Connect);
53 }
54 match (conn.is_upgrade(), offers.is_present()) {
55 (false, false) => Ok(Self::None),
56 _ if matches!(version, Version::Ten) => Ok(Self::None),
57 (true, true) => Ok(Self::Upgrade),
58 (true, false) | (false, true) => Err(Error::IncompleteUpgradeRequest),
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64#[repr(u8)]
65pub enum Kind {
66 Connect,
67 Upgrade,
68 ExtendedConnect,
69}
70
71#[derive(Clone, Copy, PartialEq, Eq)]
75pub struct Selection {
76 private: (),
77}
78
79impl Selection {
80 pub(in crate::h1) fn from_offers<B>(
81 offered: offers::View<'_, B>,
82 selected: &[u8],
83 ) -> Result<Self, UpgradeError> {
84 let selected = single_upgrade_protocol(selected)?;
85 for value in offered.values() {
86 for protocol in value.split(|byte| *byte == b',') {
87 if field::Error::trim_ows(protocol).eq_ignore_ascii_case(selected) {
88 return Ok(Self { private: () });
89 }
90 }
91 }
92 Err(UpgradeError::NotOffered)
93 }
94
95 pub fn select<'a>(
96 offered: impl IntoIterator<Item = &'a [u8]>,
97 selected: &[u8],
98 ) -> Result<Self, UpgradeError> {
99 let selected = single_upgrade_protocol(selected)?;
100 let mut matched = false;
101 for value in offered {
102 for protocol in protocols(value) {
103 let protocol = protocol?;
104 if protocol.eq_ignore_ascii_case(selected) {
105 matched = true;
106 }
107 }
108 if matched {
109 return Ok(Self { private: () });
110 }
111 }
112 Err(UpgradeError::NotOffered)
113 }
114}
115
116#[repr(transparent)]
118#[derive(Clone, Copy, Default, PartialEq, Eq)]
119pub struct Offers(u32);
120
121impl Offers {
122 pub(in crate::h1) fn try_push(self, value: &[u8]) -> Result<Self, UpgradeError> {
126 Self::validate(value)?;
127 let sep = usize::from(self.is_present());
128 let current = self.compact_len();
129 let length = current.checked_add(sep).ok_or(UpgradeError::Overflow {
130 value: current,
131 added: sep,
132 })?;
133 let actual = length
134 .checked_add(value.len())
135 .ok_or(UpgradeError::Overflow {
136 value: length,
137 added: value.len(),
138 })?;
139 let maximum = u32::MAX as usize;
140 if actual > maximum {
141 return Err(UpgradeError::TooLarge { actual, maximum });
142 }
143 Ok(Self(actual as u32))
144 }
145
146 pub const fn is_present(self) -> bool {
147 self.0 != 0
148 }
149
150 pub const fn compact_len(self) -> usize {
151 self.0 as usize
152 }
153
154 pub fn validate(value: &[u8]) -> Result<(), UpgradeError> {
155 for raw in value.split(|byte| *byte == b',') {
156 check(field::Error::trim_ows(raw))?;
157 }
158 Ok(())
159 }
160}
161
162fn protocols(value: &[u8]) -> impl Iterator<Item = Result<&[u8], UpgradeError>> {
163 value.split(|byte| *byte == b',').map(|raw| {
164 let protocol = field::Error::trim_ows(raw);
165 check(protocol)?;
166 Ok(protocol)
167 })
168}
169
170fn check(protocol: &[u8]) -> Result<(), UpgradeError> {
171 if protocol.is_empty() {
172 Err(UpgradeError::Empty)
173 } else if is_protocol(protocol) {
174 Ok(())
175 } else {
176 Err(UpgradeError::InvalidProtocol)
177 }
178}
179
180fn single_upgrade_protocol(value: &[u8]) -> Result<&[u8], UpgradeError> {
181 let value = field::Error::trim_ows(value);
182 if value.is_empty() {
183 return Err(UpgradeError::Empty);
184 }
185 if value.contains(&b',') {
186 return Err(UpgradeError::MultipleSelections);
187 }
188 if !is_protocol(value) {
189 return Err(UpgradeError::InvalidProtocol);
190 }
191 Ok(value)
192}
193
194fn is_protocol(value: &[u8]) -> bool {
195 let Some(slash) = Match::find(b'/', value) else {
196 return is_token(value);
197 };
198 is_token(slash.prefix()) && is_token(slash.suffix())
199}
200
201fn is_token(value: &[u8]) -> bool {
202 !value.is_empty() && value.iter().copied().all(field::Error::is_name_byte)
203}
204
205const _: () = assert!(mem::size_of::<UpgradeError>() <= 3 * mem::size_of::<usize>());
206const _: () = assert!(!mem::needs_drop::<UpgradeError>());
207const _: () = assert!(core::mem::size_of::<Intent>() == 1);
208const _: () = assert!(core::mem::size_of::<Offers>() == 4);