Übersicht der Quellen

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  qlog.rs  Sprache: unbekannt

 
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Functions that handle capturing QLOG traces.

use std::{
    ops::{Deref as _, RangeInclusive},
    time::{Duration, Instant},
};

use neqo_common::{Decoder, Ecn, hex, qinfo, qlog::Qlog};
use qlog::events::{
    ApplicationErrorCode, ConnectionErrorCode, EventData, RawInfo,
    connectivity::{
        ConnectionClosed, ConnectionClosedTrigger, ConnectionStarted, ConnectionState,
        ConnectionStateUpdated, MtuUpdated, TransportOwner,
    },
    quic::{
        AckedRanges, CongestionStateUpdated, CongestionStateUpdatedTrigger, ErrorSpace,
        LossTimerEventType, LossTimerUpdated, MetricsUpdated, PacketDropped, PacketDroppedTrigger,
        PacketHeader, PacketLost, PacketLostTrigger, PacketNumberSpace as QlogPacketNumberSpace,
        PacketReceived, PacketSent, PacketsAcked, QuicFrame, RecoveryParametersSet, StreamType,
        TimerType, VersionInformation,
    },
};
use smallvec::SmallVec;

use crate::{
    CloseReason,
    cc::{CWND_INITIAL_PKTS, CongestionControl, Cubic, PERSISTENT_CONG_THRESH},
    connection::State,
    frame::{CloseError, Frame},
    packet::{self, metadata::Direction},
    path::PathRef,
    recovery::sent,
    rtt::{DEFAULT_INITIAL_RTT, GRANULARITY},
    stream_id::StreamType as NeqoStreamType,
    tparams::{
        TransportParameterId::{
            self, AckDelayExponent, ActiveConnectionIdLimit, DisableMigration, InitialMaxData,
            InitialMaxStreamDataBidiLocal, InitialMaxStreamDataBidiRemote, InitialMaxStreamDataUni,
            InitialMaxStreamsBidi, InitialMaxStreamsUni, MaxAckDelay, MaxUdpPayloadSize,
            OriginalDestinationConnectionId, StatelessResetToken,
        },
        TransportParametersHandler,
    },
    tracking::PacketNumberSpace,
    version::{self, Version},
};

pub fn connection_tparams_set(qlog: &mut Qlog, tph: &TransportParametersHandler, now: Instant) {
    qlog.add_event_at(
        || {
            let remote = tph.remote();
            #[expect(clippy::cast_possible_truncation, reason = "These are OK.")]
            let ev_data =
                EventData::TransportParametersSet(qlog::events::quic::TransportParametersSet {
                    owner: Some(TransportOwner::Remote),
                    original_destination_connection_id: remote
                        .get_bytes(OriginalDestinationConnectionId)
                        .map(hex),
                    stateless_reset_token: remote.get_bytes(StatelessResetToken).map(hex),
                    disable_active_migration: remote.get_empty(DisableMigration).then_some(true),
                    max_idle_timeout: Some(remote.get_integer(TransportParameterId::IdleTimeout)),
                    max_udp_payload_size: Some(remote.get_integer(MaxUdpPayloadSize) as u32),
                    ack_delay_exponent: Some(remote.get_integer(AckDelayExponent) as u16),
                    max_ack_delay: Some(remote.get_integer(MaxAckDelay) as u16),
                    active_connection_id_limit: Some(
                        remote.get_integer(ActiveConnectionIdLimit) as u32
                    ),
                    initial_max_data: Some(remote.get_integer(InitialMaxData)),
                    initial_max_stream_data_bidi_local: Some(
                        remote.get_integer(InitialMaxStreamDataBidiLocal),
                    ),
                    initial_max_stream_data_bidi_remote: Some(
                        remote.get_integer(InitialMaxStreamDataBidiRemote),
                    ),
                    initial_max_stream_data_uni: Some(remote.get_integer(InitialMaxStreamDataUni)),
                    initial_max_streams_bidi: Some(remote.get_integer(InitialMaxStreamsBidi)),
                    initial_max_streams_uni: Some(remote.get_integer(InitialMaxStreamsUni)),
                    preferred_address: remote.get_preferred_address().and_then(|(paddr, cid)| {
                        Some(qlog::events::quic::PreferredAddress {
                            ip_v4: paddr.ipv4()?.ip().to_string(),
                            ip_v6: paddr.ipv6()?.ip().to_string(),
                            port_v4: paddr.ipv4()?.port(),
                            port_v6: paddr.ipv6()?.port(),
                            connection_id: cid.connection_id().to_string(),
                            stateless_reset_token: hex(cid.reset_token()),
                        })
                    }),
                    ..Default::default()
                });

            Some(ev_data)
        },
        now,
    );
}

