Skip to main content

h3i/recordreplay/
qlog.rs

1// Copyright (C) 2024, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use 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
58/// A qlog event representation using either the official RFC format or the
59/// catch-al JSON event.
60pub enum QlogEvent {
61    Event {
62        data: Box<EventData>,
63        ex_data: ExData,
64    },
65    JsonEvent(JsonEvent),
66}
67
68/// A collection of [QlogEvent]s.
69pub type QlogEvents = Vec<QlogEvent>;
70
71/// A collection of [Action]s.
72pub struct H3Actions(pub Vec<Action>);
73
74/// A qlog HTTP/3 [FrameCreated] event, with [ExData].
75pub 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                    // ignore offset
210                    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            // Insert custom data so that conversion of frames to Actions can
346            // use it.
347            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                    // TODO add these
395                    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                                // TODO: remove unwrap when https://github.com/cloudflare/quiche/issues/1731
430                                // is done
431                                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                // This is ugly but it reflects ambiguity in the qlog
509                // specs.
510                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}