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