pub fn server_connection_started(qlog: &mut Qlog, path: &PathRef, now: Instant) {
    connection_started(qlog, path, now);
}

pub fn client_connection_started(qlog: &mut Qlog, path: &PathRef, now: Instant) {
    connection_started(qlog, path, now);
}

fn connection_started(qlog: &mut Qlog, path: &PathRef, now: Instant) {
    qlog.add_event_at(
        || {
            let p = path.deref().borrow();
            let ev_data = EventData::ConnectionStarted(ConnectionStarted {
                ip_version: if p.local_address().ip().is_ipv4() {
                    Some("ipv4".into())
                } else {
                    Some("ipv6".into())
                },
                src_ip: p.local_address().ip().to_string(),
                dst_ip: p.remote_address().ip().to_string(),
                protocol: Some("QUIC".into()),
                src_port: p.local_address().port().into(),
                dst_port: p.remote_address().port().into(),
                src_cid: p.local_cid().map(ToString::to_string),
                dst_cid: p.remote_cid().map(ToString::to_string),
            });

            Some(ev_data)
        },
        now,
    );
}

pub fn connection_state_updated(
    qlog: &mut Qlog,
    old_state: &State,
    new_state: &State,
    now: Instant,
) {
    qlog.add_event_at(
        || {
            Some(EventData::ConnectionStateUpdated(ConnectionStateUpdated {
                old: Some(old_state.into()),
                new: new_state.into(),
            }))
        },
        now,
    );
}

pub fn client_version_information_initiated(
    qlog: &mut Qlog,
    version_config: &version::Config,
    now: Instant,
) {
    qlog.add_event_at(
        || {
            Some(EventData::VersionInformation(VersionInformation {
                client_versions: Some(
                    version_config
                        .all()
                        .iter()
                        .map(|v| format!("{:02x}", v.wire_version()))
                        .collect(),
                ),
                chosen_version: Some(format!("{:02x}", version_config.initial().wire_version())),
                ..Default::default()
            }))
        },
        now,
    );
}

pub fn client_version_information_negotiated(
    qlog: &mut Qlog,
    client: &[Version],
    server: &[version::Wire],
    chosen: Version,
    now: Instant,
) {
    qlog.add_event_at(
        || {
            Some(EventData::VersionInformation(VersionInformation {
                client_versions: Some(
                    client
                        .iter()
                        .map(|v| format!("{:02x}", v.wire_version()))
                        .collect(),
                ),
                server_versions: Some(server.iter().map(|v| format!("{v:02x}")).collect()),
                chosen_version: Some(format!("{:02x}", chosen.wire_version())),
            }))
        },
        now,
    );
}

pub fn server_version_information_failed(
    qlog: &mut Qlog,
    server: &[Version],
    client: version::Wire,
    now: Instant,
) {
    qlog.add_event_at(
        || {
            Some(EventData::VersionInformation(VersionInformation {
                client_versions: Some(vec![format!("{client:02x}")]),
                server_versions: Some(
                    server
                        .iter()
                        .map(|v| format!("{:02x}", v.wire_version()))
                        .collect(),
                ),
                ..Default::default()
            }))
        },
        now,
    );
}

