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