iroh_quinn_proto/connection/
qlog.rs

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