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.
371#[derive(Clone)]
372pub(super) struct QlogSinkWithTime<'a> {
373    #[cfg(feature = "qlog")]
374    sink: &'a QlogSink,
375    #[cfg(feature = "qlog")]
376    now: Instant,
377    #[cfg(not(feature = "qlog"))]
378    _phantom: PhantomData<&'a ()>,
379}
380
381impl<'a> QlogSinkWithTime<'a> {
382    pub(super) fn emit_timer_stop(&self, timer: Timer) {
383        #[cfg(feature = "qlog")]
384        self.sink.emit_timer(timer, TimerOp::Cancelled, self.now)
385    }
386
387    pub(super) fn emit_timer_set(&self, timer: Timer, expire_at: Instant) {
388        #[cfg(feature = "qlog")]
389        self.sink
390            .emit_timer(timer, TimerOp::Set(expire_at), self.now)
391    }
392
393    pub(super) fn emit_timer_expire(&self, timer: Timer) {
394        #[cfg(feature = "qlog")]
395        self.sink.emit_timer(timer, TimerOp::Expire, self.now)
396    }
397}
398
399#[cfg(feature = "qlog")]
400enum TimerOp {
401    Set(Instant),
402    Expire,
403    Cancelled,
404}
405
406/// Info about a sent packet. Zero-sized struct if `qlog` feature is not enabled.
407#[derive(Default)]
408pub(crate) struct QlogSentPacket {
409    #[cfg(feature = "qlog")]
410    inner: PacketSent,
411}
412
413impl QlogSentPacket {
414    /// Sets data from the packet header.
415    pub(crate) fn header(
416        &mut self,
417        header: &Header,
418        pn: Option<u64>,
419        space: SpaceId,
420        is_0rtt: bool,
421        path_id: PathId,
422    ) {
423        #[cfg(feature = "qlog")]
424        {
425            self.inner.header.scid = header.src_cid().map(stringify_cid);
426            self.inner.header.dcid = Some(stringify_cid(header.dst_cid()));
427            self.inner.header.packet_number = pn;
428            self.inner.header.packet_type = packet_type(space, is_0rtt);
429            self.inner.header.path_id = Some(path_id.as_u32() as u64);
430        }
431    }
432
433    /// Adds a frame by pushing a [`Frame`].
434    pub(crate) fn frame(&mut self, frame: &Frame) {
435        #[cfg(feature = "qlog")]
436        self.frame_raw(frame.to_qlog())
437    }
438
439    /// Adds a PADDING frame.
440    ///
441    /// This is a no-op if the `qlog` feature is not enabled.
442    pub(crate) fn frame_padding(&mut self, count: usize) {
443        #[cfg(feature = "qlog")]
444        self.frame_raw(QuicFrame::Padding {
445            raw: Some(RawInfo {
446                length: Some(count as u64),
447                payload_length: Some(count as u64),
448                data: None,
449            }),
450        });
451    }
452
453    /// Adds an ACK frame.
454    ///
455    /// This is a no-op if the `qlog` feature is not enabled.
456    pub(crate) fn frame_ack(
457        &mut self,
458        delay: u64,
459        ranges: &ArrayRangeSet,
460        ecn: Option<&EcnCounts>,
461    ) {
462        #[cfg(feature = "qlog")]
463        self.frame_raw(QuicFrame::Ack {
464            ack_delay: Some(delay as f32),
465            acked_ranges: Some(AckedRanges::Double(
466                ranges
467                    .iter()
468                    .map(|range| (range.start, range.end))
469                    .collect(),
470            )),
471            ect1: ecn.map(|e| e.ect1),
472            ect0: ecn.map(|e| e.ect0),
473            ce: ecn.map(|e| e.ce),
474            raw: None,
475        });
476    }
477
478    /// Adds a PATH_ACK frame.
479    ///
480    /// This is a no-op if the `qlog` feature is not enabled.
481    pub(crate) fn frame_path_ack(
482        &mut self,
483        path_id: PathId,
484        delay: u64,
485        ranges: &ArrayRangeSet,
486        ecn: Option<&EcnCounts>,
487    ) {
488        #[cfg(feature = "qlog")]
489        self.frame_raw(QuicFrame::PathAck {
490            path_id: path_id.as_u32() as u64,
491            ack_delay: Some(delay as f32),
492            acked_ranges: Some(AckedRanges::Double(
493                ranges
494                    .iter()
495                    .map(|range| (range.start, range.end))
496                    .collect(),
497            )),
498            ect1: ecn.map(|e| e.ect1),
499            ect0: ecn.map(|e| e.ect0),
500            ce: ecn.map(|e| e.ce),
501            raw: None,
502        });
503    }
504
505    /// Adds a DATAGRAM frame.
506    ///
507    /// This is a no-op if the `qlog` feature is not enabled.
508    pub(crate) fn frame_datagram(&mut self, len: u64) {
509        #[cfg(feature = "qlog")]
510        self.frame_raw(QuicFrame::Datagram {
511            raw: Some(RawInfo {
512                length: Some(len),
513                ..Default::default()
514            }),
515        });
516    }
517
518    /// Adds a STREAM frame.
519    ///
520    /// This is a no-op if the `qlog` feature is not enabled.
521    pub(crate) fn frame_stream(&mut self, meta: &StreamMeta) {
522        #[cfg(feature = "qlog")]
523        self.frame_raw(QuicFrame::Stream {
524            stream_id: meta.id.into(),
525            offset: Some(meta.offsets.start),
526            fin: Some(meta.fin),
527            raw: Some(RawInfo {
528                length: Some(meta.offsets.end - meta.offsets.start),
529                ..Default::default()
530            }),
531        });
532    }
533
534    /// Adds a frame by pushing a [`QuicFrame`].
535    ///
536    /// This function is only available if the `qlog` feature is enabled, because constructing a [`QuicFrame`] may involve
537    /// calculations which shouldn't be performed if the `qlog` feature is disabled.
538    #[cfg(feature = "qlog")]
539    fn frame_raw(&mut self, frame: QuicFrame) {
540        self.inner.frames.get_or_insert_default().push(frame);
541    }
542
543    /// Finalizes the packet by setting the final packet length (after encryption).
544    pub(super) fn finalize(&mut self, len: usize) {
545        #[cfg(feature = "qlog")]
546        {
547            self.inner.header.length = Some(len as u16);
548        }
549    }
550}
551
552/// Info about a received packet. Zero-sized struct if `qlog` feature is not enabled.
553pub(crate) struct QlogRecvPacket {
554    #[cfg(feature = "qlog")]
555    inner: PacketReceived,
556    #[cfg(feature = "qlog")]
557    padding: usize,
558}
559
560impl QlogRecvPacket {
561    /// Creates a new [`QlogRecvPacket`]. Noop if `qlog` feature is not enabled.
562    ///
563    /// `len` is the packet's full length (before decryption).
564    pub(crate) fn new(len: usize) -> Self {
565        #[cfg(not(feature = "qlog"))]
566        let this = Self {};
567
568        #[cfg(feature = "qlog")]
569        let this = {
570            let mut this = Self {
571                inner: Default::default(),
572                padding: 0,
573            };
574            this.inner.header.length = Some(len as u16);
575            this
576        };
577
578        this
579    }
580
581    /// Adds info from the packet header.
582    pub(crate) fn header(&mut self, header: &Header, pn: Option<u64>, path_id: PathId) {
583        #[cfg(feature = "qlog")]
584        {
585            let is_0rtt = !header.is_1rtt();
586            self.inner.header.scid = header.src_cid().map(stringify_cid);
587            self.inner.header.dcid = Some(stringify_cid(header.dst_cid()));
588            self.inner.header.packet_number = pn;
589            self.inner.header.packet_type = packet_type(header.space(), is_0rtt);
590            self.inner.header.path_id = Some(path_id.as_u32() as u64);
591        }
592    }
593
594    /// Adds a frame.
595    pub(crate) fn frame(&mut self, frame: &Frame) {
596        #[cfg(feature = "qlog")]
597        {
598            if matches!(frame, crate::Frame::Padding) {
599                self.padding += 1;
600            } else {
601                self.emit_padding();
602                self.inner
603                    .frames
604                    .get_or_insert_default()
605                    .push(frame.to_qlog())
606            }
607        }
608    }
609
610    #[cfg(feature = "qlog")]
611    fn emit_padding(&mut self) {
612        if self.padding > 0 {
613            self.inner
614                .frames
615                .get_or_insert_default()
616                .push(QuicFrame::Padding {
617                    raw: Some(RawInfo {
618                        length: Some(self.padding as u64),
619                        payload_length: Some(self.padding as u64),
620                        data: None,
621                    }),
622                });
623            self.padding = 0;
624        }
625    }
626}
627
628#[cfg(feature = "qlog")]
629impl Frame {
630    /// Converts a [`crate::Frame`] into a [`QuicFrame`].
631    pub(crate) fn to_qlog(&self) -> QuicFrame {
632        match self {
633            Self::Padding => QuicFrame::Padding {
634                raw: Some(RawInfo {
635                    length: None,
636                    payload_length: Some(1),
637                    data: None,
638                }),
639            },
640            Self::Ping => QuicFrame::Ping { raw: None },
641            Self::Ack(f) => QuicFrame::Ack {
642                ack_delay: Some(f.delay as f32),
643                acked_ranges: Some(AckedRanges::Double(
644                    f.iter()
645                        .map(|range| (*range.start(), *range.end()))
646                        .collect(),
647                )),
648                ect1: f.ecn.as_ref().map(|e| e.ect1),
649                ect0: f.ecn.as_ref().map(|e| e.ect0),
650                ce: f.ecn.as_ref().map(|e| e.ce),
651                raw: None,
652            },
653            Self::ResetStream(f) => QuicFrame::ResetStream {
654                stream_id: f.id.into(),
655                error_code: Some(f.error_code.into_inner()),
656                final_size: f.final_offset.into(),
657                error: ApplicationError::Unknown,
658                raw: None,
659            },
660            Self::StopSending(f) => QuicFrame::StopSending {
661                stream_id: f.id.into(),
662                error_code: Some(f.error_code.into_inner()),
663                error: ApplicationError::Unknown,
664                raw: None,
665            },
666            Self::Crypto(f) => QuicFrame::Crypto {
667                offset: f.offset,
668                raw: Some(RawInfo {
669                    length: Some(f.data.len() as u64),
670                    ..Default::default()
671                }),
672            },
673            Self::NewToken(f) => QuicFrame::NewToken {
674                token: qlog::Token {
675                    ty: Some(TokenType::Retry),
676                    raw: Some(RawInfo {
677                        data: HexSlice::maybe_string(Some(&f.token)),
678                        length: Some(f.token.len() as u64),
679                        payload_length: None,
680                    }),
681                    details: None,
682                },
683                raw: None,
684            },
685            Self::Stream(s) => QuicFrame::Stream {
686                stream_id: s.id.into(),
687                offset: Some(s.offset),
688                fin: Some(s.fin),
689                raw: Some(RawInfo {
690                    length: Some(s.data.len() as u64),
691                    ..Default::default()
692                }),
693            },
694            Self::MaxData(v) => QuicFrame::MaxData {
695                maximum: (*v).into(),
696                raw: None,
697            },
698            Self::MaxStreamData { id, offset } => QuicFrame::MaxStreamData {
699                stream_id: (*id).into(),
700                maximum: *offset,
701                raw: None,
702            },
703            Self::MaxStreams { dir, count } => QuicFrame::MaxStreams {
704                maximum: *count,
705                stream_type: (*dir).into(),
706                raw: None,
707            },
708            Self::DataBlocked { offset } => QuicFrame::DataBlocked {
709                limit: *offset,
710                raw: None,
711            },
712            Self::StreamDataBlocked { id, offset } => QuicFrame::StreamDataBlocked {
713                stream_id: (*id).into(),
714                limit: *offset,
715                raw: None,
716            },
717            Self::StreamsBlocked { dir, limit } => QuicFrame::StreamsBlocked {
718                stream_type: (*dir).into(),
719                limit: *limit,
720                raw: None,
721            },
722            Self::NewConnectionId(f) => QuicFrame::NewConnectionId {
723                sequence_number: f.sequence,
724                retire_prior_to: f.retire_prior_to,
725                connection_id_length: Some(f.id.len() as u8),
726                connection_id: f.id.to_string(),
727                stateless_reset_token: Some(f.reset_token.to_string()),
728                raw: None,
729            },
730            Self::RetireConnectionId(f) => QuicFrame::RetireConnectionId {
731                sequence_number: f.sequence,
732                raw: None,
733            },
734            Self::PathChallenge(token) => QuicFrame::PathChallenge {
735                data: Some(token.to_string()),
736                raw: None,
737            },
738            Self::PathResponse(token) => QuicFrame::PathResponse {
739                data: Some(token.to_string()),
740                raw: None,
741            },
742            Self::Close(close) => match close {
743                Close::Connection(f) => {
744                    let (error, error_code) = transport_error(f.error_code);
745                    let error = error.map(|transport_error| {
746                        ConnectionClosedFrameError::TransportError(transport_error)
747                    });
748                    QuicFrame::ConnectionClose {
749                        error_space: Some(ErrorSpace::TransportError),
750                        error,
751                        error_code,
752                        reason: String::from_utf8(f.reason.to_vec()).ok(),
753                        reason_bytes: None,
754                        trigger_frame_type: None,
755                    }
756                }
757                Close::Application(f) => QuicFrame::ConnectionClose {
758                    error_space: Some(ErrorSpace::ApplicationError),
759                    error: None,
760                    error_code: Some(f.error_code.into_inner()),
761                    reason: String::from_utf8(f.reason.to_vec()).ok(),
762                    reason_bytes: None,
763                    trigger_frame_type: None,
764                },
765            },
766            Self::Datagram(d) => QuicFrame::Datagram {
767                raw: Some(RawInfo {
768                    length: Some(d.data.len() as u64),
769                    ..Default::default()
770                }),
771            },
772            Self::HandshakeDone => QuicFrame::HandshakeDone { raw: None },
773            Self::PathAck(ack) => QuicFrame::PathAck {
774                path_id: ack.path_id.as_u32().into(),
775                ack_delay: Some(ack.delay as f32),
776                ect1: ack.ecn.as_ref().map(|e| e.ect1),
777                ect0: ack.ecn.as_ref().map(|e| e.ect0),
778                ce: ack.ecn.as_ref().map(|e| e.ce),
779                raw: None,
780                acked_ranges: Some(AckedRanges::Double(
781                    ack.into_iter()
782                        .map(|range| (*range.start(), *range.end()))
783                        .collect(),
784                )),
785            },
786            Self::PathAbandon(frame) => QuicFrame::PathAbandon {
787                path_id: frame.path_id.as_u32().into(),
788                error_code: frame.error_code.into(),
789                raw: None,
790            },
791            Self::PathStatusAvailable(frame) => QuicFrame::PathStatusAvailable {
792                path_id: frame.path_id.as_u32().into(),
793                path_status_sequence_number: frame.status_seq_no.into(),
794                raw: None,
795            },
796            Self::PathStatusBackup(frame) => QuicFrame::PathStatusBackup {
797                path_id: frame.path_id.as_u32().into(),
798                path_status_sequence_number: frame.status_seq_no.into(),
799                raw: None,
800            },
801            Self::PathsBlocked(frame) => QuicFrame::PathsBlocked {
802                maximum_path_id: frame.0.as_u32().into(),
803                raw: None,
804            },
805            Self::PathCidsBlocked(frame) => QuicFrame::PathCidsBlocked {
806                path_id: frame.path_id.as_u32().into(),
807                next_sequence_number: frame.next_seq.into(),
808                raw: None,
809            },
810            Self::MaxPathId(id) => QuicFrame::MaxPathId {
811                maximum_path_id: id.0.as_u32().into(),
812                raw: None,
813            },
814            Self::AckFrequency(f) => QuicFrame::AckFrequency {
815                sequence_number: f.sequence.into_inner(),
816                ack_eliciting_threshold: f.ack_eliciting_threshold.into_inner(),
817                requested_max_ack_delay: f.request_max_ack_delay.into_inner(),
818                reordering_threshold: f.reordering_threshold.into_inner(),
819                raw: None,
820            },
821            Self::ImmediateAck => QuicFrame::ImmediateAck { raw: None },
822            Self::ObservedAddr(f) => QuicFrame::ObservedAddress {
823                sequence_number: f.seq_no.into_inner(),
824                ip_v4: match f.ip {
825                    IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()),
826                    IpAddr::V6(ipv6_addr) => None,
827                },
828                ip_v6: match f.ip {
829                    IpAddr::V4(ipv4_addr) => None,
830                    IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()),
831                },
832                port: f.port,
833                raw: None,
834            },
835            Self::AddAddress(f) => QuicFrame::AddAddress {
836                sequence_number: f.seq_no.into_inner(),
837                ip_v4: match f.ip {
838                    IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()),
839                    IpAddr::V6(ipv6_addr) => None,
840                },
841                ip_v6: match f.ip {
842                    IpAddr::V4(ipv4_addr) => None,
843                    IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()),
844                },
845                port: f.port,
846            },
847            Self::ReachOut(f) => QuicFrame::ReachOut {
848                round: f.round.into_inner(),
849                ip_v4: match f.ip {
850                    IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()),
851                    IpAddr::V6(ipv6_addr) => None,
852                },
853                ip_v6: match f.ip {
854                    IpAddr::V4(ipv4_addr) => None,
855                    IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()),
856                },
857                port: f.port,
858            },
859            Self::RemoveAddress(f) => QuicFrame::RemoveAddress {
860                sequence_number: f.seq_no.into_inner(),
861            },
862        }
863    }
864}
865
866#[cfg(feature = "qlog")]
867impl From<crate::Dir> for StreamType {
868    fn from(value: crate::Dir) -> Self {
869        match value {
870            crate::Dir::Bi => Self::Bidirectional,
871            crate::Dir::Uni => Self::Unidirectional,
872        }
873    }
874}
875
876#[cfg(feature = "qlog")]
877fn packet_type(space: SpaceId, is_0rtt: bool) -> PacketType {
878    match space {
879        SpaceId::Initial => PacketType::Initial,
880        SpaceId::Handshake => PacketType::Handshake,
881        SpaceId::Data if is_0rtt => PacketType::ZeroRtt,
882        SpaceId::Data => PacketType::OneRtt,
883    }
884}
885
886#[cfg(feature = "qlog")]
887fn stringify_cid(cid: ConnectionId) -> String {
888    format!("{cid}")
889}
890
891#[cfg(feature = "qlog")]
892fn tuple_endpoint_info(
893    ip: Option<IpAddr>,
894    port: Option<u16>,
895    cid: Option<ConnectionId>,
896) -> TupleEndpointInfo {
897    let (ip_v4, port_v4, ip_v6, port_v6) = match ip {
898        Some(addr) => match addr {
899            IpAddr::V4(ipv4_addr) => (Some(ipv4_addr.to_string()), port, None, None),
900            IpAddr::V6(ipv6_addr) => (None, None, Some(ipv6_addr.to_string()), port),
901        },
902        None => (None, None, None, None),
903    };
904    TupleEndpointInfo {
905        ip_v4,
906        port_v4,
907        ip_v6,
908        port_v6,
909        connection_ids: cid.map(|cid| vec![cid.to_string()]),
910    }
911}
912
913#[cfg(feature = "qlog")]
914fn transport_error(code: TransportErrorCode) -> (Option<quic::TransportError>, Option<u64>) {
915    let transport_error = match code {
916        TransportErrorCode::NO_ERROR => Some(quic::TransportError::NoError),
917        TransportErrorCode::INTERNAL_ERROR => Some(quic::TransportError::InternalError),
918        TransportErrorCode::CONNECTION_REFUSED => Some(quic::TransportError::ConnectionRefused),
919        TransportErrorCode::FLOW_CONTROL_ERROR => Some(quic::TransportError::FlowControlError),
920        TransportErrorCode::STREAM_LIMIT_ERROR => Some(quic::TransportError::StreamLimitError),
921        TransportErrorCode::STREAM_STATE_ERROR => Some(quic::TransportError::StreamStateError),
922        TransportErrorCode::FINAL_SIZE_ERROR => Some(quic::TransportError::FinalSizeError),
923        TransportErrorCode::FRAME_ENCODING_ERROR => Some(quic::TransportError::FrameEncodingError),
924        TransportErrorCode::TRANSPORT_PARAMETER_ERROR => {
925            Some(quic::TransportError::TransportParameterError)
926        }
927        TransportErrorCode::CONNECTION_ID_LIMIT_ERROR => {
928            Some(quic::TransportError::ConnectionIdLimitError)
929        }
930        TransportErrorCode::PROTOCOL_VIOLATION => Some(quic::TransportError::ProtocolViolation),
931        TransportErrorCode::INVALID_TOKEN => Some(quic::TransportError::InvalidToken),
932        TransportErrorCode::APPLICATION_ERROR => Some(quic::TransportError::ApplicationError),
933        TransportErrorCode::CRYPTO_BUFFER_EXCEEDED => {
934            Some(quic::TransportError::CryptoBufferExceeded)
935        }
936        TransportErrorCode::KEY_UPDATE_ERROR => Some(quic::TransportError::KeyUpdateError),
937        TransportErrorCode::AEAD_LIMIT_REACHED => Some(quic::TransportError::AeadLimitReached),
938        TransportErrorCode::NO_VIABLE_PATH => Some(quic::TransportError::NoViablePath),
939        // multipath
940        TransportErrorCode::APPLICATION_ABANDON_PATH => {
941            Some(quic::TransportError::ApplicationAbandonPath)
942        }
943        TransportErrorCode::PATH_RESOURCE_LIMIT_REACHED => {
944            Some(quic::TransportError::PathResourceLimitReached)
945        }
946        TransportErrorCode::PATH_UNSTABLE_OR_POOR => Some(quic::TransportError::PathUnstableOrPoor),
947        TransportErrorCode::NO_CID_AVAILABLE_FOR_PATH => {
948            Some(quic::TransportError::NoCidsAvailableForPath)
949        }
950        _ => None,
951    };
952    let code = match transport_error {
953        None => Some(code.into()),
954        Some(_) => None,
955    };
956    (transport_error, code)
957}
958
959#[cfg(feature = "qlog")]
960fn fmt_tuple_id(path_id: u64) -> String {
961    format!("p{path_id}")
962}
963
964#[cfg(feature = "qlog")]
965impl TransportParameters {
966    fn to_qlog(self, initiator: TransportInitiator) -> ParametersSet {
967        ParametersSet {
968            initiator: Some(initiator),
969            resumption_allowed: None,
970            early_data_enabled: None,
971            tls_cipher: None,
972            original_destination_connection_id: self
973                .original_dst_cid
974                .as_ref()
975                .map(ToString::to_string),
976            initial_source_connection_id: self.initial_src_cid.as_ref().map(ToString::to_string),
977            retry_source_connection_id: self.retry_src_cid.as_ref().map(ToString::to_string),
978            stateless_reset_token: self.stateless_reset_token.as_ref().map(ToString::to_string),
979            disable_active_migration: Some(self.disable_active_migration),
980            max_idle_timeout: Some(self.max_idle_timeout.into()),
981            max_udp_payload_size: Some(self.max_udp_payload_size.into()),
982            ack_delay_exponent: Some(self.ack_delay_exponent.into()),
983            max_ack_delay: Some(self.max_ack_delay.into()),
984            active_connection_id_limit: Some(self.active_connection_id_limit.into()),
985            initial_max_data: Some(self.initial_max_data.into()),
986            initial_max_stream_data_bidi_local: Some(
987                self.initial_max_stream_data_bidi_local.into(),
988            ),
989            initial_max_stream_data_bidi_remote: Some(
990                self.initial_max_stream_data_bidi_remote.into(),
991            ),
992            initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()),
993            initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()),
994            initial_max_streams_uni: Some(self.initial_max_streams_uni.into()),
995            preferred_address: self.preferred_address.as_ref().map(Into::into),
996            min_ack_delay: self.min_ack_delay.map(Into::into),
997            address_discovery: self.address_discovery_role.to_qlog(),
998            initial_max_path_id: self.initial_max_path_id.map(|p| p.as_u32() as u64),
999            max_remote_nat_traversal_addresses: self
1000                .max_remote_nat_traversal_addresses
1001                .map(|v| u64::from(v.get())),
1002            max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into),
1003            grease_quic_bit: Some(self.grease_quic_bit),
1004            unknown_parameters: Default::default(),
1005        }
1006    }
1007
1008    fn to_qlog_restored(self) -> ParametersRestored {
1009        ParametersRestored {
1010            disable_active_migration: Some(self.disable_active_migration),
1011            max_idle_timeout: Some(self.max_idle_timeout.into()),
1012            max_udp_payload_size: Some(self.max_udp_payload_size.into()),
1013            active_connection_id_limit: Some(self.active_connection_id_limit.into()),
1014            initial_max_data: Some(self.initial_max_data.into()),
1015            initial_max_stream_data_bidi_local: Some(
1016                self.initial_max_stream_data_bidi_local.into(),
1017            ),
1018            initial_max_stream_data_bidi_remote: Some(
1019                self.initial_max_stream_data_bidi_remote.into(),
1020            ),
1021            initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()),
1022            initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()),
1023            initial_max_streams_uni: Some(self.initial_max_streams_uni.into()),
1024            max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into),
1025            grease_quic_bit: Some(self.grease_quic_bit),
1026        }
1027    }
1028}
1029
1030#[cfg(feature = "qlog")]
1031impl From<&crate::transport_parameters::PreferredAddress> for PreferredAddress {
1032    fn from(value: &crate::transport_parameters::PreferredAddress) -> Self {
1033        let port_v4 = value.address_v4.map(|addr| addr.port()).unwrap_or_default();
1034        let port_v6 = value.address_v6.map(|addr| addr.port()).unwrap_or_default();
1035        let ip_v4 = value
1036            .address_v4
1037            .map(|addr| addr.ip().to_string())
1038            .unwrap_or_default();
1039        let ip_v6 = value
1040            .address_v6
1041            .map(|addr| addr.ip().to_string())
1042            .unwrap_or_default();
1043        let connection_id = value.connection_id.to_string();
1044        let stateless_reset_token = value.stateless_reset_token.to_string();
1045
1046        Self {
1047            ip_v4,
1048            ip_v6,
1049            port_v4,
1050            port_v6,
1051            connection_id,
1052            stateless_reset_token,
1053        }
1054    }
1055}
1056
1057#[cfg(feature = "qlog")]
1058impl crate::address_discovery::Role {
1059    fn to_qlog(self) -> Option<AddressDiscoveryRole> {
1060        match self {
1061            Self::SendOnly => Some(AddressDiscoveryRole::SendOnly),
1062            Self::ReceiveOnly => Some(AddressDiscoveryRole::ReceiveOnly),
1063            Self::Both => Some(AddressDiscoveryRole::Both),
1064            Self::Disabled => None,
1065        }
1066    }
1067}