webtrans_proto/
capsule.rs1use std::sync::Arc;
4
5use bytes::{Buf, BufMut, Bytes, BytesMut};
6use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
7
8use crate::grease::is_grease_value;
9use crate::{VarInt, VarIntUnexpectedEnd};
10
11const CLOSE_WEBTRANSPORT_SESSION_TYPE: u64 = 0x2843;
15const MAX_MESSAGE_SIZE: usize = 1024;
16const MAX_CLOSE_PAYLOAD_SIZE: usize = 4 + MAX_MESSAGE_SIZE;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum Capsule {
21 CloseWebTransportSession {
23 code: u32,
25 reason: String,
27 },
28 Unknown {
30 typ: VarInt,
32 payload: Bytes,
34 },
35}
36
37impl Capsule {
38 pub fn decode<B: Buf>(buf: &mut B) -> Result<Self, CapsuleError> {
40 loop {
41 let typ = VarInt::decode(buf)?;
42 let length = VarInt::decode(buf)?;
43
44 let mut payload = buf.take(length.into_inner() as usize);
45 if payload.remaining() > MAX_CLOSE_PAYLOAD_SIZE {
46 return Err(CapsuleError::MessageTooLong);
47 }
48
49 if payload.remaining() < payload.limit() {
50 return Err(CapsuleError::UnexpectedEnd);
51 }
52
53 match typ.into_inner() {
54 CLOSE_WEBTRANSPORT_SESSION_TYPE => {
55 if payload.remaining() < 4 {
56 return Err(CapsuleError::UnexpectedEnd);
57 }
58
59 let error_code = payload.get_u32();
60
61 let message_len = payload.remaining();
62 if message_len > MAX_MESSAGE_SIZE {
63 return Err(CapsuleError::MessageTooLong);
64 }
65
66 let message_bytes = payload.copy_to_bytes(message_len);
67 let error_message = String::from_utf8(message_bytes.to_vec())
68 .map_err(|_| CapsuleError::InvalidUtf8)?;
69
70 return Ok(Self::CloseWebTransportSession {
71 code: error_code,
72 reason: error_message,
73 });
74 }
75 t if is_grease(t) => continue,
76 _ => {
77 let payload_bytes = payload.copy_to_bytes(payload.remaining());
78 return Ok(Self::Unknown {
79 typ,
80 payload: payload_bytes,
81 });
82 }
83 }
84 }
85 }
86
87 pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, CapsuleError> {
89 loop {
90 let typ = VarInt::read(stream)
91 .await
92 .map_err(|_| CapsuleError::UnexpectedEnd)?;
93 let length = VarInt::read(stream)
94 .await
95 .map_err(|_| CapsuleError::UnexpectedEnd)?;
96 let length =
97 usize::try_from(length.into_inner()).map_err(|_| CapsuleError::MessageTooLong)?;
98 if length > MAX_CLOSE_PAYLOAD_SIZE {
99 return Err(CapsuleError::MessageTooLong);
100 }
101
102 let mut payload = vec![0; length];
103 stream.read_exact(&mut payload).await?;
104 if is_grease(typ.into_inner()) {
105 continue;
106 }
107
108 let mut capsule = Vec::with_capacity(VarInt::MAX_SIZE * 2 + length);
109 typ.encode(&mut capsule);
110 VarInt::try_from(length)
111 .map_err(|_| CapsuleError::MessageTooLong)?
112 .encode(&mut capsule);
113 capsule.extend_from_slice(&payload);
114 return Self::decode(&mut capsule.as_slice());
115 }
116 }
117
118 pub fn encode<B: BufMut>(&self, buf: &mut B) -> Result<(), CapsuleError> {
120 match self {
121 Self::CloseWebTransportSession {
122 code: error_code,
123 reason: error_message,
124 } => {
125 if error_message.len() > MAX_MESSAGE_SIZE {
126 return Err(CapsuleError::MessageTooLong);
127 }
128
129 VarInt::from_u64(CLOSE_WEBTRANSPORT_SESSION_TYPE)
131 .unwrap()
132 .encode(buf);
133
134 let length = 4 + error_message.len();
136 VarInt::from_u32(length as u32).encode(buf);
137
138 buf.put_u32(*error_code);
140
141 buf.put_slice(error_message.as_bytes());
143 }
144 Self::Unknown { typ, payload } => {
145 if payload.len() > MAX_CLOSE_PAYLOAD_SIZE {
146 return Err(CapsuleError::MessageTooLong);
147 }
148
149 typ.encode(buf);
151
152 VarInt::try_from(payload.len())
154 .map_err(|_| CapsuleError::MessageTooLong)?
155 .encode(buf);
156
157 buf.put_slice(payload);
159 }
160 }
161 Ok(())
162 }
163
164 pub async fn write<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<(), CapsuleError> {
166 let mut buf = BytesMut::new();
167 self.encode(&mut buf)?;
168 stream.write_all_buf(&mut buf).await?;
169 Ok(())
170 }
171}
172
173fn is_grease(val: u64) -> bool {
174 is_grease_value(val)
175}
176
177#[derive(Debug, Clone, thiserror::Error)]
178pub enum CapsuleError {
180 #[error("unexpected end of buffer")]
181 UnexpectedEnd,
183
184 #[error("invalid UTF-8")]
185 InvalidUtf8,
187
188 #[error("message too long")]
189 MessageTooLong,
191
192 #[error("unknown capsule type: {0:?}")]
193 UnknownType(VarInt),
195
196 #[error("varint decode error: {0:?}")]
197 VarInt(#[from] VarIntUnexpectedEnd),
199
200 #[error("io error: {0}")]
201 Io(Arc<std::io::Error>),
203}
204
205impl From<std::io::Error> for CapsuleError {
206 fn from(err: std::io::Error) -> Self {
207 CapsuleError::Io(Arc::new(err))
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use bytes::Bytes;
215
216 #[test]
217 fn test_close_webtransport_session_decode() {
218 let mut data = Vec::new();
220 VarInt::from_u64(0x2843).unwrap().encode(&mut data);
221 VarInt::from_u32(8).encode(&mut data);
222 data.extend_from_slice(b"\x00\x00\x01\xa4test");
223
224 let mut buf = data.as_slice();
225 let capsule = Capsule::decode(&mut buf).unwrap();
226
227 match capsule {
228 Capsule::CloseWebTransportSession {
229 code: error_code,
230 reason: error_message,
231 } => {
232 assert_eq!(error_code, 420);
233 assert_eq!(error_message, "test");
234 }
235 _ => panic!("Expected CloseWebTransportSession"),
236 }
237
238 assert_eq!(buf.len(), 0); }
240
241 #[test]
242 fn test_close_webtransport_session_encode() {
243 let capsule = Capsule::CloseWebTransportSession {
244 code: 420,
245 reason: "test".to_string(),
246 };
247
248 let mut buf = Vec::new();
249 capsule.encode(&mut buf).unwrap();
250
251 assert_eq!(buf, b"\x68\x43\x08\x00\x00\x01\xa4test");
254 }
255
256 #[test]
257 fn test_close_webtransport_session_roundtrip() {
258 let original = Capsule::CloseWebTransportSession {
259 code: 12345,
260 reason: "Connection closed by application".to_string(),
261 };
262
263 let mut buf = Vec::new();
264 original.encode(&mut buf).unwrap();
265
266 let mut read_buf = buf.as_slice();
267 let decoded = Capsule::decode(&mut read_buf).unwrap();
268
269 assert_eq!(original, decoded);
270 assert_eq!(read_buf.len(), 0); }
272
273 #[test]
274 fn test_empty_error_message() {
275 let capsule = Capsule::CloseWebTransportSession {
276 code: 0,
277 reason: String::new(),
278 };
279
280 let mut buf = Vec::new();
281 capsule.encode(&mut buf).unwrap();
282
283 assert_eq!(buf, b"\x68\x43\x04\x00\x00\x00\x00");
285
286 let mut read_buf = buf.as_slice();
287 let decoded = Capsule::decode(&mut read_buf).unwrap();
288 assert_eq!(capsule, decoded);
289 }
290
291 #[test]
292 fn test_invalid_utf8() {
293 let mut data = Vec::new();
295 VarInt::from_u64(0x2843).unwrap().encode(&mut data); VarInt::from_u32(5).encode(&mut data); data.extend_from_slice(b"\x00\x00\x00\x00"); data.push(0xFF); let mut buf = data.as_slice();
301 let result = Capsule::decode(&mut buf);
302 assert!(matches!(result, Err(CapsuleError::InvalidUtf8)));
303 }
304
305 #[test]
306 fn test_truncated_error_code() {
307 let mut data = Vec::new();
309 VarInt::from_u64(0x2843).unwrap().encode(&mut data); VarInt::from_u32(3).encode(&mut data); data.extend_from_slice(b"\x00\x00\x00"); let mut buf = data.as_slice();
314 let result = Capsule::decode(&mut buf);
315 assert!(matches!(result, Err(CapsuleError::UnexpectedEnd)));
316 }
317
318 #[test]
319 fn test_unknown_capsule() {
320 let unknown_type = 0x1234u64;
322 let payload_data = b"unknown payload";
323
324 let mut data = Vec::new();
325 VarInt::from_u64(unknown_type).unwrap().encode(&mut data);
326 VarInt::from_u32(payload_data.len() as u32).encode(&mut data);
327 data.extend_from_slice(payload_data);
328
329 let mut buf = data.as_slice();
330 let capsule = Capsule::decode(&mut buf).unwrap();
331
332 match capsule {
333 Capsule::Unknown { typ, payload } => {
334 assert_eq!(typ.into_inner(), unknown_type);
335 assert_eq!(payload.as_ref(), payload_data);
336 }
337 _ => panic!("Expected Unknown capsule"),
338 }
339 }
340
341 #[test]
342 fn test_unknown_capsule_roundtrip() {
343 let capsule = Capsule::Unknown {
344 typ: VarInt::from_u64(0x9999).unwrap(),
345 payload: Bytes::from("test payload"),
346 };
347
348 let mut buf = Vec::new();
349 capsule.encode(&mut buf).unwrap();
350
351 let mut read_buf = buf.as_slice();
352 let decoded = Capsule::decode(&mut read_buf).unwrap();
353
354 assert_eq!(capsule, decoded);
355 assert_eq!(read_buf.len(), 0);
356 }
357
358 #[test]
359 fn test_oversized_close_reason_is_rejected_when_encoding() {
360 let capsule = Capsule::CloseWebTransportSession {
361 code: 0,
362 reason: "x".repeat(MAX_MESSAGE_SIZE + 1),
363 };
364
365 assert!(matches!(
366 capsule.encode(&mut Vec::new()),
367 Err(CapsuleError::MessageTooLong)
368 ));
369 }
370}