pub fn packet_io(qlog: &mut Qlog, meta: packet::MetaData, now: Instant) {
    qlog.add_event_at(
        || {
            let mut d = Decoder::from(meta.payload());
            let raw = RawInfo {
                length: Some(meta.length() as u64),
                payload_length: None,
                data: None,
            };

            let mut frames = SmallVec::new();
            while d.remaining() > 0 {
                if let Ok(f) = Frame::decode(&mut d) {
                    frames.push(QuicFrame::from(f));
                } else {
                    qinfo!("qlog: invalid frame");
                    break;
                }
            }

            match meta.direction() {
                Direction::Tx => Some(EventData::PacketSent(PacketSent {
                    header: meta.into(),
                    frames: Some(frames),
                    raw: Some(raw),
                    ..Default::default()
                })),
                Direction::Rx => Some(EventData::PacketReceived(PacketReceived {
                    header: meta.into(),
                    frames: Some(frames.to_vec()),
                    raw: Some(raw),
                    ..Default::default()
                })),
            }
        },
        now,
    );
}
pub fn packet_dropped(qlog: &mut Qlog, decrypt_err: &packet::DecryptionError, now: Instant) {
    qlog.add_event_at(
        || {
            let header =
                PacketHeader::with_type(decrypt_err.packet_type().into(), None, None, None, None);
            let raw = RawInfo {
                length: Some(decrypt_err.len() as u64),
                ..Default::default()
            };

            let ev_data = EventData::PacketDropped(PacketDropped {
                header: Some(header),
                raw: Some(raw),
                details: Some(decrypt_err.error.to_string()),
                trigger: Some(PacketDroppedTrigger::DecryptionFailure),
                ..Default::default()
            });

            Some(ev_data)
        },
        now,
    );
}

pub fn packets_lost(qlog: &mut Qlog, pkts: &[sent::Packet], now: Instant) {
    qlog.add_event_with_stream(|stream| {
        for pkt in pkts {
            let header =
                PacketHeader::with_type(pkt.packet_type().into(), Some(pkt.pn()), None, None, None);

            let trigger = pkt
                .loss_info()
                .map(|info| PacketLostTrigger::from(info.trigger))
                .or_else(|| pkt.pto_fired().then_some(PacketLostTrigger::PtoExpired));

            let ev_data = EventData::PacketLost(PacketLost {
                header: Some(header),
                trigger,
                ..Default::default()
            });

            stream.add_event_data_with_instant(ev_data, now)?;
        }
        Ok(())
    });
}

pub fn recovery_parameters_set(
    qlog: &mut Qlog,
    plpmtu: usize,
    cc: CongestionControl,
    now: Instant,
) {
    qlog.add_event_at(
        || {
            let loss_reduction_factor = match cc {
                CongestionControl::NewReno => 0.5,
                CongestionControl::Cubic => {
                    f32::from(u8::try_from(Cubic::BETA_USIZE_DIVIDEND).expect("fits"))
                        / f32::from(u8::try_from(Cubic::BETA_USIZE_DIVISOR).expect("fits"))
                }
            };
            Some(EventData::RecoveryParametersSet(RecoveryParametersSet {
                reordering_threshold: Some(
                    u16::try_from(crate::recovery::PACKET_THRESHOLD).expect("fits"),
                ),
                time_threshold: Some(9.0 / 8.0),
                timer_granularity: Some(u16::try_from(GRANULARITY.as_millis()).expect("fits")),
                initial_rtt: Some(DEFAULT_INITIAL_RTT.as_secs_f32() * 1000.0),
                max_datagram_size: Some(u32::try_from(plpmtu).expect("MTU fits in u32")),
                initial_congestion_window: Some(
                    u64::try_from(CWND_INITIAL_PKTS * plpmtu).expect("fits"),
                ),
                minimum_congestion_window: Some(
                    u32::try_from(2 * plpmtu).expect("MTU fits in u32"),
                ),
                loss_reduction_factor: Some(loss_reduction_factor),
                persistent_congestion_threshold: Some(
                    u16::try_from(PERSISTENT_CONG_THRESH).expect("fits"),
                ),
            }))
        },
        now,
    );
}

pub fn connection_closed(qlog: &mut Qlog, close_reason: &CloseReason, now: Instant) {
    qlog.add_event_at(
        || Some(EventData::ConnectionClosed(close_reason.into())),
        now,
    );
}

pub fn packets_acked(
    qlog: &mut Qlog,
    space: PacketNumberSpace,
    acked_pkts: &[sent::Packet],
    now: Instant,
) {
    if acked_pkts.is_empty() {
        return;
    }
    qlog.add_event_at(
        || {
            let packet_number_space = Some(QlogPacketNumberSpace::from(space));
            let packet_numbers = Some(acked_pkts.iter().map(sent::Packet::pn).collect::<Vec<_>>());
            Some(EventData::PacketsAcked(PacketsAcked {
                packet_number_space,
                packet_numbers,
            }))
        },
        now,
    );
}

