iroh_quinn_proto/connection/
qlog.rs

1//! Implements support for emitting qlog events.
2//!
3//! This uses the [`qlog`] crate to emit qlog events. The n0-qlog crate, and thus this
4//! implementation, is currently based on [draft-ietf-quic-qlog-main-schema-13] an
5//! [draft-ietf-quic-qlog-quic-events-12].
6//!
7//! [draft-ietf-quic-qlog-main-schema-13]: https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-13.html
8//! [draft-ietf-quic-qlog-quic-events-12]: https://www.ietf.org/archive/id/draft-ietf-quic-qlog-quic-events-12.html
9
10// Function bodies in this module are regularly cfg'd out
11#![allow(unused_variables)]
12
13#[cfg(not(feature = "qlog"))]
14use std::marker::PhantomData;
15#[cfg(feature = "qlog")]
16use std::sync::{Arc, Mutex};
17use std::{
18    net::{IpAddr, SocketAddr},
19    time::Duration,
20};
21
22#[cfg(feature = "qlog")]
23use qlog::{
24    CommonFields, HexSlice, TokenType, VantagePoint,
25    events::{
26        ApplicationError, ConnectionClosedFrameError, Event, EventData, RawInfo, TupleEndpointInfo,
27        quic::{
28            self, AckedRanges, AddressDiscoveryRole, ConnectionStarted, ErrorSpace, PacketHeader,
29            PacketLost, PacketLostTrigger, PacketReceived, PacketSent, PacketType,
30            ParametersRestored, ParametersSet, PreferredAddress, QlogTimerType, QuicFrame,
31            StreamType, TimerEventType, TimerType, TimerUpdated, TransportInitiator, TupleAssigned,
32        },
33    },
34    streamer::QlogStreamer,
35};
36#[cfg(feature = "qlog")]
37use tracing::warn;
38
39use crate::{
40    Connection, ConnectionId, Frame, Instant, PathId,
41    connection::{PathData, SentPacket, timer::Timer},
42    frame::{EcnCounts, StreamMeta},
43    packet::{Header, SpaceId},
44    range_set::ArrayRangeSet,
45    transport_parameters::TransportParameters,
46};
47#[cfg(feature = "qlog")]
48use crate::{
49    QlogConfig, Side, TransportErrorCode,
50    connection::timer::{ConnTimer, PathTimer},
51    frame::Close,
52};
53
54/// Shareable handle to a single qlog output stream
55#[cfg(feature = "qlog")]
56#[derive(Clone)]
57pub(crate) struct QlogStream(Arc<Mutex<QlogStreamer>>);
58
59#[cfg(feature = "qlog")]
60impl QlogStream {
61    pub(crate) fn new(
62        config: QlogConfig,
63        initial_dst_cid: ConnectionId,
64        side: Side,
65        now: Instant,
66    ) -> Result<Self, qlog::Error> {
67        let vantage_point = VantagePoint {
68            name: None,
69            ty: match side {
70                Side::Client => qlog::VantagePointType::Client,
71                Side::Server => qlog::VantagePointType::Server,
72            },
73            flow: None,
74        };
75
76        let common_fields = CommonFields {
77            group_id: Some(initial_dst_cid.to_string()),
78            ..Default::default()
79        };
80
81        let trace = qlog::TraceSeq::new(
82            config.title.clone(),
83            config.description.clone(),
84            Some(common_fields),
85            Some(vantage_point),
86            vec![],
87        );
88
89        let start_time = config.start_time.unwrap_or(now);
90
91        let mut streamer = QlogStreamer::new(
92            config.title,
93            config.description,
94            start_time,
95            trace,
96            qlog::events::EventImportance::Extra,
97            config.writer,
98        );
99
100        streamer.start_log()?;
101        Ok(Self(Arc::new(Mutex::new(streamer))))
102    }
103
104    fn emit_event(&self, event: EventData, now: Instant) {
105        self.emit_event_with_tuple_id(event, now, None);
106    }
107
108    fn emit_event_with_tuple_id(&self, event: EventData, now: Instant, tuple: Option<String>) {
109        // Time will be overwritten by `add_event_with_instant`
110        let mut event = Event::with_time(0.0, event);
111        event.tuple = tuple;
112        let mut qlog_streamer = self.0.lock().unwrap();
113        if let Err(e) = qlog_streamer.add_event_with_instant(event, now) {
114            warn!("could not emit qlog event: {e}");
115        }
116    }
117}
118
119/// A [`QlogStream`] that may be either dynamically disabled or compiled out entirely
120#[derive(Clone, Default)]
121pub(crate) struct QlogSink {
122    #[cfg(feature = "qlog")]
123    stream: Option<QlogStream>,
124}
125
126impl QlogSink {
127    #[cfg(feature = "qlog")]
128    pub(crate) fn new(
129        config: QlogConfig,
130        initial_dst_cid: ConnectionId,
131        side: Side,
132        now: Instant,
133    ) -> Self {
134        let stream = QlogStream::new(config, initial_dst_cid, side, now)
135            .inspect_err(|err| warn!("failed to initialize qlog streamer: {err}"))
136            .ok();
137        Self { stream }
138    }
139
140    pub(crate) fn emit_connection_started(
141        &self,
142        now: Instant,
143        loc_cid: ConnectionId,
144        rem_cid: ConnectionId,
145        remote: SocketAddr,
146        local_ip: Option<IpAddr>,
147        transport_params: &TransportParameters,
148    ) {
149        #[cfg(feature = "qlog")]
150        {
151            let Some(stream) = self.stream.as_ref() else {
152                return;
153            };
154            stream.emit_event(
155                EventData::ConnectionStarted(ConnectionStarted {
156                    local: tuple_endpoint_info(local_ip, None, Some(loc_cid)),
157                    remote: tuple_endpoint_info(
158                        Some(remote.ip()),
159                        Some(remote.port()),
160                        Some(rem_cid),
161                    ),
162                }),
163                now,
164            );
165
166            let params = transport_params.to_qlog(TransportInitiator::Local);
167            let event = EventData::ParametersSet(params);
168            stream.emit_event(event, now);
169        }
170    }
171
172    pub(super) fn emit_recovery_metrics(&self, path_id: PathId, path: &mut PathData, now: Instant) {
173        #[cfg(feature = "qlog")]
174        {
175            let Some(stream) = self.stream.as_ref() else {
176                return;
177            };
178
179            let Some(metrics) = path.qlog_recovery_metrics(path_id) else {
180                return;
181            };
182
183            stream.emit_event(EventData::MetricsUpdated(metrics), now);
184        }
185    }
186
187    pub(super) fn emit_packet_lost(
188        &self,
189        pn: u64,
190        info: &SentPacket,
191        loss_delay: Duration,
192        space: SpaceId,
193        now: Instant,
194    ) {
195        #[cfg(feature = "qlog")]
196        {
197            let Some(stream) = self.stream.as_ref() else {
198                return;
199            };
200
201            let event = PacketLost {
202                header: Some(PacketHeader {
203                    packet_number: Some(pn),
204                    packet_type: packet_type(space, false),
205                    length: Some(info.size),
206                    ..Default::default()
207                }),
208                frames: None,
209                trigger: Some(
210                    match info.time_sent.saturating_duration_since(now) >= loss_delay {
211                        true => PacketLostTrigger::TimeThreshold,
212                        false => PacketLostTrigger::ReorderingThreshold,
213                    },
214                ),
215                is_mtu_probe_packet: None,
216            };
217
218            stream.emit_event(EventData::PacketLost(event), now);
219        }
220    }
221
222    pub(super) fn emit_peer_transport_params_restored(&self, conn: &Connection, now: Instant) {
223        #[cfg(feature = "qlog")]
224        {
225            let Some(stream) = self.stream.as_ref() else {
226                return;
227            };
228            let params = conn.peer_params.to_qlog_restored();
229            let event = EventData::ParametersRestored(params);
230            stream.emit_event(event, now);
231        }
232    }
233
234    pub(super) fn emit_peer_transport_params_received(&self, conn: &Connection, now: Instant) {
235        #[cfg(feature = "qlog")]
236        {
237            let Some(stream) = self.stream.as_ref() else {
238                return;
239            };
240            let params = conn.peer_params.to_qlog(TransportInitiator::Remote);
241            let event = EventData::ParametersSet(params);
242            stream.emit_event(event, now);
243        }
244    }
245
246    pub(super) fn emit_tuple_assigned(&self, path_id: PathId, remote: SocketAddr, now: Instant) {
247        #[cfg(feature = "qlog")]
248        {
249            let Some(stream) = self.stream.as_ref() else {
250                return;
251            };
252            let tuple_id = fmt_tuple_id(path_id.as_u32() as u64);
253            let event = TupleAssigned {
254                tuple_id,
255                tuple_local: None,
256                tuple_remote: Some(tuple_endpoint_info(
257                    Some(remote.ip()),
258                    Some(remote.port()),
259                    None,
260                )),
261            };
262
263            stream.emit_event(EventData::TupleAssigned(event), now);
264        }
265    }
266
267    pub(super) fn emit_packet_sent(&self, packet: QlogSentPacket, now: Instant) {
268        #[cfg(feature = "qlog")]
269        {
270            let Some(stream) = self.stream.as_ref() else {
271                return;
272            };
273            let tuple_id = packet.inner.header.path_id.map(fmt_tuple_id);
274            stream.emit_event_with_tuple_id(EventData::PacketSent(packet.inner), now, tuple_id);
275        }
276    }
277
278    pub(super) fn emit_packet_received(&self, packet: QlogRecvPacket, now: Instant) {
279        #[cfg(feature = "qlog")]
280        {
281            let Some(stream) = self.stream.as_ref() else {
282                return;
283            };
284            let mut packet = packet;
285            packet.emit_padding();
286            let tuple_id = packet.inner.header.path_id.map(fmt_tuple_id);
287            let event = packet.inner;
288            stream.emit_event_with_tuple_id(EventData::PacketReceived(event), now, tuple_id);
289        }
290    }
291
292    /// Emits a timer event.
293    ///
294    /// This function is not public: Instead, create a [`QlogSinkWithTime`] via [`Self::with_time`] and use
295    /// its `emit_timer_` methods.
296    #[cfg(feature = "qlog")]
297    fn emit_timer(&self, timer: Timer, op: TimerOp, now: Instant) {
298        let Some(stream) = self.stream.as_ref() else {
299            return;
300        };
301
302        let timer_type: Option<TimerType> = match timer {
303            Timer::Conn(conn_timer) => match conn_timer {
304                ConnTimer::Idle => Some(QlogTimerType::IdleTimeout.into()),
305                ConnTimer::Close => Some(TimerType::custom("close")),
306                ConnTimer::KeyDiscard => Some(TimerType::custom("key_discard")),
307                ConnTimer::KeepAlive => Some(TimerType::custom("keep_alive")),
308                ConnTimer::PushNewCid => Some(TimerType::custom("push_new_cid")),
309            },
310            Timer::PerPath(_, path_timer) => match path_timer {
311                PathTimer::LossDetection => Some(QlogTimerType::LossTimeout.into()),
312                PathTimer::PathIdle => Some(TimerType::custom("path_idle")),
313                PathTimer::PathValidation => Some(QlogTimerType::PathValidation.into()),
314                PathTimer::PathChallengeLost => Some(TimerType::custom("path_challenge_lost")),
315                PathTimer::PathOpen => Some(TimerType::custom("path_open")),
316                PathTimer::PathKeepAlive => Some(TimerType::custom("path_keep_alive")),
317                PathTimer::Pacing => Some(TimerType::custom("pacing")),
318                PathTimer::MaxAckDelay => Some(QlogTimerType::Ack.into()),
319                PathTimer::DiscardPath => Some(TimerType::custom("discard_path")),
320            },
321        };
322
323        let Some(timer_type) = timer_type else {
324            return;
325        };
326
327        let delta = match op {
328            TimerOp::Set(instant) => instant
329                .checked_duration_since(now)
330                .map(|dur| dur.as_secs_f32() * 1000.),
331            _ => None,
332        };
333        let path_id = match timer {
334            Timer::Conn(_) => None,
335            Timer::PerPath(path_id, _) => Some(path_id.as_u32() as u64),
336        };
337
338        let event_type = match op {
339            TimerOp::Set(_) => TimerEventType::Set,
340            TimerOp::Expire => TimerEventType::Expired,
341            TimerOp::Cancelled => TimerEventType::Cancelled,
342        };
343
344        let event = TimerUpdated {
345            path_id,
346            timer_type: Some(timer_type),
347            timer_id: None,
348            packet_number_space: None,
349            event_type,
350            delta,
351        };
352        stream.emit_event(EventData::TimerUpdated(event), now);
353    }
354
355    /// Returns a [`QlogSinkWithTime`] that passes along a `now` timestamp.
356    ///
357    /// This may be used if you want to pass a [`QlogSink`] downwards together with the current
358    /// `now` timestamp, to not have to pass the latter separately as an additional argument just
359    /// for qlog support.
360    pub(super) fn with_time(&self, now: Instant) -> QlogSinkWithTime<'_> {
361        #[cfg(feature = "qlog")]
362        let s = QlogSinkWithTime { sink: self, now };
363        #[cfg(not(feature = "qlog"))]
364        let s = QlogSinkWithTime {
365            _phantom: PhantomData,
366        };
367        s
368    }
369}
370
371/// A [`QlogSink`] with a `now` timestamp.
372#[derive(Clone)]
373pub(super) struct QlogSinkWithTime<'a> {
374    #[cfg(feature = "qlog")]
375    sink: &'a QlogSink,
376    #[cfg(feature = "qlog")]
377    now: Instant,
378    #[cfg(not(feature = "qlog"))]
379    _phantom: PhantomData<&'a ()>,
380}
381
382impl<'a> QlogSinkWithTime<'a> {
383    pub(super) fn emit_timer_stop(&self, timer: Timer) {
384        #[cfg(feature = "qlog")]
385        self.sink.emit_timer(timer, TimerOp::Cancelled, self.now)
386    }
387
388    pub(super) fn emit_timer_set(&self, timer: Timer, expire_at: Instant) {
389        #[cfg(feature = "qlog")]
390        self.sink
391            .emit_timer(timer, TimerOp::Set(expire_at), self.now)
392    }
393
394    pub(super) fn emit_timer_expire(&self, timer: Timer) {
395        #[cfg(feature = "qlog")]
396        self.sink.emit_timer(timer, TimerOp::Expire, self.now)
397    }
398}
399
400#[cfg(feature = "qlog")]
401enum TimerOp {
402    Set(Instant),
403    Expire,
404    Cancelled,
405}
406
407/// Info about a sent packet. Zero-sized struct if `qlog` feature is not enabled.
408#[derive(Default)]
409pub(crate) struct QlogSentPacket {
410    #[cfg(feature = "qlog")]
411    inner: PacketSent,
412}
413
414impl QlogSentPacket {
415    /// Sets data from the packet header.
416    pub(crate) fn header(
417        &mut self,
418        header: &Header,
419        pn: Option<u64>,
420        space: SpaceId,
421        is_0rtt: bool,
422        path_id: PathId,
423    ) {
424        #[cfg(feature = "qlog")]
425        {
426            self.inner.header.scid = header.src_cid().map(stringify_cid);
427            self.inner.header.dcid = Some(stringify_cid(header.dst_cid()));
428            self.inner.header.packet_number = pn;
429            self.inner.header.packet_type = packet_type(space, is_0rtt);
430            self.inner.header.path_id = Some(path_id.as_u32() as u64);
431        }
432    }
433
434    /// Adds a frame by pushing a [`Frame`].
435    pub(crate) fn frame(&mut self, frame: &Frame) {
436        #[cfg(feature = "qlog")]
437        self.frame_raw(frame.to_qlog())
438    }
439
440    /// Adds a PADDING frame.
441    ///
442    /// This is a no-op if the `qlog` feature is not enabled.
443    pub(crate) fn frame_padding(&mut self, count: usize) {
444        #[cfg(feature = "qlog")]
445        self.frame_raw(QuicFrame::Padding {
446            raw: Some(RawInfo {
447                length: Some(count as u64),
448                payload_length: Some(count as u64),
449                data: None,
450            }),
451        });
452    }
453
454    /// Adds an ACK frame.
455    ///
456    /// This is a no-op if the `qlog` feature is not enabled.
457    pub(crate) fn frame_ack(
458        &mut self,
459        delay: u64,
460        ranges: &ArrayRangeSet,
461        ecn: Option<&EcnCounts>,
462    ) {
463        #[cfg(feature = "qlog")]
464        self.frame_raw(QuicFrame::Ack {
465            ack_delay: Some(delay as f32),
466            acked_ranges: Some(AckedRanges::Double(
467                ranges
468                    .iter()
469                    .map(|range| (range.start, range.end))
470                    .collect(),
471            )),
472            ect1: ecn.map(|e| e.ect1),
473            ect0: ecn.map(|e| e.ect0),
474            ce: ecn.map(|e| e.ce),
475            raw: None,
476        });
477    }
478
479    /// Adds a PATH_ACK frame.
480    ///
481    /// This is a no-op if the `qlog` feature is not enabled.
482    pub(crate) fn frame_path_ack(
483        &mut self,
484        path_id: PathId,
485        delay: u64,
486        ranges: &ArrayRangeSet,
487        ecn: Option<&EcnCounts>,
488    ) {
489        #[cfg(feature = "qlog")]
490        self.frame_raw(QuicFrame::PathAck {
491            path_id: path_id.as_u32() as u64,
492            ack_delay: Some(delay as f32),
493            acked_ranges: Some(AckedRanges::Double(
494                ranges
495                    .iter()
496                    .map(|range| (range.start, range.end))
497                    .collect(),
498            )),
499            ect1: ecn.map(|e| e.ect1),
500            ect0: ecn.map(|e| e.ect0),
501            ce: ecn.map(|e| e.ce),
502            raw: None,
503        });
504    }
505
506    /// Adds a DATAGRAM frame.
507    ///
508    /// This is a no-op if the `qlog` feature is not enabled.
509    pub(crate) fn frame_datagram(&mut self, len: u64) {
510        #[cfg(feature = "qlog")]
511        self.frame_raw(QuicFrame::Datagram {
512            raw: Some(RawInfo {
513                length: Some(len),
514                ..Default::default()
515            }),
516        });
517    }
518
519    /// Adds a STREAM frame.
520    ///
521    /// This is a no-op if the `qlog` feature is not enabled.
522    pub(crate) fn frame_stream(&mut self, meta: &StreamMeta) {
523        #[cfg(feature = "qlog")]
524        self.frame_raw(QuicFrame::Stream {
525            stream_id: meta.id.into(),
526            offset: Some(meta.offsets.start),
527            fin: Some(meta.fin),
528            raw: Some(RawInfo {
529                length: Some(meta.offsets.end - meta.offsets.start),
530                ..Default::default()
531            }),
532        });
533    }
534
535    /// Adds a frame by pushing a [`QuicFrame`].
536    ///
537    /// This function is only available if the `qlog` feature is enabled, because constructing a [`QuicFrame`] may involve
538    /// calculations which shouldn't be performed if the `qlog` feature is disabled.
539    #[cfg(feature = "qlog")]
540    fn frame_raw(&mut self, frame: QuicFrame) {
541        self.inner.frames.get_or_insert_default().push(frame);
542    }
543
544    /// Finalizes the packet by setting the final packet length (after encryption).
545    pub(super) fn finalize(&mut self, len: usize) {
546        #[cfg(feature = "qlog")]
547        {
548            self.inner.header.length = Some(len as u16);
549        }
550    }
551}
552
553/// Info about a received packet. Zero-sized struct if `qlog` feature is not enabled.
554pub(crate) struct QlogRecvPacket {
555    #[cfg(feature = "qlog")]
556    inner: PacketReceived,
557    #[cfg(feature = "qlog")]
558    padding: usize,
559}
560
561impl QlogRecvPacket {
562    /// Creates a new [`QlogRecvPacket`]. Noop if `qlog` feature is not enabled.
563    ///
564    /// `len` is the packet's full length (before decryption).
565    pub(crate) fn new(len: usize) -> Self {
566        #[cfg(not(feature = "qlog"))]
567        let this = Self {};
568
569        #[cfg(feature = "qlog")]
570        let this = {
571            let mut this = Self {
572                inner: Default::default(),
573                padding: 0,
574            };
575            this.inner.header.length = Some(len as u16);
576            this
577        };
578
579        this
580    }
581
582    /// Adds info from the packet header.
583    pub(crate) fn header(&mut self, header: &Header, pn: Option<u64>, path_id: PathId) {
584        #[cfg(feature = "qlog")]
585        {
586            let is_0rtt = !header.is_1rtt();
587            self.inner.header.scid = header.src_cid().map(stringify_cid);
588            self.inner.header.dcid = Some(stringify_cid(header.dst_cid()));
589            self.inner.header.packet_number = pn;
590            self.inner.header.packet_type = packet_type(header.space(), is_0rtt);
591            self.inner.header.path_id = Some(path_id.as_u32() as u64);
592        }
593    }
594
595    /// Adds a frame.
596    pub(crate) fn frame(&mut self, frame: &Frame) {
597        #[cfg(feature = "qlog")]
598        {
599            if matches!(frame, crate::Frame::Padding) {
600                self.padding += 1;
601            } else {
602                self.emit_padding();
603                self.inner
604                    .frames
605                    .get_or_insert_default()
606                    .push(frame.to_qlog())
607            }
608        }
609    }
610
611    #[cfg(feature = "qlog")]
612    fn emit_padding(&mut self) {
613        if self.padding > 0 {
614            self.inner
615                .frames
616                .get_or_insert_default()
617                .push(QuicFrame::Padding {
618                    raw: Some(RawInfo {
619                        length: Some(self.padding as u64),
620                        payload_length: Some(self.padding as u64),
621                        data: None,
622                    }),
623                });
624            self.padding = 0;
625        }
626    }
627}
628
629#[cfg(feature = "qlog")]
630impl Frame {
631    /// Converts a [`crate::Frame`] into a [`QuicFrame`].
632    pub(crate) fn to_qlog(&self) -> QuicFrame {
633        match self {
634            Self::Padding => QuicFrame::Padding {
635                raw: Some(RawInfo {
636                    length: None,
637                    payload_length: Some(1),
638                    data: None,
639                }),
640            },
641            Self::Ping => QuicFrame::Ping { raw: None },
642            Self::Ack(f) => QuicFrame::Ack {
643                ack_delay: Some(f.delay as f32),
644                acked_ranges: Some(AckedRanges::Double(
645                    f.iter()
646                        .map(|range| (*range.start(), *range.end()))
647                        .collect(),
648                )),
649                ect1: f.ecn.as_ref().map(|e| e.ect1),
650                ect0: f.ecn.as_ref().map(|e| e.ect0),
651                ce: f.ecn.as_ref().map(|e| e.ce),
652                raw: None,
653            },
654            Self::ResetStream(f) => QuicFrame::ResetStream {
655                stream_id: f.id.into(),
656                error_code: Some(f.error_code.into_inner()),
657                final_size: f.final_offset.into(),
658                error: ApplicationError::Unknown,
659                raw: None,
660            },
661            Self::StopSending(f) => QuicFrame::StopSending {
662                stream_id: f.id.into(),
663                error_code: Some(f.error_code.into_inner()),
664                error: ApplicationError::Unknown,
665                raw: None,
666            },
667            Self::Crypto(f) => QuicFrame::Crypto {
668                offset: f.offset,
669                raw: Some(RawInfo {
670                    length: Some(f.data.len() as u64),
671                    ..Default::default()
672                }),
673            },
674            Self::NewToken(f) => QuicFrame::NewToken {
675                token: qlog::Token {
676                    ty: Some(TokenType::Retry),
677                    raw: Some(RawInfo {
678                        data: HexSlice::maybe_string(Some(&f.token)),
679                        length: Some(f.token.len() as u64),
680                        payload_length: None,
681                    }),
682                    details: None,
683                },
684                raw: None,
685            },
686            Self::Stream(s) => QuicFrame::Stream {
687                stream_id: s.id.into(),
688                offset: Some(s.offset),
689                fin: Some(s.fin),
690                raw: Some(RawInfo {
691                    length: Some(s.data.len() as u64),
692                    ..Default::default()
693                }),
694            },
695            Self::MaxData(v) => QuicFrame::MaxData {
696                maximum: (*v).into(),
697                raw: None,
698            },
699            Self::MaxStreamData { id, offset } => QuicFrame::MaxStreamData {
700                stream_id: (*id).into(),
701                maximum: *offset,
702                raw: None,
703            },
704            Self::MaxStreams { dir, count } => QuicFrame::MaxStreams {
705                maximum: *count,
706                stream_type: (*dir).into(),
707                raw: None,
708            },
709            Self::DataBlocked { offset } => QuicFrame::DataBlocked {
710                limit: *offset,
711                raw: None,
712            },
713            Self::StreamDataBlocked { id, offset } => QuicFrame::StreamDataBlocked {
714                stream_id: (*id).into(),
715                limit: *offset,
716                raw: None,
717            },
718            Self::StreamsBlocked { dir, limit } => QuicFrame::StreamsBlocked {
719                stream_type: (*dir).into(),
720                limit: *limit,
721                raw: None,
722            },
723            Self::NewConnectionId(f) => QuicFrame::NewConnectionId {
724                sequence_number: f.sequence,
725                retire_prior_to: f.retire_prior_to,
726                connection_id_length: Some(f.id.len() as u8),
727                connection_id: f.id.to_string(),
728                stateless_reset_token: Some(f.reset_token.to_string()),
729                raw: None,
730            },
731            Self::RetireConnectionId(f) => QuicFrame::RetireConnectionId {
732                sequence_number: f.sequence,
733                raw: None,
734            },
735            Self::PathChallenge(token) => QuicFrame::PathChallenge {
736                data: Some(token.to_string()),
737                raw: None,
738            },
739            Self::PathResponse(token) => QuicFrame::PathResponse {
740                data: Some(token.to_string()),
741                raw: None,
742            },
743            Self::Close(close) => match close {
744                Close::Connection(f) => {
745                    let (error, error_code) = transport_error(f.error_code);
746                    let error = error.map(|transport_error| {
747                        ConnectionClosedFrameError::TransportError(transport_error)
748                    });
749                    QuicFrame::ConnectionClose {
750                        error_space: Some(ErrorSpace::TransportError),
751                        error,
752                        error_code,
753                        reason: String::from_utf8(f.reason.to_vec()).ok(),
754                        reason_bytes: None,
755                        trigger_frame_type: None,
756                    }
757                }
758                Close::Application(f) => QuicFrame::ConnectionClose {
759                    error_space: Some(ErrorSpace::ApplicationError),
760                    error: None,
761                    error_code: Some(f.error_code.into_inner()),
762                    reason: String::from_utf8(f.reason.to_vec()).ok(),
763                    reason_bytes: None,
764                    trigger_frame_type: None,
765                },
766            },
767            Self::Datagram(d) => QuicFrame::Datagram {
768                raw: Some(RawInfo {
769                    length: Some(d.data.len() as u64),
770                    ..Default::default()
771                }),
772            },
773            Self::HandshakeDone => QuicFrame::HandshakeDone { raw: None },
774            Self::PathAck(ack) => QuicFrame::PathAck {
775                path_id: ack.path_id.as_u32().into(),
776                ack_delay: Some(ack.delay as f32),
777                ect1: ack.ecn.as_ref().map(|e| e.ect1),
778                ect0: ack.ecn.as_ref().map(|e| e.ect0),
779                ce: ack.ecn.as_ref().map(|e| e.ce),
780                raw: None,
781                acked_ranges: Some(AckedRanges::Double(
782                    ack.into_iter()
783                        .map(|range| (*range.start(), *range.end()))
784                        .collect(),
785                )),
786            },
787            Self::PathAbandon(frame) => QuicFrame::PathAbandon {
788                path_id: frame.path_id.as_u32().into(),
789                error_code: frame.error_code.into(),
790                raw: None,
791            },
792            Self::PathStatusAvailable(frame) => QuicFrame::PathStatusAvailable {
793                path_id: frame.path_id.as_u32().into(),
794                path_status_sequence_number: frame.status_seq_no.into(),
795                raw: None,
796            },
797            Self::PathStatusBackup(frame) => QuicFrame::PathStatusBackup {
798                path_id: frame.path_id.as_u32().into(),
799                path_status_sequence_number: frame.status_seq_no.into(),
800                raw: None,
801            },
802            Self::PathsBlocked(frame) => QuicFrame::PathsBlocked {
803                maximum_path_id: frame.0.as_u32().into(),
804                raw: None,
805            },
806            Self::PathCidsBlocked(frame) => QuicFrame::PathCidsBlocked {
807                path_id: frame.path_id.as_u32().into(),
808                next_sequence_number: frame.next_seq.into(),
809                raw: None,
810            },
811            Self::MaxPathId(id) => QuicFrame::MaxPathId {
812                maximum_path_id: id.0.as_u32().into(),
813                raw: None,
814            },
815            Self::AckFrequency(f) => QuicFrame::AckFrequency {
816                sequence_number: f.sequence.into_inner(),
817                ack_eliciting_threshold: f.ack_eliciting_threshold.into_inner(),
818                requested_max_ack_delay: f.request_max_ack_delay.into_inner(),
819                reordering_threshold: f.reordering_threshold.into_inner(),
820                raw: None,
821            },
822            Self::ImmediateAck => QuicFrame::ImmediateAck { raw: None },
823            Self::ObservedAddr(f) => QuicFrame::ObservedAddress {
824                sequence_number: f.seq_no.into_inner(),
825                ip_v4: match f.ip {
826                    IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()),
827                    IpAddr::V6(ipv6_addr) => None,
828                },
829                ip_v6: match f.ip {
830                    IpAddr::V4(ipv4_addr) => None,
831                    IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()),
832                },
833                port: f.port,
834                raw: None,
835            },
836            Self::AddAddress(f) => QuicFrame::AddAddress {
837                sequence_number: f.seq_no.into_inner(),
838                ip_v4: match f.ip {
839                    IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()),
840                    IpAddr::V6(ipv6_addr) => None,
841                },
842                ip_v6: match f.ip {
843                    IpAddr::V4(ipv4_addr) => None,
844                    IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()),
845                },
846                port: f.port,
847            },
848            Self::ReachOut(f) => QuicFrame::ReachOut {
849                round: f.round.into_inner(),
850                ip_v4: match f.ip {
851                    IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()),
852                    IpAddr::V6(ipv6_addr) => None,
853                },
854                ip_v6: match f.ip {
855                    IpAddr::V4(ipv4_addr) => None,
856                    IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()),
857                },
858                port: f.port,
859            },
860            Self::RemoveAddress(f) => QuicFrame::RemoveAddress {
861                sequence_number: f.seq_no.into_inner(),
862            },
863        }
864    }
865}
866
867#[cfg(feature = "qlog")]
868impl From<crate::Dir> for StreamType {
869    fn from(value: crate::Dir) -> Self {
870        match value {
871            crate::Dir::Bi => Self::Bidirectional,
872            crate::Dir::Uni => Self::Unidirectional,
873        }
874    }
875}
876
877#[cfg(feature = "qlog")]
878fn packet_type(space: SpaceId, is_0rtt: bool) -> PacketType {
879    match space {
880        SpaceId::Initial => PacketType::Initial,
881        SpaceId::Handshake => PacketType::Handshake,
882        SpaceId::Data if is_0rtt => PacketType::ZeroRtt,
883        SpaceId::Data => PacketType::OneRtt,
884    }
885}
886
887#[cfg(feature = "qlog")]
888fn stringify_cid(cid: ConnectionId) -> String {
889    format!("{cid}")
890}
891
892#[cfg(feature = "qlog")]
893fn tuple_endpoint_info(
894    ip: Option<IpAddr>,
895    port: Option<u16>,
896    cid: Option<ConnectionId>,
897) -> TupleEndpointInfo {
898    let (ip_v4, port_v4, ip_v6, port_v6) = match ip {
899        Some(addr) => match addr {
900            IpAddr::V4(ipv4_addr) => (Some(ipv4_addr.to_string()), port, None, None),
901            IpAddr::V6(ipv6_addr) => (None, None, Some(ipv6_addr.to_string()), port),
902        },
903        None => (None, None, None, None),
904    };
905    TupleEndpointInfo {
906        ip_v4,
907        port_v4,
908        ip_v6,
909        port_v6,
910        connection_ids: cid.map(|cid| vec![cid.to_string()]),
911    }
912}
913
914#[cfg(feature = "qlog")]
915fn transport_error(code: TransportErrorCode) -> (Option<quic::TransportError>, Option<u64>) {
916    let transport_error = match code {
917        TransportErrorCode::NO_ERROR => Some(quic::TransportError::NoError),
918        TransportErrorCode::INTERNAL_ERROR => Some(quic::TransportError::InternalError),
919        TransportErrorCode::CONNECTION_REFUSED => Some(quic::TransportError::ConnectionRefused),
920        TransportErrorCode::FLOW_CONTROL_ERROR => Some(quic::TransportError::FlowControlError),
921        TransportErrorCode::STREAM_LIMIT_ERROR => Some(quic::TransportError::StreamLimitError),
922        TransportErrorCode::STREAM_STATE_ERROR => Some(quic::TransportError::StreamStateError),
923        TransportErrorCode::FINAL_SIZE_ERROR => Some(quic::TransportError::FinalSizeError),
924        TransportErrorCode::FRAME_ENCODING_ERROR => Some(quic::TransportError::FrameEncodingError),
925        TransportErrorCode::TRANSPORT_PARAMETER_ERROR => {
926            Some(quic::TransportError::TransportParameterError)
927        }
928        TransportErrorCode::CONNECTION_ID_LIMIT_ERROR => {
929            Some(quic::TransportError::ConnectionIdLimitError)
930        }
931        TransportErrorCode::PROTOCOL_VIOLATION => Some(quic::TransportError::ProtocolViolation),
932        TransportErrorCode::INVALID_TOKEN => Some(quic::TransportError::InvalidToken),
933        TransportErrorCode::APPLICATION_ERROR => Some(quic::TransportError::ApplicationError),
934        TransportErrorCode::CRYPTO_BUFFER_EXCEEDED => {
935            Some(quic::TransportError::CryptoBufferExceeded)
936        }
937        TransportErrorCode::KEY_UPDATE_ERROR => Some(quic::TransportError::KeyUpdateError),
938        TransportErrorCode::AEAD_LIMIT_REACHED => Some(quic::TransportError::AeadLimitReached),
939        TransportErrorCode::NO_VIABLE_PATH => Some(quic::TransportError::NoViablePath),
940        // multipath
941        TransportErrorCode::APPLICATION_ABANDON_PATH => {
942            Some(quic::TransportError::ApplicationAbandonPath)
943        }
944        TransportErrorCode::PATH_RESOURCE_LIMIT_REACHED => {
945            Some(quic::TransportError::PathResourceLimitReached)
946        }
947        TransportErrorCode::PATH_UNSTABLE_OR_POOR => Some(quic::TransportError::PathUnstableOrPoor),
948        TransportErrorCode::NO_CID_AVAILABLE_FOR_PATH => {
949            Some(quic::TransportError::NoCidsAvailableForPath)
950        }
951        _ => None,
952    };
953    let code = match transport_error {
954        None => Some(code.into()),
955        Some(_) => None,
956    };
957    (transport_error, code)
958}
959
960#[cfg(feature = "qlog")]
961fn fmt_tuple_id(path_id: u64) -> String {
962    format!("p{path_id}")
963}
964
965#[cfg(feature = "qlog")]
966impl TransportParameters {
967    fn to_qlog(self, initiator: TransportInitiator) -> ParametersSet {
968        ParametersSet {
969            initiator: Some(initiator),
970            resumption_allowed: None,
971            early_data_enabled: None,
972            tls_cipher: None,
973            original_destination_connection_id: self
974                .original_dst_cid
975                .as_ref()
976                .map(ToString::to_string),
977            initial_source_connection_id: self.initial_src_cid.as_ref().map(ToString::to_string),
978            retry_source_connection_id: self.retry_src_cid.as_ref().map(ToString::to_string),
979            stateless_reset_token: self.stateless_reset_token.as_ref().map(ToString::to_string),
980            disable_active_migration: Some(self.disable_active_migration),
981            max_idle_timeout: Some(self.max_idle_timeout.into()),
982            max_udp_payload_size: Some(self.max_udp_payload_size.into()),
983            ack_delay_exponent: Some(self.ack_delay_exponent.into()),
984            max_ack_delay: Some(self.max_ack_delay.into()),
985            active_connection_id_limit: Some(self.active_connection_id_limit.into()),
986            initial_max_data: Some(self.initial_max_data.into()),
987            initial_max_stream_data_bidi_local: Some(
988                self.initial_max_stream_data_bidi_local.into(),
989            ),
990            initial_max_stream_data_bidi_remote: Some(
991                self.initial_max_stream_data_bidi_remote.into(),
992            ),
993            initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()),
994            initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()),
995            initial_max_streams_uni: Some(self.initial_max_streams_uni.into()),
996            preferred_address: self.preferred_address.as_ref().map(Into::into),
997            min_ack_delay: self.min_ack_delay.map(Into::into),
998            address_discovery: self.address_discovery_role.to_qlog(),
999            initial_max_path_id: self.initial_max_path_id.map(|p| p.as_u32() as u64),
1000            max_remote_nat_traversal_addresses: self
1001                .max_remote_nat_traversal_addresses
1002                .map(|v| u64::from(v.get())),
1003            max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into),
1004            grease_quic_bit: Some(self.grease_quic_bit),
1005            unknown_parameters: Default::default(),
1006        }
1007    }
1008
1009    fn to_qlog_restored(self) -> ParametersRestored {
1010        ParametersRestored {
1011            disable_active_migration: Some(self.disable_active_migration),
1012            max_idle_timeout: Some(self.max_idle_timeout.into()),
1013            max_udp_payload_size: Some(self.max_udp_payload_size.into()),
1014            active_connection_id_limit: Some(self.active_connection_id_limit.into()),
1015            initial_max_data: Some(self.initial_max_data.into()),
1016            initial_max_stream_data_bidi_local: Some(
1017                self.initial_max_stream_data_bidi_local.into(),
1018            ),
1019            initial_max_stream_data_bidi_remote: Some(
1020                self.initial_max_stream_data_bidi_remote.into(),
1021            ),
1022            initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()),
1023            initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()),
1024            initial_max_streams_uni: Some(self.initial_max_streams_uni.into()),
1025            max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into),
1026            grease_quic_bit: Some(self.grease_quic_bit),
1027        }
1028    }
1029}
1030
1031#[cfg(feature = "qlog")]
1032impl From<&crate::transport_parameters::PreferredAddress> for PreferredAddress {
1033    fn from(value: &crate::transport_parameters::PreferredAddress) -> Self {
1034        let port_v4 = value.address_v4.map(|addr| addr.port()).unwrap_or_default();
1035        let port_v6 = value.address_v6.map(|addr| addr.port()).unwrap_or_default();
1036        let ip_v4 = value
1037            .address_v4
1038            .map(|addr| addr.ip().to_string())
1039            .unwrap_or_default();
1040        let ip_v6 = value
1041            .address_v6
1042            .map(|addr| addr.ip().to_string())
1043            .unwrap_or_default();
1044        let connection_id = value.connection_id.to_string();
1045        let stateless_reset_token = value.stateless_reset_token.to_string();
1046
1047        Self {
1048            ip_v4,
1049            ip_v6,
1050            port_v4,
1051            port_v6,
1052            connection_id,
1053            stateless_reset_token,
1054        }
1055    }
1056}
1057
1058#[cfg(feature = "qlog")]
1059impl crate::address_discovery::Role {
1060    fn to_qlog(self) -> Option<AddressDiscoveryRole> {
1061        match self {
1062            Self::SendOnly => Some(AddressDiscoveryRole::SendOnly),
1063            Self::ReceiveOnly => Some(AddressDiscoveryRole::ReceiveOnly),
1064            Self::Both => Some(AddressDiscoveryRole::Both),
1065            Self::Disabled => None,
1066        }
1067    }
1068}