1use std::cmp;
30use std::convert::TryFrom;
31use std::error::Error;
32use std::fmt::Debug;
33use std::sync::Arc;
34
35use multimap::MultiMap;
36use quiche;
37
38use quiche::h3::frame::Frame as QFrame;
39use quiche::h3::Header;
40use quiche::h3::NameValue;
41use serde::ser::SerializeStruct;
42use serde::ser::Serializer;
43use serde::Serialize;
44
45use crate::client::connection_summary::MAX_SERIALIZED_BUFFER_LEN;
46use crate::encode_header_block;
47
48pub type BoxError = Box<dyn Error + Send + Sync + 'static>;
49
50#[derive(Debug, Eq, PartialEq, Clone)]
53pub enum H3iFrame {
54 QuicheH3(QFrame),
56 Headers(EnrichedHeaders),
58 ResetStream(ResetStream),
60}
61
62impl H3iFrame {
63 pub fn to_enriched_headers(&self) -> Option<EnrichedHeaders> {
67 if let H3iFrame::Headers(header) = self {
68 Some(header.clone())
69 } else {
70 None
71 }
72 }
73}
74
75impl Serialize for H3iFrame {
76 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
77 where
78 S: Serializer,
79 {
80 match self {
81 H3iFrame::QuicheH3(frame) => {
82 let mut state = s.serialize_struct("frame", 1)?;
83 let name = frame_name(frame);
84 state.serialize_field(name, &SerializableQFrame(frame))?;
85 state.end()
86 },
87 H3iFrame::Headers(headers) => {
88 let mut state = s.serialize_struct("enriched_headers", 1)?;
89 state.serialize_field("enriched_headers", headers)?;
90 state.end()
91 },
92 H3iFrame::ResetStream(reset) => {
93 let mut state = s.serialize_struct("reset_stream", 1)?;
94 state.serialize_field("reset_stream", reset)?;
95 state.end()
96 },
97 }
98 }
99}
100
101impl From<QFrame> for H3iFrame {
102 fn from(value: QFrame) -> Self {
103 Self::QuicheH3(value)
104 }
105}
106
107impl From<Vec<Header>> for H3iFrame {
108 fn from(value: Vec<Header>) -> Self {
109 Self::Headers(EnrichedHeaders::from(value))
110 }
111}
112
113pub type HeaderMap = MultiMap<Vec<u8>, Vec<u8>>;
114
115#[derive(Clone, PartialEq, Eq)]
117pub struct EnrichedHeaders {
118 header_block: Vec<u8>,
119 headers: Vec<Header>,
120 header_map: HeaderMap,
122}
123
124pub struct SerializableHeader<'a>(&'a Header);
126
127impl Serialize for SerializableHeader<'_> {
128 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
129 where
130 S: Serializer,
131 {
132 let mut state = s.serialize_struct("header", 2)?;
133 state.serialize_field("name", &String::from_utf8_lossy(self.0.name()))?;
134 state
135 .serialize_field("value", &String::from_utf8_lossy(self.0.value()))?;
136 state.end()
137 }
138}
139
140impl EnrichedHeaders {
141 pub fn headers(&self) -> &[Header] {
156 &self.headers
157 }
158
159 pub fn header_map(&self) -> &HeaderMap {
194 &self.header_map
195 }
196
197 pub fn status_code(&self) -> Option<&Vec<u8>> {
211 self.header_map.get(b":status".as_slice())
212 }
213}
214
215impl Serialize for EnrichedHeaders {
216 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
217 where
218 S: Serializer,
219 {
220 let mut state = s.serialize_struct("enriched_headers", 2)?;
221 state.serialize_field("header_block_len", &self.header_block.len())?;
222 let x: Vec<SerializableHeader> =
223 self.headers.iter().map(SerializableHeader).collect();
224 state.serialize_field("headers", &x)?;
225 state.end()
226 }
227}
228
229impl From<Vec<Header>> for EnrichedHeaders {
230 fn from(headers: Vec<Header>) -> Self {
231 let header_block = encode_header_block(&headers).unwrap();
232
233 let mut header_map: HeaderMap = MultiMap::with_capacity(headers.len());
234 for header in headers.iter() {
235 header_map.insert(header.name().to_vec(), header.value().to_vec());
236 }
237
238 Self {
239 header_block,
240 headers,
241 header_map,
242 }
243 }
244}
245
246impl TryFrom<QFrame> for EnrichedHeaders {
247 type Error = BoxError;
248
249 fn try_from(value: QFrame) -> Result<Self, Self::Error> {
250 match value {
251 QFrame::Headers { header_block } => {
252 let mut qpack_decoder = quiche::h3::qpack::Decoder::new();
253 let headers =
254 qpack_decoder.decode(&header_block, u64::MAX).unwrap();
255
256 Ok(EnrichedHeaders::from(headers))
257 },
258 _ => Err("Cannot convert non-Headers frame into HeadersFrame".into()),
259 }
260 }
261}
262
263impl Debug for EnrichedHeaders {
264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265 write!(f, "{:?}", self.headers)
266 }
267}
268
269#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
274pub struct ResetStream {
275 pub stream_id: u64,
277 pub error_code: u64,
279}
280
281fn frame_name(frame: &QFrame) -> &'static str {
282 match frame {
283 QFrame::Data { .. } => "DATA",
284 QFrame::Headers { .. } => "HEADERS",
285 QFrame::CancelPush { .. } => "CANCEL_PUSH",
286 QFrame::Settings { .. } => "SETTINGS",
287 QFrame::PushPromise { .. } => "PUSH_PROMISE",
288 QFrame::GoAway { .. } => "GO_AWAY",
289 QFrame::MaxPushId { .. } => "MAX_PUSH_ID",
290 QFrame::PriorityUpdateRequest { .. } => "PRIORITY_UPDATE(REQUEST)",
291 QFrame::PriorityUpdatePush { .. } => "PRIORITY_UPDATE(PUSH)",
292 QFrame::Unknown { .. } => "UNKNOWN",
293 }
294}
295
296pub struct SerializableQFrame<'a>(&'a QFrame);
298
299impl Serialize for SerializableQFrame<'_> {
300 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
301 where
302 S: Serializer,
303 {
304 let name = frame_name(self.0);
305 match self.0 {
306 QFrame::Data { payload } => {
307 let mut state = s.serialize_struct(name, 2)?;
308 let max = cmp::min(payload.len(), MAX_SERIALIZED_BUFFER_LEN);
309 state.serialize_field("payload_len", &payload.len())?;
310 state.serialize_field(
311 "payload",
312 &qlog::HexSlice::maybe_string(Some(&payload[..max])),
313 )?;
314 state.end()
315 },
316
317 QFrame::Headers { header_block } => {
318 let mut state = s.serialize_struct(name, 1)?;
319 state.serialize_field("header_block_len", &header_block.len())?;
320 state.end()
321 },
322
323 QFrame::CancelPush { push_id } => {
324 let mut state = s.serialize_struct(name, 1)?;
325 state.serialize_field("push_id", &push_id)?;
326 state.end()
327 },
328
329 QFrame::Settings {
330 max_field_section_size,
331 qpack_max_table_capacity,
332 qpack_blocked_streams,
333 connect_protocol_enabled,
334 h3_datagram,
335 grease: _,
336 additional_settings,
337 raw: _,
338 } => {
339 let mut state = s.serialize_struct(name, 6)?;
340 state.serialize_field(
341 "max_field_section_size",
342 &max_field_section_size,
343 )?;
344 state.serialize_field(
345 "qpack_max_table_capacity",
346 &qpack_max_table_capacity,
347 )?;
348 state.serialize_field(
349 "qpack_blocked_streams",
350 &qpack_blocked_streams,
351 )?;
352 state.serialize_field(
353 "connect_protocol_enabled",
354 &connect_protocol_enabled,
355 )?;
356 state.serialize_field("h3_datagram", &h3_datagram)?;
357 state.serialize_field(
358 "additional_settings",
359 &additional_settings,
360 )?;
361 state.end()
362 },
363
364 QFrame::PushPromise {
365 push_id,
366 header_block,
367 } => {
368 let mut state = s.serialize_struct(name, 2)?;
369 state.serialize_field("push_id", &push_id)?;
370 state.serialize_field("header_block_len", &header_block.len())?;
371 state.end()
372 },
373
374 QFrame::GoAway { id } => {
375 let mut state = s.serialize_struct(name, 1)?;
376 state.serialize_field("id", &id)?;
377 state.end()
378 },
379
380 QFrame::MaxPushId { push_id } => {
381 let mut state = s.serialize_struct(name, 1)?;
382 state.serialize_field("push_id", &push_id)?;
383 state.end()
384 },
385
386 QFrame::PriorityUpdateRequest {
387 prioritized_element_id,
388 priority_field_value,
389 } => {
390 let mut state = s.serialize_struct(name, 3)?;
391 state.serialize_field(
392 "prioritized_element_id",
393 &prioritized_element_id,
394 )?;
395
396 let max = cmp::min(
397 priority_field_value.len(),
398 MAX_SERIALIZED_BUFFER_LEN,
399 );
400 state.serialize_field(
401 "priority_field_value_len",
402 &priority_field_value.len(),
403 )?;
404 state.serialize_field(
405 "priority_field_value",
406 &String::from_utf8_lossy(&priority_field_value[..max]),
407 )?;
408 state.end()
409 },
410
411 QFrame::PriorityUpdatePush {
412 prioritized_element_id,
413 priority_field_value,
414 } => {
415 let mut state = s.serialize_struct(name, 3)?;
416 state.serialize_field(
417 "prioritized_element_id",
418 &prioritized_element_id,
419 )?;
420 let max = cmp::min(
421 priority_field_value.len(),
422 MAX_SERIALIZED_BUFFER_LEN,
423 );
424 state.serialize_field(
425 "priority_field_value_len",
426 &priority_field_value.len(),
427 )?;
428 state.serialize_field(
429 "priority_field_value",
430 &String::from_utf8_lossy(&priority_field_value[..max]),
431 )?;
432 state.end()
433 },
434
435 QFrame::Unknown { raw_type, payload } => {
436 let mut state = s.serialize_struct(name, 3)?;
437 state.serialize_field("raw_type", &raw_type)?;
438 let max = cmp::min(payload.len(), MAX_SERIALIZED_BUFFER_LEN);
439 state.serialize_field("payload_len", &payload.len())?;
440 state.serialize_field(
441 "payload",
442 &qlog::HexSlice::maybe_string(Some(&payload[..max])),
443 )?;
444 state.end()
445 },
446 }
447 }
448}
449
450type CustomEquivalenceHandler =
451 Box<dyn for<'f> Fn(&'f H3iFrame) -> bool + Send + Sync + 'static>;
452
453#[derive(Clone)]
454enum Comparator {
455 Frame(H3iFrame),
456 Fn(Arc<CustomEquivalenceHandler>),
466}
467
468impl Serialize for Comparator {
469 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
470 where
471 S: Serializer,
472 {
473 match self {
474 Self::Fn(_) => serializer.serialize_str("<comparator_fn>"),
475 Self::Frame(f) => {
476 let mut frame_ser = serializer.serialize_struct("frame", 1)?;
477 frame_ser.serialize_field("frame", f)?;
478 frame_ser.end()
479 },
480 }
481 }
482}
483
484#[derive(Serialize, Clone)]
489pub struct CloseTriggerFrame {
490 stream_id: u64,
491 comparator: Comparator,
492}
493
494impl CloseTriggerFrame {
495 pub fn new(stream_id: u64, frame: impl Into<H3iFrame>) -> Self {
514 Self {
515 stream_id,
516 comparator: Comparator::Frame(frame.into()),
517 }
518 }
519
520 pub fn new_with_comparator<F>(stream_id: u64, comparator_fn: F) -> Self
527 where
528 F: Fn(&H3iFrame) -> bool + Send + Sync + 'static,
529 {
530 Self {
531 stream_id,
532 comparator: Comparator::Fn(Arc::new(Box::new(comparator_fn))),
533 }
534 }
535
536 pub(crate) fn stream_id(&self) -> u64 {
537 self.stream_id
538 }
539
540 pub(crate) fn is_equivalent(&self, other: &H3iFrame) -> bool {
541 let frame = match &self.comparator {
542 Comparator::Fn(compare) => return compare(other),
543 Comparator::Frame(frame) => frame,
544 };
545
546 match frame {
547 H3iFrame::Headers(me) => {
548 let H3iFrame::Headers(other) = other else {
549 return false;
550 };
551
552 me.headers().iter().all(|m| other.headers().contains(m))
559 },
560 H3iFrame::QuicheH3(me) => match other {
561 H3iFrame::QuicheH3(other) => me == other,
562 _ => false,
563 },
564 H3iFrame::ResetStream(me) => match other {
565 H3iFrame::ResetStream(rs) => me == rs,
566 _ => false,
567 },
568 }
569 }
570}
571
572impl Debug for CloseTriggerFrame {
573 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574 let repr = match &self.comparator {
575 Comparator::Frame(frame) => format!("{frame:?}"),
576 Comparator::Fn(_) => "closure".to_string(),
577 };
578
579 write!(
580 f,
581 "CloseTriggerFrame {{ stream_id: {}, comparator: {repr} }}",
582 self.stream_id
583 )
584 }
585}
586
587impl PartialEq for CloseTriggerFrame {
588 fn eq(&self, other: &Self) -> bool {
589 match (&self.comparator, &other.comparator) {
590 (Comparator::Frame(this_frame), Comparator::Frame(other_frame)) =>
591 self.stream_id == other.stream_id && this_frame == other_frame,
592 _ => false,
593 }
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 use quiche::h3::frame::Frame;
601
602 #[test]
603 fn test_header_equivalence() {
604 let this = CloseTriggerFrame::new(0, vec![
605 Header::new(b"hello", b"world"),
606 Header::new(b"go", b"jets"),
607 ]);
608 let other: H3iFrame = vec![
609 Header::new(b"hello", b"world"),
610 Header::new(b"go", b"jets"),
611 Header::new(b"go", b"devils"),
612 ]
613 .into();
614
615 assert!(this.is_equivalent(&other));
616 }
617
618 #[test]
619 fn test_header_non_equivalence() {
620 let this = CloseTriggerFrame::new(0, vec![
621 Header::new(b"hello", b"world"),
622 Header::new(b"go", b"jets"),
623 Header::new(b"go", b"devils"),
624 ]);
625 let other: H3iFrame =
626 vec![Header::new(b"hello", b"world"), Header::new(b"go", b"jets")]
627 .into();
628
629 assert!(!this.is_equivalent(&other));
632 }
633
634 #[test]
635 fn test_rst_stream_equivalence() {
636 let mut rs = ResetStream {
637 stream_id: 0,
638 error_code: 57,
639 };
640
641 let this = CloseTriggerFrame::new(0, H3iFrame::ResetStream(rs.clone()));
642 let incoming = H3iFrame::ResetStream(rs.clone());
643 assert!(this.is_equivalent(&incoming));
644
645 rs.stream_id = 57;
646 let incoming = H3iFrame::ResetStream(rs);
647 assert!(!this.is_equivalent(&incoming));
648 }
649
650 #[test]
651 fn test_frame_equivalence() {
652 let mut d = Frame::Data {
653 payload: b"57".to_vec(),
654 };
655
656 let this = CloseTriggerFrame::new(0, H3iFrame::QuicheH3(d.clone()));
657 let incoming = H3iFrame::QuicheH3(d.clone());
658 assert!(this.is_equivalent(&incoming));
659
660 d = Frame::Data {
661 payload: b"go jets".to_vec(),
662 };
663 let incoming = H3iFrame::QuicheH3(d.clone());
664 assert!(!this.is_equivalent(&incoming));
665 }
666
667 #[test]
668 fn test_comparator() {
669 let this = CloseTriggerFrame::new_with_comparator(0, |frame| {
670 if let H3iFrame::Headers(..) = frame {
671 frame
672 .to_enriched_headers()
673 .unwrap()
674 .header_map()
675 .get(&b"cookie".to_vec())
676 .is_some_and(|v| {
677 std::str::from_utf8(v)
678 .map(|s| s.to_lowercase())
679 .unwrap()
680 .contains("cookie")
681 })
682 } else {
683 false
684 }
685 });
686
687 let incoming: H3iFrame =
688 vec![Header::new(b"cookie", b"SomeRandomCookie1234")].into();
689
690 assert!(this.is_equivalent(&incoming));
691 }
692}