pub fn mtu_updated(qlog: &mut Qlog, old_mtu: usize, new_mtu: usize, done: bool, now: Instant) {
    qlog.add_event_at(
        || {
            Some(EventData::MtuUpdated(MtuUpdated {
                old: Some(u16::try_from(old_mtu).expect("MTU fits in u16")),
                new: u16::try_from(new_mtu).expect("MTU fits in u16"),
                done: Some(done),
            }))
        },
        now,
    );
}

#[derive(Clone, Copy)]
#[expect(dead_code, reason = "TODO: Construct all variants.")]
pub enum Metric {
    MinRtt(Duration),
    SmoothedRtt(Duration),
    LatestRtt(Duration),
    RttVariance(Duration),
    PtoCount(usize),
    CongestionWindow(usize),
    BytesInFlight(usize),
    SsThresh(usize),
    PacketsInFlight(u64),
    PacingRate(u64),
}

pub fn metrics_updated<M: IntoIterator<Item = Metric>>(
    qlog: &mut Qlog,
    updated_metrics: M,
    now: Instant,
) {
    qlog.add_event_at(
        || {
            let mut min_rtt: Option<f32> = None;
            let mut smoothed_rtt: Option<f32> = None;
            let mut latest_rtt: Option<f32> = None;
            let mut rtt_variance: Option<f32> = None;
            let mut pto_count: Option<u16> = None;
            let mut congestion_window: Option<u64> = None;
            let mut bytes_in_flight: Option<u64> = None;
            let mut ssthresh: Option<u64> = None;
            let mut packets_in_flight: Option<u64> = None;
            let mut pacing_rate: Option<u64> = None;

            for metric in updated_metrics {
                match metric {
                    Metric::MinRtt(v) => min_rtt = Some(v.as_secs_f32() * 1000.0),
                    Metric::SmoothedRtt(v) => smoothed_rtt = Some(v.as_secs_f32() * 1000.0),
                    Metric::LatestRtt(v) => latest_rtt = Some(v.as_secs_f32() * 1000.0),
                    Metric::RttVariance(v) => rtt_variance = Some(v.as_secs_f32() * 1000.0),
                    Metric::PtoCount(v) => {
                        pto_count = Some(u16::try_from(v).expect("fits in u16"));
                    }
                    Metric::CongestionWindow(v) => {
                        congestion_window = Some(u64::try_from(v).expect("fits in u64"));
                    }
                    Metric::BytesInFlight(v) => {
                        bytes_in_flight = Some(u64::try_from(v).expect("fits in u64"));
                    }
                    Metric::SsThresh(v) => {
                        ssthresh = Some(u64::try_from(v).expect("fits in u64"));
                    }
                    Metric::PacketsInFlight(v) => packets_in_flight = Some(v),
                    Metric::PacingRate(v) => pacing_rate = Some(v),
                }
            }

            debug_assert!(
                min_rtt.is_some()
                    || smoothed_rtt.is_some()
                    || latest_rtt.is_some()
                    || rtt_variance.is_some()
                    || pto_count.is_some()
                    || congestion_window.is_some()
                    || bytes_in_flight.is_some()
                    || ssthresh.is_some()
                    || packets_in_flight.is_some()
                    || pacing_rate.is_some(),
                "metrics_updated called with no metrics"
            );

            let ev_data = EventData::MetricsUpdated(MetricsUpdated {
                min_rtt,
                smoothed_rtt,
                latest_rtt,
                rtt_variance,
                pto_count,
                congestion_window,
                bytes_in_flight,
                ssthresh,
                packets_in_flight,
                pacing_rate,
                ..Default::default()
            });

            Some(ev_data)
        },
        now,
    );
}

/// Trigger for a `recovery:congestion_state_updated` qlog event.
#[derive(Clone, Copy)]
pub enum CongestionStateTrigger {
    /// The congestion state change was triggered by an ECN mark.
    Ecn,
    /// The congestion state change was triggered by persistent congestion.
    PersistentCongestion,
}

