1use std::collections::BTreeMap;
28
29use crate::quiche;
30use qlog::events::http3::FrameCreated;
31use qlog::events::http3::Http3Frame;
32use qlog::events::http3::HttpHeader;
33use qlog::events::http3::Initiator;
34use qlog::events::http3::StreamTypeSet;
35use qlog::events::quic::ErrorSpace;
36use qlog::events::quic::PacketSent;
37use qlog::events::quic::QuicFrame;
38use qlog::events::Event;
39use qlog::events::EventData;
40use qlog::events::ExData;
41use qlog::events::JsonEvent;
42use qlog::events::RawInfo;
43use quiche::h3::frame::Frame;
44use quiche::h3::NameValue;
45
46use serde_json::json;
47
48use crate::actions::h3::Action;
49use crate::actions::h3::WaitType;
50use crate::encode_header_block;
51use crate::encode_header_block_literal;
52use crate::fake_packet_sent;
53use crate::HTTP3_CONTROL_STREAM_TYPE_ID;
54use crate::HTTP3_PUSH_STREAM_TYPE_ID;
55use crate::QPACK_DECODER_STREAM_TYPE_ID;
56use crate::QPACK_ENCODER_STREAM_TYPE_ID;
57
58pub enum QlogEvent {
61 Event {
62 data: Box<EventData>,
63 ex_data: ExData,
64 },
65 JsonEvent(JsonEvent),
66}
67
68pub type QlogEvents = Vec<QlogEvent>;
70
71pub struct H3Actions(pub Vec<Action>);
73
74pub struct H3FrameCreatedEx {
76 frame_created: FrameCreated,
77 ex_data: ExData,
78}
79
80impl From<&Action> for QlogEvents {
81 fn from(action: &Action) -> Self {
82 match action {
83 Action::SendFrame {
84 stream_id,
85 fin_stream,
86 frame,
87 ..
88 } => {
89 let frame_ev = EventData::Http3FrameCreated(FrameCreated {
90 stream_id: *stream_id,
91 frame: frame.to_qlog(),
92 ..Default::default()
93 });
94
95 let mut ex = BTreeMap::new();
96
97 if *fin_stream {
98 ex.insert("fin_stream".to_string(), json!(true));
99 }
100
101 vec![QlogEvent::Event {
102 data: Box::new(frame_ev),
103 ex_data: ex,
104 }]
105 },
106
107 Action::SendHeadersFrame {
108 stream_id,
109 fin_stream,
110 headers,
111 literal_headers,
112 ..
113 } => {
114 let qlog_headers = headers
115 .iter()
116 .map(|h| qlog::events::http3::HttpHeader {
117 name: Some(
118 String::from_utf8_lossy(h.name()).into_owned(),
119 ),
120 name_bytes: None,
121 value: Some(
122 String::from_utf8_lossy(h.value()).into_owned(),
123 ),
124 value_bytes: None,
125 })
126 .collect();
127
128 let frame = Http3Frame::Headers {
129 headers: qlog_headers,
130 raw: None,
131 };
132
133 let frame_ev = EventData::Http3FrameCreated(FrameCreated {
134 stream_id: *stream_id,
135 frame,
136 ..Default::default()
137 });
138
139 let mut ex = BTreeMap::new();
140
141 if *fin_stream {
142 ex.insert("fin_stream".to_string(), json!(true));
143 }
144
145 if *literal_headers {
146 ex.insert("literal_headers".to_string(), json!(true));
147 }
148
149 vec![QlogEvent::Event {
150 data: Box::new(frame_ev),
151 ex_data: ex,
152 }]
153 },
154
155 Action::OpenUniStream {
156 stream_id,
157 fin_stream,
158 stream_type,
159 ..
160 } => {
161 let ty = match *stream_type {
162 HTTP3_CONTROL_STREAM_TYPE_ID =>
163 qlog::events::http3::StreamType::Control,
164 HTTP3_PUSH_STREAM_TYPE_ID =>
165 qlog::events::http3::StreamType::Push,
166 QPACK_ENCODER_STREAM_TYPE_ID =>
167 qlog::events::http3::StreamType::QpackEncode,
168 QPACK_DECODER_STREAM_TYPE_ID =>
169 qlog::events::http3::StreamType::QpackDecode,
170
171 _ => qlog::events::http3::StreamType::Unknown,
172 };
173 let ty_val =
174 if matches!(ty, qlog::events::http3::StreamType::Unknown) {
175 Some(*stream_type)
176 } else {
177 None
178 };
179
180 let stream_ev = EventData::Http3StreamTypeSet(StreamTypeSet {
181 initiator: Some(Initiator::Local),
182 stream_id: *stream_id,
183 stream_type: ty,
184 stream_type_bytes: ty_val,
185 ..Default::default()
186 });
187 let mut ex = BTreeMap::new();
188
189 if *fin_stream {
190 ex.insert("fin_stream".to_string(), json!(true));
191 }
192
193 vec![QlogEvent::Event {
194 data: Box::new(stream_ev),
195 ex_data: ex,
196 }]
197 },
198
199 Action::StreamBytes {
200 stream_id,
201 fin_stream,
202 bytes,
203 ..
204 } => {
205 let len = bytes.len() as u64;
206 let ev = fake_packet_sent(Some(vec![QuicFrame::Stream {
207 stream_id: *stream_id,
208 fin: Some(*fin_stream),
209 offset: None,
211 raw: Some(Box::new(RawInfo {
212 length: None,
213 payload_length: Some(len),
214 data: String::from_utf8(bytes.clone()).ok().map(Box::new),
215 })),
216 }]));
217
218 vec![QlogEvent::Event {
219 data: Box::new(ev),
220 ex_data: BTreeMap::new(),
221 }]
222 },
223
224 Action::SendDatagram { payload } => {
225 let len = payload.len() as u64;
226 let ev = fake_packet_sent(Some(vec![QuicFrame::Datagram {
227 raw: Some(Box::new(RawInfo {
228 length: None,
229 payload_length: Some(len),
230 data: String::from_utf8(payload.clone())
231 .ok()
232 .map(Box::new),
233 })),
234 }]));
235
236 vec![QlogEvent::Event {
237 data: Box::new(ev),
238 ex_data: BTreeMap::new(),
239 }]
240 },
241
242 Action::ResetStream {
243 stream_id,
244 error_code,
245 } => {
246 let ev = fake_packet_sent(Some(vec![QuicFrame::ResetStream {
247 stream_id: *stream_id,
248 error: qlog::events::ApplicationError::Unknown,
249 error_code: Some(*error_code),
250 final_size: 0,
251 raw: None,
252 }]));
253 vec![QlogEvent::Event {
254 data: Box::new(ev),
255 ex_data: BTreeMap::new(),
256 }]
257 },
258
259 Action::StopSending {
260 stream_id,
261 error_code,
262 } => {
263 let ev = fake_packet_sent(Some(vec![QuicFrame::StopSending {
264 stream_id: *stream_id,
265 error: qlog::events::ApplicationError::Unknown,
266 error_code: Some(*error_code),
267 raw: None,
268 }]));
269 vec![QlogEvent::Event {
270 data: Box::new(ev),
271 ex_data: BTreeMap::new(),
272 }]
273 },
274
275 Action::Wait { wait_type } => {
276 let name = "h3i:wait".into();
277
278 let data = match wait_type {
279 d @ WaitType::WaitDuration(_) =>
280 serde_json::to_value(d).unwrap(),
281 WaitType::StreamEvent(event) =>
282 serde_json::to_value(event).unwrap(),
283 d @ WaitType::CanOpenNumStreams(_) =>
284 serde_json::to_value(d).unwrap(),
285 };
286
287 vec![QlogEvent::JsonEvent(qlog::events::JsonEvent {
288 time: 0.0,
289 importance: qlog::events::EventImportance::Core,
290 name,
291 data,
292 })]
293 },
294
295 Action::ConnectionClose { error } => {
296 let error_space = if error.is_app {
297 ErrorSpace::Application
298 } else {
299 ErrorSpace::Transport
300 };
301
302 let reason = if error.reason.is_empty() {
303 None
304 } else {
305 Some(String::from_utf8(error.reason.clone()).unwrap())
306 };
307
308 let ev =
309 fake_packet_sent(Some(vec![QuicFrame::ConnectionClose {
310 error_space: Some(error_space),
311 error: None,
312 error_code: Some(error.error_code),
313 reason,
314 reason_bytes: None,
315 trigger_frame_type: None,
316 }]));
317
318 vec![QlogEvent::Event {
319 data: Box::new(ev),
320 ex_data: BTreeMap::new(),
321 }]
322 },
323
324 Action::FlushPackets => {
325 vec![]
326 },
327 }
328 }
329}
330
331pub fn actions_from_qlog(event: Event, host_override: Option<&str>) -> H3Actions {
332 let mut actions = vec![];
333 match &event.data {
334 EventData::QuicPacketSent(ps) => {
335 let packet_actions: H3Actions = ps.into();
336 actions.extend(packet_actions.0);
337 },
338
339 EventData::Http3FrameCreated(fc) => {
340 let mut frame_created = H3FrameCreatedEx {
341 frame_created: fc.clone(),
342 ex_data: event.ex_data.as_ref().clone(),
343 };
344
345 if let Some(host) = host_override {
348 frame_created
349 .ex_data
350 .insert("host_override".into(), host.into());
351 }
352
353 actions.push(frame_created.into());
354 },
355
356 EventData::Http3StreamTypeSet(st) => {
357 let stream_actions = from_qlog_stream_type_set(st, &event.ex_data);
358 actions.extend(stream_actions);
359 },
360
361 _ => (),
362 }
363
364 H3Actions(actions)
365}
366
367impl From<JsonEvent> for H3Actions {
368 fn from(event: JsonEvent) -> Self {
369 let mut actions = vec![];
370 match event.name.as_ref() {
371 "h3i:wait" => {
372 let wait_type =
373 serde_json::from_value::<WaitType>(event.clone().data);
374
375 if let Ok(wt) = wait_type {
376 actions.push(Action::Wait { wait_type: wt });
377 } else {
378 log::debug!("couldn't create action from event: {event:?}");
379 }
380 },
381 _ => unimplemented!(),
382 }
383
384 Self(actions)
385 }
386}
387
388impl From<&PacketSent> for H3Actions {
389 fn from(ps: &PacketSent) -> Self {
390 let mut actions = vec![];
391 if let Some(frames) = &ps.frames {
392 for frame in frames {
393 match &frame {
394 QuicFrame::ResetStream {
396 stream_id,
397 error_code,
398 ..
399 } => actions.push(Action::ResetStream {
400 stream_id: *stream_id,
401 error_code: error_code.unwrap_or_default(),
402 }),
403
404 QuicFrame::StopSending {
405 stream_id,
406 error_code,
407 ..
408 } => actions.push(Action::StopSending {
409 stream_id: *stream_id,
410 error_code: error_code.unwrap_or_default(),
411 }),
412
413 QuicFrame::ConnectionClose {
414 error_space,
415 error_code,
416 reason,
417 ..
418 } => {
419 let is_app = matches!(
420 error_space.as_ref().expect(
421 "invalid CC frame in qlog input, no error space"
422 ),
423 ErrorSpace::Application
424 );
425
426 actions.push(Action::ConnectionClose {
427 error: quiche::ConnectionError {
428 is_app,
429 error_code: error_code.expect("invalid CC frame in qlog input, no error code"),
432 reason: reason
433 .as_ref()
434 .map(|s| s.as_bytes().to_vec())
435 .unwrap_or_default(),
436 },
437 })
438 },
439
440 QuicFrame::Stream { stream_id, fin, .. } => {
441 let fin = fin.unwrap_or_default();
442
443 if fin {
444 actions.push(Action::StreamBytes {
445 stream_id: *stream_id,
446 fin_stream: true,
447 bytes: vec![],
448 expected_result: Default::default(),
449 });
450 }
451 },
452
453 QuicFrame::Datagram { raw, .. } => {
454 actions.push(Action::SendDatagram {
455 payload: (*raw
456 .clone()
457 .unwrap_or_default()
458 .data
459 .unwrap_or_default())
460 .into(),
461 });
462 },
463 _ => (),
464 }
465 }
466 }
467
468 Self(actions)
469 }
470}
471
472fn map_header(
473 hdr: &HttpHeader, host_override: Option<&str>,
474) -> quiche::h3::Header {
475 let name = hdr.name.as_deref().unwrap_or("");
476 let value = hdr.value.as_deref().unwrap_or("");
477
478 if name.eq_ignore_ascii_case(":authority") ||
479 name.eq_ignore_ascii_case("host")
480 {
481 if let Some(host) = host_override {
482 return quiche::h3::Header::new(name.as_bytes(), host.as_bytes());
483 }
484 }
485
486 quiche::h3::Header::new(name.as_bytes(), value.as_bytes())
487}
488
489impl From<H3FrameCreatedEx> for Action {
490 fn from(value: H3FrameCreatedEx) -> Self {
491 let stream_id = value.frame_created.stream_id;
492 let fin_stream = value
493 .ex_data
494 .get("fin_stream")
495 .unwrap_or(&serde_json::Value::Null)
496 .as_bool()
497 .unwrap_or_default();
498 let host_override = value
499 .ex_data
500 .get("host_override")
501 .unwrap_or(&serde_json::Value::Null)
502 .as_str();
503
504 let ret = match &value.frame_created.frame {
505 Http3Frame::Settings { settings, .. } => {
506 let mut raw_settings = vec![];
507 let mut additional_settings = vec![];
508 for s in settings {
511 let name = s.name.as_deref().unwrap_or("");
512 match name {
513 "MAX_FIELD_SECTION_SIZE" =>
514 raw_settings.push((0x6, s.value)),
515 "QPACK_MAX_TABLE_CAPACITY" =>
516 raw_settings.push((0x1, s.value)),
517 "QPACK_BLOCKED_STREAMS" =>
518 raw_settings.push((0x7, s.value)),
519 "SETTINGS_ENABLE_CONNECT_PROTOCOL" =>
520 raw_settings.push((0x8, s.value)),
521 "H3_DATAGRAM" => raw_settings.push((0x33, s.value)),
522
523 _ =>
524 if let Some(ty) = s.name_bytes {
525 raw_settings.push((ty, s.value));
526 additional_settings.push((ty, s.value));
527 } else if let Ok(ty) = name.parse::<u64>() {
528 raw_settings.push((ty, s.value));
529 additional_settings.push((ty, s.value));
530 },
531 }
532 }
533
534 Action::SendFrame {
535 stream_id,
536 fin_stream,
537 frame: Frame::Settings {
538 max_field_section_size: None,
539 qpack_max_table_capacity: None,
540 qpack_blocked_streams: None,
541 connect_protocol_enabled: None,
542 h3_datagram: None,
543 grease: None,
544 raw: Some(raw_settings),
545 additional_settings: Some(additional_settings),
546 },
547 expected_result: Default::default(),
548 }
549 },
550
551 Http3Frame::Headers { headers, .. } => {
552 let hdrs: Vec<quiche::h3::Header> = headers
553 .iter()
554 .map(|h| map_header(h, host_override))
555 .collect();
556
557 let literal_headers = value
558 .ex_data
559 .get("literal_headers")
560 .unwrap_or(&serde_json::Value::Null)
561 .as_bool()
562 .unwrap_or_default();
563
564 let header_block = if literal_headers {
565 encode_header_block_literal(&hdrs).unwrap()
566 } else {
567 encode_header_block(&hdrs).unwrap()
568 };
569
570 Action::SendHeadersFrame {
571 stream_id,
572 fin_stream,
573 literal_headers,
574 headers: hdrs,
575 frame: Frame::Headers { header_block },
576 expected_result: Default::default(),
577 }
578 },
579
580 Http3Frame::Data { raw } => {
581 let mut payload = vec![];
582 if let Some(r) = raw {
583 payload =
584 r.data.clone().unwrap_or_default().as_bytes().to_vec();
585 }
586
587 Action::SendFrame {
588 stream_id,
589 fin_stream,
590 frame: Frame::Data { payload },
591 expected_result: Default::default(),
592 }
593 },
594
595 Http3Frame::Goaway { id, .. } => Action::SendFrame {
596 stream_id,
597 fin_stream,
598 frame: Frame::GoAway { id: *id },
599 expected_result: Default::default(),
600 },
601
602 _ => unimplemented!(),
603 };
604
605 ret
606 }
607}
608
609fn from_qlog_stream_type_set(
610 st: &StreamTypeSet, ex_data: &ExData,
611) -> Vec<Action> {
612 let mut actions = vec![];
613 let fin_stream = parse_ex_data(ex_data);
614 let stream_type = match st.stream_type {
615 qlog::events::http3::StreamType::Control => Some(0x0),
616 qlog::events::http3::StreamType::Push => Some(0x1),
617 qlog::events::http3::StreamType::QpackEncode => Some(0x2),
618 qlog::events::http3::StreamType::QpackDecode => Some(0x3),
619 qlog::events::http3::StreamType::Reserved |
620 qlog::events::http3::StreamType::Unknown => st.stream_type_bytes,
621 _ => None,
622 };
623
624 if let Some(ty) = stream_type {
625 actions.push(Action::OpenUniStream {
626 stream_id: st.stream_id,
627 fin_stream,
628 stream_type: ty,
629 expected_result: Default::default(),
630 })
631 }
632
633 actions
634}
635
636fn parse_ex_data(ex_data: &ExData) -> bool {
637 ex_data
638 .get("fin_stream")
639 .unwrap_or(&serde_json::Value::Null)
640 .as_bool()
641 .unwrap_or_default()
642}
643
644#[cfg(test)]
645mod tests {
646 use crate::actions::h3::StreamEvent;
647 use crate::actions::h3::StreamEventType;
648 use crate::encode_header_block_literal;
649 use std::time::Duration;
650
651 use super::*;
652 use quiche::h3::Header;
653 use serde_json;
654
655 const NOW: f64 = 123.0;
656 const H3I_WAIT: &str = "h3i:wait";
657
658 #[test]
659 fn ser_duration_wait() {
660 let ev = JsonEvent {
661 time: NOW,
662 importance: qlog::events::EventImportance::Core,
663 name: H3I_WAIT.to_string(),
664 data: serde_json::to_value(WaitType::WaitDuration(
665 Duration::from_millis(12345),
666 ))
667 .unwrap(),
668 };
669 let serialized = serde_json::to_string(&ev);
670
671 let expected =
672 r#"{"time":123.0,"name":"h3i:wait","data":{"duration":12345.0}}"#;
673 assert_eq!(&serialized.unwrap(), expected);
674 }
675
676 #[test]
677 fn deser_duration_wait() {
678 let ev = JsonEvent {
679 time: NOW,
680 importance: qlog::events::EventImportance::Core,
681 name: H3I_WAIT.to_string(),
682 data: serde_json::to_value(WaitType::WaitDuration(
683 Duration::from_millis(12345),
684 ))
685 .unwrap(),
686 };
687
688 let expected =
689 r#"{"time":123.0,"name":"h3i:wait","data":{"duration":12345.0}}"#;
690 let deser = serde_json::from_str::<JsonEvent>(expected).unwrap();
691 assert_eq!(deser.data, ev.data);
692 }
693
694 #[test]
695 fn ser_stream_wait() {
696 let expected = r#"{"time":123.0,"name":"h3i:wait","data":{"stream_id":0,"type":"data"}}"#;
697 let ev = JsonEvent {
698 time: NOW,
699 importance: qlog::events::EventImportance::Core,
700 name: H3I_WAIT.to_string(),
701 data: serde_json::to_value(StreamEvent {
702 stream_id: 0,
703 event_type: StreamEventType::Data,
704 })
705 .unwrap(),
706 };
707
708 let serialized = serde_json::to_string(&ev);
709 assert_eq!(&serialized.unwrap(), expected);
710 }
711
712 #[test]
713 fn deser_stream_wait() {
714 let ev = JsonEvent {
715 time: NOW,
716 importance: qlog::events::EventImportance::Core,
717 name: H3I_WAIT.to_string(),
718 data: serde_json::to_value(StreamEvent {
719 stream_id: 0,
720 event_type: StreamEventType::Data,
721 })
722 .unwrap(),
723 };
724
725 let expected = r#"{"time":123.0,"name":"h3i:wait","data":{"stream_id":0,"type":"data"}}"#;
726 let deser = serde_json::from_str::<JsonEvent>(expected).unwrap();
727 assert_eq!(deser.data, ev.data);
728 }
729
730 #[test]
731 fn deser_http_headers_to_action() {
732 let serialized = r#"{"time":0.074725,"name":"http3:frame_created","data":{"stream_id":0,"frame":{"frame_type":"headers","headers":[{"name":":method","value":"GET"},{"name":":authority","value":"example.net"},{"name":":path","value":"/"},{"name":":scheme","value":"https"}]}},"fin_stream":true}"#;
733 let deserialized = serde_json::from_str::<Event>(serialized).unwrap();
734 let actions = actions_from_qlog(deserialized, None);
735 assert!(actions.0.len() == 1);
736
737 let headers = vec![
738 Header::new(b":method", b"GET"),
739 Header::new(b":authority", b"example.net"),
740 Header::new(b":path", b"/"),
741 Header::new(b":scheme", b"https"),
742 ];
743 let header_block = encode_header_block(&headers).unwrap();
744 let frame = Frame::Headers { header_block };
745 let expected = Action::SendHeadersFrame {
746 stream_id: 0,
747 fin_stream: true,
748 literal_headers: false,
749 headers,
750 frame,
751 expected_result: Default::default(),
752 };
753
754 assert_eq!(actions.0[0], expected);
755 }
756
757 #[test]
758 fn deser_http_headers_host_overrid_to_action() {
759 let serialized = r#"{"time":0.074725,"name":"http3:frame_created","data":{"stream_id":0,"frame":{"frame_type":"headers","headers":[{"name":":method","value":"GET"},{"name":":authority","value":"bla.com"},{"name":":path","value":"/"},{"name":":scheme","value":"https"}]}},"fin_stream":true}"#;
760 let deserialized = serde_json::from_str::<Event>(serialized).unwrap();
761 let actions = actions_from_qlog(deserialized, Some("example.org"));
762 assert!(actions.0.len() == 1);
763
764 let headers = vec![
765 Header::new(b":method", b"GET"),
766 Header::new(b":authority", b"example.org"),
767 Header::new(b":path", b"/"),
768 Header::new(b":scheme", b"https"),
769 ];
770 let header_block = encode_header_block(&headers).unwrap();
771 let frame = Frame::Headers { header_block };
772 let expected = Action::SendHeadersFrame {
773 stream_id: 0,
774 fin_stream: true,
775 literal_headers: false,
776 headers,
777 frame,
778 expected_result: Default::default(),
779 };
780
781 assert_eq!(actions.0[0], expected);
782 }
783
784 #[test]
785 fn deser_http_headers_literal_to_action() {
786 let serialized = r#"{"time":0.074725,"name":"http3:frame_created","data":{"stream_id":0,"frame":{"frame_type":"headers","headers":[{"name":":method","value":"GET"},{"name":":authority","value":"bla.com"},{"name":":path","value":"/"},{"name":":scheme","value":"https"},{"name":"Foo","value":"bar"}]}},"fin_stream":true,"literal_headers":true}"#;
787 let deserialized = serde_json::from_str::<Event>(serialized).unwrap();
788 let actions = actions_from_qlog(deserialized, None);
789 assert!(actions.0.len() == 1);
790
791 let headers = vec![
792 Header::new(b":method", b"GET"),
793 Header::new(b":authority", b"bla.com"),
794 Header::new(b":path", b"/"),
795 Header::new(b":scheme", b"https"),
796 Header::new(b"Foo", b"bar"),
797 ];
798 let header_block = encode_header_block_literal(&headers).unwrap();
799 let frame = Frame::Headers { header_block };
800 let expected = Action::SendHeadersFrame {
801 stream_id: 0,
802 fin_stream: true,
803 literal_headers: true,
804 headers,
805 frame,
806 expected_result: Default::default(),
807 };
808
809 assert_eq!(actions.0[0], expected);
810 }
811}