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