impl From<CongestionStateTrigger> for CongestionStateUpdatedTrigger {
    fn from(value: CongestionStateTrigger) -> Self {
        match value {
            CongestionStateTrigger::Ecn => Self::Ecn,
            CongestionStateTrigger::PersistentCongestion => Self::PersistentCongestion,
        }
    }
}

pub fn congestion_state_updated(
    qlog: &mut Qlog,
    old_state: Option<&'static str>,
    new_state: &'static str,
    trigger: Option<CongestionStateTrigger>,
    now: Instant,
) {
    qlog.add_event_at(
        || {
            Some(EventData::CongestionStateUpdated(CongestionStateUpdated {
                old: old_state.map(ToOwned::to_owned),
                new: new_state.to_owned(),
                trigger: trigger.map(Into::into),
            }))
        },
        now,
    );
}

/// The type of loss recovery timer that fired or was updated.
#[derive(Clone, Copy, Debug)]
pub enum LossTimerType {
    /// The reordering/loss-detection timer (ACK-based).
    Ack,
    /// The Probe Timeout timer.
    Pto,
}

/// Emit a `loss_timer_updated` Set event.
///
/// Only the PTO timer has explicit set/cancel lifecycle in neqo. The
/// loss-detection (Ack) timer is derived lazily from packet state on every
/// call to [`crate::recovery::Loss::next_timeout`] and has no single arm or
/// cancel point to instrument.
pub fn loss_timer_set(qlog: &mut Qlog, now: Instant) {
    loss_timer_updated(qlog, LossTimerEventType::Set, Some(TimerType::Pto), now);
}

pub fn loss_timer_expired(qlog: &mut Qlog, timer_type: LossTimerType, now: Instant) {
    loss_timer_updated(
        qlog,
        LossTimerEventType::Expired,
        Some(timer_type.into()),
        now,
    );
}

/// Emit a `loss_timer_updated` Cancelled event.
///
/// See [`loss_timer_set`] for why only `TimerType::Pto` is used here.
pub fn loss_timer_cancelled(qlog: &mut Qlog, now: Instant) {
    loss_timer_updated(
        qlog,
        LossTimerEventType::Cancelled,
        Some(TimerType::Pto),
        now,
    );
}

fn loss_timer_updated(
    qlog: &mut Qlog,
    event_type: LossTimerEventType,
    timer_type: Option<TimerType>,
    now: Instant,
) {
    qlog.add_event_at(
        || {
            Some(EventData::LossTimerUpdated(LossTimerUpdated {
                timer_type,
                packet_number_space: None,
                event_type,
                delta: None,
            }))
        },
        now,
    );
}

// Helper functions

