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