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