#[expect(clippy::too_many_lines, reason = "Yeah, but it's a nice match.")]
#[expect(
    clippy::cast_precision_loss,
    clippy::cast_possible_truncation,
    reason = "We need to truncate here."
)]
impl From<Frame<'_>> for QuicFrame {
    fn from(frame: Frame) -> Self {
        match frame {
            Frame::Padding(len) => Self::Padding {
                length: None,
                payload_length: u32::from(len),
            },
            Frame::Ping => Self::Ping {
                length: None,
                payload_length: None,
            },
            Frame::Ack {
                largest_acknowledged,
                ack_delay,
                first_ack_range,
                ack_ranges,
                ecn_count,
            } => {
                let ranges =
                    Frame::decode_ack_frame(largest_acknowledged, first_ack_range, &ack_ranges)
                        .ok();

                let acked_ranges = ranges.map(|all| {
                    AckedRanges::Double(
                        all.into_iter()
                            .map(RangeInclusive::into_inner)
                            .collect::<Vec<_>>(),
                    )
                });

                Self::Ack {
                    ack_delay: Some(ack_delay as f32 / 1000.0),
                    acked_ranges,
                    ect1: ecn_count.map(|c| c[Ecn::Ect1]),
                    ect0: ecn_count.map(|c| c[Ecn::Ect0]),
                    ce: ecn_count.map(|c| c[Ecn::Ce]),
                    length: None,
                    payload_length: None,
                }
            }
            Frame::ResetStream {
                stream_id,
                application_error_code,
                final_size,
            } => Self::ResetStream {
                stream_id: stream_id.as_u64(),
                error_code: application_error_code,
                final_size,
                length: None,
                payload_length: None,
            },
            Frame::StopSending {
                stream_id,
                application_error_code,
            } => Self::StopSending {
                stream_id: stream_id.as_u64(),
                error_code: application_error_code,
                length: None,
                payload_length: None,
            },
            Frame::Crypto { offset, data } => Self::Crypto {
                offset,
                length: data.len() as u64,
            },
            Frame::NewToken { token } => Self::NewToken {
                token: qlog::Token {
                    ty: None,
                    details: None,
                    raw: Some(RawInfo {
                        data: Some(hex(token)),
                        length: Some(token.len() as u64),
                        payload_length: None,
                    }),
                },
            },
            Frame::Stream {
                fin,
                stream_id,
                offset,
                data,
                ..
            } => Self::Stream {
                stream_id: stream_id.as_u64(),
                offset,
                length: data.len() as u64,
                fin: Some(fin),
                raw: None,
            },
            Frame::MaxData { maximum_data } => Self::MaxData {
                maximum: maximum_data,
            },
            Frame::MaxStreamData {
                stream_id,
                maximum_stream_data,
            } => Self::MaxStreamData {
                stream_id: stream_id.as_u64(),
                maximum: maximum_stream_data,
            },
            Frame::MaxStreams {
                stream_type,
                maximum_streams,
            } => Self::MaxStreams {
                stream_type: stream_type.into(),
                maximum: maximum_streams,
            },
            Frame::DataBlocked { data_limit } => Self::DataBlocked { limit: data_limit },
            Frame::StreamDataBlocked {
                stream_id,
                stream_data_limit,
            } => Self::StreamDataBlocked {
                stream_id: stream_id.as_u64(),
                limit: stream_data_limit,
            },
            Frame::StreamsBlocked {
                stream_type,
                stream_limit,
            } => Self::StreamsBlocked {
                stream_type: stream_type.into(),
                limit: stream_limit,
            },
            Frame::NewConnectionId {
                sequence_number,
                retire_prior,
                connection_id,
                stateless_reset_token,
            } => Self::NewConnectionId {
                sequence_number: sequence_number as u32,
                retire_prior_to: retire_prior as u32,
                connection_id_length: Some(connection_id.len() as u8),
                connection_id: hex(connection_id),
                stateless_reset_token: Some(hex(stateless_reset_token)),
            },
            Frame::RetireConnectionId { sequence_number } => Self::RetireConnectionId {
                sequence_number: sequence_number as u32,
            },
            Frame::PathChallenge { data } => Self::PathChallenge {
                data: Some(hex(data)),
            },
            Frame::PathResponse { data } => Self::PathResponse {
                data: Some(hex(data)),
            },
            Frame::ConnectionClose {
                error_code,
                frame_type,
                reason_phrase,
            } => Self::ConnectionClose {
                error_space: Some((&error_code).into()),
                error_code: Some(error_code.code()),
                error_code_value: Some(error_code.code()),
                reason: Some(reason_phrase),
                trigger_frame_type: Some(frame_type),
            },
            Frame::HandshakeDone => Self::HandshakeDone,
            Frame::AckFrequency { .. } => Self::Unknown {
                frame_type_value: None,
                raw_frame_type: frame.get_type().into(),
                raw: None,
            },
            Frame::Datagram { data, .. } => Self::Datagram {
                length: data.len() as u64,
                raw: None,
            },
        }
    }
}

impl From<&State> for ConnectionState {
    fn from(state: &State) -> Self {
        match state {
            State::Init | State::WaitInitial => Self::Attempted,
            State::WaitVersion | State::Handshaking => Self::HandshakeStarted,
            State::Connected => Self::HandshakeCompleted,
            State::Confirmed => Self::HandshakeConfirmed,
            State::Closing { .. } => Self::Closing,
            State::Draining { .. } => Self::Draining,
            State::Closed { .. } => Self::Closed,
        }
    }
}

impl From<PacketNumberSpace> for QlogPacketNumberSpace {
    fn from(space: PacketNumberSpace) -> Self {
        match space {
            PacketNumberSpace::Initial => Self::Initial,
            PacketNumberSpace::Handshake => Self::Handshake,
            PacketNumberSpace::ApplicationData => Self::ApplicationData,
        }
    }
}

impl From<NeqoStreamType> for StreamType {
    fn from(stream_type: NeqoStreamType) -> Self {
        match stream_type {
            NeqoStreamType::BiDi => Self::Bidirectional,
            NeqoStreamType::UniDi => Self::Unidirectional,
        }
    }
}

impl From<&CloseError> for ErrorSpace {
    fn from(error: &CloseError) -> Self {
        match error {
            CloseError::Transport(_) => Self::TransportError,
            CloseError::Application(_) => Self::ApplicationError,
        }
    }
}

impl From<&CloseReason> for ConnectionClosed {
    fn from(close_reason: &CloseReason) -> Self {
        let (connection_code, application_code, trigger) = match close_reason {
            CloseReason::Transport(e) if *e == crate::Error::IdleTimeout => {
                (None, None, Some(ConnectionClosedTrigger::IdleTimeout))
            }
            CloseReason::Transport(e) if *e == crate::Error::StatelessReset => {
                (None, None, Some(ConnectionClosedTrigger::StatelessReset))
            }
            CloseReason::Transport(e) if *e == crate::Error::VersionNegotiation => {
                (None, None, Some(ConnectionClosedTrigger::VersionMismatch))
            }
            CloseReason::Transport(e) if *e == crate::Error::None => {
                (None, None, Some(ConnectionClosedTrigger::Clean))
            }
            CloseReason::Transport(crate::Error::Peer(code)) => (
                Some(ConnectionErrorCode::Value(*code)),
                None,
                Some(ConnectionClosedTrigger::Error),
            ),
            CloseReason::Application(code)
            | CloseReason::Transport(crate::Error::PeerApplication(code)) => (
                None,
                Some(ApplicationErrorCode::Value(*code)),
                Some(ConnectionClosedTrigger::Application),
            ),
            CloseReason::Transport(e) => (
                Some(ConnectionErrorCode::Value(e.code())),
                None,
                Some(ConnectionClosedTrigger::Error),
            ),
        };
        Self {
            owner: None,
            connection_code,
            application_code,
            internal_code: None,
            reason: None,
            trigger,
        }
    }
}

impl From<LossTimerType> for TimerType {
    fn from(value: LossTimerType) -> Self {
        match value {
            LossTimerType::Ack => Self::Ack,
            LossTimerType::Pto => Self::Pto,
        }
    }
}

impl From<sent::LossTrigger> for PacketLostTrigger {
    fn from(value: sent::LossTrigger) -> Self {
        match value {
            sent::LossTrigger::TimeThreshold => Self::TimeThreshold,
            sent::LossTrigger::ReorderingThreshold => Self::ReorderingThreshold,
        }
    }
}

impl From<packet::Type> for qlog::events::quic::PacketType {
    fn from(value: packet::Type) -> Self {
        match value {
            packet::Type::Initial => Self::Initial,
            packet::Type::Handshake => Self::Handshake,
            packet::Type::ZeroRtt => Self::ZeroRtt,
            packet::Type::Short => Self::OneRtt,
            packet::Type::Retry => Self::Retry,
            packet::Type::VersionNegotiation => Self::VersionNegotiation,
            packet::Type::OtherVersion => Self::Unknown,
        }
    }
}

#[cfg(test)]
mod tests {
    use test_fixture::new_neqo_qlog;

    use super::{Metric, metrics_updated};

    /// Verify that `metrics_updated` records all metric variants, including
    /// `SsThresh`, when qlog is enabled.
    #[test]
    fn metrics_updated_all_variants() {
        let (mut qlog, contents) = new_neqo_qlog();
        let now = test_fixture::now();
        metrics_updated(
            &mut qlog,
            [Metric::CongestionWindow(10_000), Metric::SsThresh(5_000)],
            now,
        );
        drop(qlog);
        let output = contents.to_string();
        assert!(
            output.contains("congestion_window"),
            "missing congestion_window"
        );
        assert!(output.contains("ssthresh"), "missing ssthresh");
    }
}

Messung V0.5 in Prozent
C=93 H=96 G=94

[0.28QuellennavigatorsProjekt 2026-10-11]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1127926
#Domains=2039723