From f2091e0e5d7b6437edeeb6449c7359d9fb0da681 Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Wed, 10 Jun 2026 06:44:31 -0700 Subject: [PATCH 1/3] agnosticdev/RTTMetrics: Expose RTT Metrics --- .../Connection/DataTransferSnapshot.swift | 6 +- .../Connection/NetworkMetrics.swift | 27 +++++ .../ProtocolEstablishmentReport.swift | 104 ++++++++++++++++++ .../Protocols/BottomProtocol.swift | 23 ++++ .../Protocols/HarnessProtocols.swift | 6 + .../Protocols/ManyToManyProtocol.swift | 26 +++++ .../Protocols/OneToOneProtocol.swift | 34 ++++++ .../Protocols/ProtocolControlHandlers.swift | 28 +++++ .../Protocols/ProtocolLinkage.swift | 4 + .../SwiftNetwork/QUIC/CongestionControl.swift | 13 +++ Sources/SwiftNetwork/QUIC/Crypto.swift | 3 + Sources/SwiftNetwork/QUIC/Cubic.swift | 2 - Sources/SwiftNetwork/QUIC/Ledbat.swift | 2 - Sources/SwiftNetwork/QUIC/Migration.swift | 10 ++ Sources/SwiftNetwork/QUIC/Prague.swift | 2 - .../SwiftNetwork/QUIC/QUICConnection.swift | 71 +++++++++++- Sources/SwiftNetwork/QUIC/QUICPath.swift | 5 + Sources/SwiftNetwork/QUIC/Statistics.swift | 5 +- Tests/SwiftNetworkTests/QUICTestHarness.swift | 33 ++++++ .../SwiftNetworkQUICHarnessTests.swift | 9 ++ 20 files changed, 399 insertions(+), 14 deletions(-) create mode 100644 Sources/SwiftNetwork/Connection/NetworkMetrics.swift create mode 100644 Sources/SwiftNetwork/Connection/ProtocolEstablishmentReport.swift diff --git a/Sources/SwiftNetwork/Connection/DataTransferSnapshot.swift b/Sources/SwiftNetwork/Connection/DataTransferSnapshot.swift index ebbcc7e..94e72c3 100644 --- a/Sources/SwiftNetwork/Connection/DataTransferSnapshot.swift +++ b/Sources/SwiftNetwork/Connection/DataTransferSnapshot.swift @@ -12,8 +12,9 @@ // //===----------------------------------------------------------------------===// -#if !NETWORK_EMBEDDED -struct DataTransferSnapshot: Equatable { +@_spi(ProtocolProvider) +@available(Network 0.1.0, *) +public struct DataTransferSnapshot: Equatable { var interfaceIndex: UInt64? var receivedIPPacketCount: UInt64 = 0 @@ -49,4 +50,3 @@ struct DataTransferSnapshot: Equatable { var migrationToOtherCount: UInt64 = 0 var migrationToFallbackCount: UInt64 = 0 } -#endif diff --git a/Sources/SwiftNetwork/Connection/NetworkMetrics.swift b/Sources/SwiftNetwork/Connection/NetworkMetrics.swift new file mode 100644 index 0000000..e954afb --- /dev/null +++ b/Sources/SwiftNetwork/Connection/NetworkMetrics.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@_spi(ProtocolProvider) +@available(Network 0.1.0, *) +public enum NetworkMetricsType { + case protocolEstablishmentReports + case dataTransferSnapshot +} + +@_spi(ProtocolProvider) +@available(Network 0.1.0, *) +public enum NetworkMetrics { + case protocolEstablishmentReports([ProtocolEstablishmentReport]) + case dataTransferSnapshot(DataTransferSnapshot) +} diff --git a/Sources/SwiftNetwork/Connection/ProtocolEstablishmentReport.swift b/Sources/SwiftNetwork/Connection/ProtocolEstablishmentReport.swift new file mode 100644 index 0000000..5288f41 --- /dev/null +++ b/Sources/SwiftNetwork/Connection/ProtocolEstablishmentReport.swift @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@_spi(ProtocolProvider) +@available(Network 0.1.0, *) +public enum ClientAccurateECNState: UInt32, Equatable { + case ecnInvalid = 0 + case ecnFeatureDisabled = 1 + case ecnFeatureEnabled = 2 + case classicECNAvailable = 3 // TCP only + case ecnNotAvailable = 4 + case ecnNegotiationBlackholed = 5 + case ecnAccurateECNBleachingDetected = 6 // TCP only + case ecnNegotiationSuccess = 7 + case ecnNegotiationSuccessECTManglingDetected = 8 // TCP only + case ecnNegotiationSuccessECTBleachingDetected = 9 // TCP only +} + +@_spi(ProtocolProvider) +@available(Network 0.1.0, *) +public enum ServerAccurateECNState: UInt32, Equatable { + case ecnInvalid = 0 + case ecnFeatureDisabled = 1 + case ecnFeatureEnabled = 2 + case noECNRequested = 3 + case classicEcnRequested = 4 + case ecnRequested = 5 + case ecnNegotiationBlackholed = 6 + case ecnAccurateECNBleachingDetected = 7 + case ecnNegotiationSuccess = 8 + case ecnNegotiationSuccessECTManglingDetected = 9 + case ecnNegotiationSuccessECTBleachingDetected = 10 +} + +@_spi(ProtocolProvider) +@available(Network 0.1.0, *) +public struct ProtocolEstablishmentReport: Equatable { + let handshakeMilliseconds: NetworkDuration + let handshakeRTTMilliseconds: NetworkDuration + let protocolIdentifier: ProtocolIdentifier + let clientAccurateECNState: ClientAccurateECNState + let serverAccurateECNState: ServerAccurateECNState + + init( + handshakeMilliseconds: NetworkDuration, + handshakeRTTMilliseconds: NetworkDuration, + protocolIdentifier: ProtocolIdentifier, + clientAccurateECNState: ClientAccurateECNState = .ecnInvalid, + serverAccurateECNState: ServerAccurateECNState = .ecnInvalid + ) { + self.handshakeMilliseconds = handshakeMilliseconds + self.handshakeRTTMilliseconds = handshakeRTTMilliseconds + self.protocolIdentifier = protocolIdentifier + self.clientAccurateECNState = clientAccurateECNState + self.serverAccurateECNState = serverAccurateECNState + } + + struct Flags: OptionSet { + init(rawValue: Self.RawValue) { + self.rawValue = rawValue + } + var rawValue: UInt8 + static let l4sEnabled = Flags(rawValue: 1 << 0) + static let quicMigrationSupported = Flags(rawValue: 1 << 1) + static let quicStatelessResetReceived = Flags(rawValue: 1 << 2) + static let quicStatelessResetDuringPathProbe = Flags(rawValue: 1 << 3) + } + private var flags = Flags() + var l4sEnabled: Bool { + get { flags.contains(.l4sEnabled) } + set { if newValue { flags.insert(.l4sEnabled) } else { flags.remove(.l4sEnabled) } } + } + var quicMigrationSupported: Bool { + get { flags.contains(.quicMigrationSupported) } + set { if newValue { flags.insert(.quicMigrationSupported) } else { flags.remove(.quicMigrationSupported) } } + } + var quicStatelessResetReceived: Bool { + get { flags.contains(.quicStatelessResetReceived) } + set { + if newValue { flags.insert(.quicStatelessResetReceived) } else { flags.remove(.quicStatelessResetReceived) } + } + } + var quicStatelessResetDuringPathProbe: Bool { + get { flags.contains(.quicStatelessResetDuringPathProbe) } + set { + if newValue { + flags.insert(.quicStatelessResetDuringPathProbe) + } else { + flags.remove(.quicStatelessResetDuringPathProbe) + } + } + } +} diff --git a/Sources/SwiftNetwork/Protocols/BottomProtocol.swift b/Sources/SwiftNetwork/Protocols/BottomProtocol.swift index 450e745..3f8b2ba 100644 --- a/Sources/SwiftNetwork/Protocols/BottomProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/BottomProtocol.swift @@ -66,6 +66,12 @@ public protocol BottomProtocolHandler: ~Copyable, OutboundDataHandler { /// The metadata state for this protocol. var metadata: AbstractProtocolMetadata? { get } #endif + + /// Update this protocols contribution to a data transfer snapshot. + func updateDataTransferSnapshot(_ snapshot: inout DataTransferSnapshot) + + /// Fetch this protocols establishment report entry + var protocolEstablishmentReport: ProtocolEstablishmentReport? { get } } @_spi(ProtocolProvider) @@ -245,6 +251,19 @@ extension BottomProtocolHandler where Self: ~Copyable { return nil } + public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + do { try validate(upper: from, #function) } catch { return nil } + switch type { + case .protocolEstablishmentReports: + guard let report = protocolEstablishmentReport else { return nil } + return .protocolEstablishmentReports([report]) + case .dataTransferSnapshot: + var snapshot = DataTransferSnapshot() + updateDataTransferSnapshot(&snapshot) + return .dataTransferSnapshot(snapshot) + } + } + #if !NETWORK_EMBEDDED public func getOptions(from parameters: Parameters) -> ProtocolOptions? { parameters.protocolOptions(for: self.reference) @@ -279,6 +298,10 @@ extension BottomProtocolHandler where Self: ~Copyable { #if !NETWORK_EMBEDDED public var metadata: AbstractProtocolMetadata? { nil } #endif + + public func updateDataTransferSnapshot(_ snapshot: inout DataTransferSnapshot) {} + + public var protocolEstablishmentReport: ProtocolEstablishmentReport? { nil } } extension BottomProtocolHandler where Self: ~Copyable, UpperProtocol == InboundDatagramLinkage { diff --git a/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift b/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift index 057129d..a545124 100644 --- a/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift +++ b/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift @@ -926,6 +926,12 @@ public class NewFlowHarness NetworkMetrics? { + fromExternal { + lower.invokeGetMetrics(reference, type: type) + } + } } @_spi(ProtocolProvider) diff --git a/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift b/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift index 735b6e2..1c02817 100644 --- a/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift @@ -61,6 +61,8 @@ public protocol ManyToManyProtocolHandler: ListenerHandler, LoggableProtocol { func teardown(flow: MultiplexedFlowIdentifier) func handleApplicationEvent(flow: MultiplexedFlowIdentifier, event: ApplicationEvent) -> HandleNetworkEventResult func getMetadata

(flow: MultiplexedFlowIdentifier) -> ProtocolMetadata

? where P: NetworkProtocol + func updateDataTransferSnapshot(flow: MultiplexedFlowIdentifier, _ snapshot: inout DataTransferSnapshot) + var protocolEstablishmentReport: ProtocolEstablishmentReport? { get } // MARK: Per-path events to implement func handleConnectedEvent(path: MultiplexingPathIdentifier) @@ -291,6 +293,25 @@ extension ManyToManyProtocolHandler { getMetadata(flow: .allFlows) } + public func updateDataTransferSnapshot(flow: MultiplexedFlowIdentifier, _ snapshot: inout DataTransferSnapshot) {} + public var protocolEstablishmentReport: ProtocolEstablishmentReport? { nil } + + public func getMetrics(flow: MultiplexedFlowIdentifier, type: NetworkMetricsType) -> NetworkMetrics? { + switch type { + case .protocolEstablishmentReports: + guard let report = protocolEstablishmentReport else { return nil } + return .protocolEstablishmentReports([report]) + case .dataTransferSnapshot: + var snapshot = DataTransferSnapshot() + updateDataTransferSnapshot(flow: flow, &snapshot) + return .dataTransferSnapshot(snapshot) + } + } + + public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + getMetrics(flow: .allFlows, type: type) + } + public func handleInboundDataAvailableEvent(path: MultiplexingPathIdentifier) {} public func handleOutboundRoomAvailableEvent(path: MultiplexingPathIdentifier) {} @@ -1211,6 +1232,11 @@ extension MultiplexedFlow { return parentProtocol.getMetadata(flow: identifier) } + public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + do { try validate(upper: from, #function) } catch { return nil } + return parentProtocol.getMetrics(flow: identifier, type: type) + } + fileprivate func deliverConnectedEvent() { if upper.isDetached { // Enqueue pending event instead of delivering immediately. diff --git a/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift b/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift index 1b0a784..a578150 100644 --- a/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift @@ -89,6 +89,12 @@ public protocol OneToOneProtocolHandler: ~Copyable, OutboundDataHandler, Inbound /// Protocols that don't handle events should initialize this to `true`. /// The stack may set this to `false` explicitly, after which you shouldn't set it back to `true`. var passthroughEvents: Bool { get set } + + /// Update this protocols data transfer snapshot. + func updateDataTransferSnapshot(_ snapshot: inout DataTransferSnapshot) + + /// Fetch this protocols establishment report entry + var protocolEstablishmentReport: ProtocolEstablishmentReport? { get } } @_spi(ProtocolProvider) @@ -379,6 +385,30 @@ extension OneToOneProtocolHandler where Self: ~Copyable { #endif } + public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + do { try validate(upper: from, #function) } catch { return nil } + let lowerMetrics = lower.invokeGetMetrics(effectiveSelfReference, type: type) + switch type { + case .protocolEstablishmentReports: + var reports = [ProtocolEstablishmentReport]() + if case .protocolEstablishmentReports(let protocolEstablishmentReports) = lowerMetrics { + reports = protocolEstablishmentReports + } + if let currentProtocolEstablishmentReport = protocolEstablishmentReport { + reports.append(currentProtocolEstablishmentReport) + } + return .protocolEstablishmentReports(reports) + case .dataTransferSnapshot: + if case .dataTransferSnapshot(var snapshot) = lowerMetrics { + updateDataTransferSnapshot(&snapshot) + return .dataTransferSnapshot(snapshot) + } + var snapshot = DataTransferSnapshot() + updateDataTransferSnapshot(&snapshot) + return .dataTransferSnapshot(snapshot) + } + } + public func tlsOptions(from parameters: Parameters) -> ProtocolOptions? { parameters.tlsOptions(for: self.reference) } @@ -429,6 +459,10 @@ extension OneToOneProtocolHandler where Self: ~Copyable { public mutating func handleApplicationEvent(_ event: ApplicationEvent) -> HandleNetworkEventResult { .unconsumed } + + public func updateDataTransferSnapshot(_ snapshot: inout DataTransferSnapshot) {} + + public var protocolEstablishmentReport: ProtocolEstablishmentReport? { nil } } extension OneToOneProtocolHandler where Self: ~Copyable { diff --git a/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift b/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift index f325637..2c7df11 100644 --- a/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift +++ b/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift @@ -142,6 +142,7 @@ public protocol LowerProtocolHandler: ~Copyable, ProtocolInstance mutating func handleApplicationEvent(_ from: ProtocolInstanceReference, event: ApplicationEvent) func getMetadata(_ from: ProtocolInstanceReference) -> ProtocolMetadata

? + func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? } extension ProtocolInstanceReference { @@ -888,4 +889,31 @@ extension ProtocolInstanceReference { } } } + + public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + self.handleCallFromUpperProtocol { + switch self.reference { + case .none: return nil + case .udp(let index): return context.udpInstances[index].getMetrics(from, type: type) + case .ip(let index): return context.ipInstances[index].getMetrics(from, type: type) + case .tcp(let instance): return instance.getMetrics(from, type: type) + case .tls(let instance): return instance.getMetrics(from, type: type) + #if !NETWORK_NO_SWIFT_QUIC + case .quic(let instance): return instance.getMetrics(from, type: type) + case .quicStream(let instance): return instance.getMetrics(from, type: type) + case .quicDatagram(let instance): return instance.getMetrics(from, type: type) + case .quicCrypto(let instance): return instance.getMetrics(from, type: type) + #if !NETWORK_NO_TESTING_HARNESS + case .datagramLowerHarness(let instance): return instance.getMetrics(from, type: type) + case .streamLowerHarness(let instance): return instance.getMetrics(from, type: type) + #endif + #endif + #if !NETWORK_EMBEDDED + case .custom(let container, let index): + return container.accessLower(at: index) { $0.getMetrics(from, type: type) } + #endif + default: fatalError("Protocol cannot accept getMetrics call") + } + } + } } diff --git a/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift b/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift index e26f2de..3e37691 100644 --- a/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift +++ b/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift @@ -213,6 +213,10 @@ extension LowerProtocolLinkage { public func invokeGetMetadata(_ from: ProtocolInstanceReference) -> ProtocolMetadata

? { reference.getMetadata(from) } + + public func invokeGetMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + reference.getMetrics(from, type: type) + } } @_spi(ProtocolProvider) diff --git a/Sources/SwiftNetwork/QUIC/CongestionControl.swift b/Sources/SwiftNetwork/QUIC/CongestionControl.swift index 4b27a66..b96b352 100644 --- a/Sources/SwiftNetwork/QUIC/CongestionControl.swift +++ b/Sources/SwiftNetwork/QUIC/CongestionControl.swift @@ -281,6 +281,19 @@ enum CongestionControl { #endif } } + + func filloutDataTransferSnapshot(dataTransferSnapshot: inout DataTransferSnapshot) { + switch self { + case .cubic(algorithm: let cubic): + cubic.filloutDataTransferSnapshot(dataTransferSnapshot: &dataTransferSnapshot) + #if !NETWORK_EMBEDDED + case .ledbat(algorithm: let ledbat): + ledbat.filloutDataTransferSnapshot(dataTransferSnapshot: &dataTransferSnapshot) + case .prague(algorithm: let prague): + prague.filloutDataTransferSnapshot(dataTransferSnapshot: &dataTransferSnapshot) + #endif + } + } } protocol CongestionControlProtocol: PrefixedLoggable { diff --git a/Sources/SwiftNetwork/QUIC/Crypto.swift b/Sources/SwiftNetwork/QUIC/Crypto.swift index 1d429bc..c923a54 100644 --- a/Sources/SwiftNetwork/QUIC/Crypto.swift +++ b/Sources/SwiftNetwork/QUIC/Crypto.swift @@ -508,6 +508,9 @@ extension QUICCrypto: OutboundStreamHandler { ) {} func getMetadata

(_ from: ProtocolInstanceReference) -> ProtocolMetadata

? where P: NetworkProtocol { nil } + func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + nil + } func levelForReference(_ from: ProtocolInstanceReference) -> SwiftTLSOptions.EncryptionLevel? { if from == initialLinkage.reference { return .initial } if from == earlyDataLinkage.reference { return .earlyData } diff --git a/Sources/SwiftNetwork/QUIC/Cubic.swift b/Sources/SwiftNetwork/QUIC/Cubic.swift index 363073c..88326c1 100644 --- a/Sources/SwiftNetwork/QUIC/Cubic.swift +++ b/Sources/SwiftNetwork/QUIC/Cubic.swift @@ -432,12 +432,10 @@ struct Cubic: CongestionControlProtocol, CubicLikeProtocol { initPipeAckSamples() } - #if !NETWORK_EMBEDDED func filloutDataTransferSnapshot(dataTransferSnapshot: inout DataTransferSnapshot) { dataTransferSnapshot.transportCongestionWindow = congestionWindow dataTransferSnapshot.transportSlowStartThreshold = slowStartThreshold } - #endif mutating func reset(mss: Int, qlog: QLog? = nil) { congestionWindow = Cubic.initialCongestionWindow(mss) diff --git a/Sources/SwiftNetwork/QUIC/Ledbat.swift b/Sources/SwiftNetwork/QUIC/Ledbat.swift index 9ac96bd..9db4826 100644 --- a/Sources/SwiftNetwork/QUIC/Ledbat.swift +++ b/Sources/SwiftNetwork/QUIC/Ledbat.swift @@ -317,12 +317,10 @@ struct Ledbat: CongestionControlProtocol, CubicLikeProtocol { logUpdate(qlog: qlog) } - #if !NETWORK_EMBEDDED func filloutDataTransferSnapshot(dataTransferSnapshot: inout DataTransferSnapshot) { dataTransferSnapshot.transportCongestionWindow = congestionWindow dataTransferSnapshot.transportSlowStartThreshold = slowStartThreshold } - #endif mutating func reset(mss: Int, qlog: QLog? = nil) { congestionWindow = Ledbat.initialCongestionWindow(mss) diff --git a/Sources/SwiftNetwork/QUIC/Migration.swift b/Sources/SwiftNetwork/QUIC/Migration.swift index f7a2325..94effd7 100644 --- a/Sources/SwiftNetwork/QUIC/Migration.swift +++ b/Sources/SwiftNetwork/QUIC/Migration.swift @@ -129,6 +129,16 @@ struct Migration: ~Copyable { } + func probingPathCount(_ connection: QUICConnection) -> Int { + var probingPaths = 0 + connection.applyToAllPaths { path in + if path.state.isProbing { + probingPaths += 1 + } + } + return probingPaths + } + func handshakeConfirmed(_ connection: QUICConnection) { // TODO: pending migration feature completion } diff --git a/Sources/SwiftNetwork/QUIC/Prague.swift b/Sources/SwiftNetwork/QUIC/Prague.swift index 655a6fc..374e016 100644 --- a/Sources/SwiftNetwork/QUIC/Prague.swift +++ b/Sources/SwiftNetwork/QUIC/Prague.swift @@ -657,12 +657,10 @@ struct Prague: CongestionControlProtocol, CubicLikeProtocol { resetInternal() } - #if !NETWORK_EMBEDDED func filloutDataTransferSnapshot(dataTransferSnapshot: inout DataTransferSnapshot) { dataTransferSnapshot.transportCongestionWindow = congestionWindow dataTransferSnapshot.transportSlowStartThreshold = slowStartThreshold } - #endif mutating func reset(mss: Int, qlog: QLog? = nil) { congestionWindow = Prague.initialCongestionWindow(mss) diff --git a/Sources/SwiftNetwork/QUIC/QUICConnection.swift b/Sources/SwiftNetwork/QUIC/QUICConnection.swift index e00d776..37f2ec0 100644 --- a/Sources/SwiftNetwork/QUIC/QUICConnection.swift +++ b/Sources/SwiftNetwork/QUIC/QUICConnection.swift @@ -238,8 +238,10 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, } } - var handshakeStartTime: NetworkClock.Instant = .zero - var idleTimeout: NetworkDuration = .zero + private(set) var handshakeStartTime: NetworkClock.Instant = .zero + private(set) var handshakeDuration: NetworkDuration = .zero + private(set) var handshakeRTT: NetworkDuration = .zero + private(set) var idleTimeout: NetworkDuration = .zero var keepaliveDuration: NetworkDuration = .zero var keepaliveTimerID: Timer.TimerID? @@ -352,6 +354,7 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, private(set) var waitingForOutstandingKeepAliveAcks = false private(set) var tlsOptions: SwiftTLSProtocol.Options? private(set) var testSendingShortPackets = false + private(set) var migrationSupported = false // false == IPv6, true == IPv4 private(set) var initialAddressIsIPv4 = false @@ -1022,6 +1025,7 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, log.error("Error inserting remote CID for a preferred address: \(error)") } } + migrationSupported = true migration.addPreferredAddress(preferredAddress) } } @@ -2499,6 +2503,58 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, } #endif + public func updateDataTransferSnapshot(flow: MultiplexedFlowIdentifier, _ snapshot: inout DataTransferSnapshot) { + snapshot.receivedTransportOutOfOrderByteCount = UInt64(clamping: self.stats[.rxOutOfOrderBytes]) + snapshot.sentTransportRetransmittedByteCount = UInt64(clamping: self.stats[.txRetransmittedBytes]) + snapshot.sentTransportECNCapablePacketCount = UInt64(clamping: self.stats[.ecnCapablePacketsSent]) + snapshot.sentTransportECNCapableAckedPacketCount = UInt64(clamping: self.stats[.ecnCapablePacketsAcknowledged]) + snapshot.sentTransportECNCapableMarkedPacketCount = UInt64(clamping: self.stats[.ecnCapablePacketsMarked]) + snapshot.sentTransportECNCapableLostPacketCount = UInt64(clamping: self.stats[.ecnCapablePacketsLost]) + if let path = currentPath { + snapshot.transportMinimumRTT = path.rtt.minRTT + snapshot.transportSmoothedRTT = path.rtt.smoothedRTT + snapshot.transportCurrentRTT = path.rtt.adjustedRTT + snapshot.transportRTTVariance = path.rtt.RTTVariance + path.congestionControlFilloutDataTransferSnapshot(snapshot: &snapshot) + } + } + + public var protocolEstablishmentReport: ProtocolEstablishmentReport? { + var clientAccurateECNState: ClientAccurateECNState = .ecnFeatureDisabled + var l4sEnabled = false + if let path = currentPath { + l4sEnabled = path.l4sEnabled + if let ecnState = path.ecnState?.state { + switch ecnState { + case .probing, .validate: + clientAccurateECNState = .ecnFeatureEnabled + case .manglingDetected: + clientAccurateECNState = .ecnNegotiationSuccessECTManglingDetected + case .handshakeValidationSuccess, .capable: + clientAccurateECNState = .ecnNegotiationSuccess + case .blackholed: + clientAccurateECNState = .ecnNegotiationBlackholed + case .unsupported: + clientAccurateECNState = .ecnNotAvailable + default: + break + } + } + } + var protocolEstablishmentReport = ProtocolEstablishmentReport( + handshakeMilliseconds: handshakeDuration, + handshakeRTTMilliseconds: handshakeRTT, + protocolIdentifier: QUICConnectionProtocol.identifier, + clientAccurateECNState: clientAccurateECNState + ) + protocolEstablishmentReport.l4sEnabled = l4sEnabled + protocolEstablishmentReport.quicMigrationSupported = migrationSupported + protocolEstablishmentReport.quicStatelessResetReceived = (self.stats[.statelessResetReceived] > 0) + protocolEstablishmentReport.quicStatelessResetDuringPathProbe = (self.stats[.statelessResetDuringPathProbe] > 0) + + return protocolEstablishmentReport + } + func setupNewOutboundStream( _ stream: QUICStreamInstance, with protocolOptions: QUICStreamProtocol.Options @@ -3867,7 +3923,11 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, let packetToken = packet.tag, let statelessToken = QUICStatelessResetToken(packetToken) { + self.stats.increment(.statelessResetReceived) if remoteCIDs.find(statelessResetToken: statelessToken) != nil { + if migration.probingPathCount(self) > 0 { + self.stats.increment(.statelessResetDuringPathProbe) + } log.info("received valid stateless reset token") errorToReport = NetworkError.posix(ECONNRESET) close() @@ -4055,6 +4115,8 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, } else if !isServer, self.currentPath?.dcid?.length == 0 { log.info("disabling migration due to zero-length peer CID") migration.disableActiveMigration() + } else { + migrationSupported = true } state.change(to: .connected, logIDString: logPrefixer.logIDString) @@ -4070,12 +4132,13 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, } } - let establishmentTime = handshakeStartTime.duration(to: .now) + handshakeDuration = handshakeStartTime.duration(to: .now) var currentRTT: NetworkDuration = .milliseconds(0) if let currentPath = currentPath { currentRTT = currentPath.rtt.smoothedRTT + handshakeRTT = currentRTT } - log.notice("QUIC connection established in \(establishmentTime), RTT \(currentRTT)") + log.notice("QUIC connection established in \(handshakeDuration), RTT \(currentRTT)") self.keyState = .phase0 diff --git a/Sources/SwiftNetwork/QUIC/QUICPath.swift b/Sources/SwiftNetwork/QUIC/QUICPath.swift index da66fc1..5219295 100644 --- a/Sources/SwiftNetwork/QUIC/QUICPath.swift +++ b/Sources/SwiftNetwork/QUIC/QUICPath.swift @@ -757,6 +757,11 @@ extension QUICPath { #endif } } + + @inline(__always) + func congestionControlFilloutDataTransferSnapshot(snapshot: inout DataTransferSnapshot) { + congestionControl?.filloutDataTransferSnapshot(dataTransferSnapshot: &snapshot) + } } #endif diff --git a/Sources/SwiftNetwork/QUIC/Statistics.swift b/Sources/SwiftNetwork/QUIC/Statistics.swift index 88c458d..952dacb 100644 --- a/Sources/SwiftNetwork/QUIC/Statistics.swift +++ b/Sources/SwiftNetwork/QUIC/Statistics.swift @@ -136,11 +136,14 @@ enum QUICStatistic: Int, CaseIterable { case rxNewToken case txDepartureTimestamp + + case statelessResetReceived + case statelessResetDuringPathProbe } struct Statistics: ~Copyable { - private var statisticsArray: [96 of Int] + private var statisticsArray: [98 of Int] init() { statisticsArray = .init(repeating: 0) diff --git a/Tests/SwiftNetworkTests/QUICTestHarness.swift b/Tests/SwiftNetworkTests/QUICTestHarness.swift index 2b71a07..08433c5 100644 --- a/Tests/SwiftNetworkTests/QUICTestHarness.swift +++ b/Tests/SwiftNetworkTests/QUICTestHarness.swift @@ -906,6 +906,7 @@ final class QUICTestHarness { clientOptions: ProtocolOptions = QUICProtocol.options(), serverOptions: ProtocolOptions = QUICProtocol.options(), sendMaxStreamUpdate: Bool = false, + validateMetrics: Bool = false, extraServerCIDs: [(QUICConnectionID, QUICStatelessResetToken)] = .init(), afterHandshake: ((QUICTestHarness) -> Void)? = nil, // Block to run after handshake is complete afterData: ((QUICTestHarness) -> Void)? = nil, // Block to run after handshake is complete @@ -1096,6 +1097,38 @@ final class QUICTestHarness { afterData(self) } + if validateMetrics { + if let serverHarness = state?.serverHarness, let clientHarness = state?.clientHarness { + var clientReports: NetworkMetrics? + var serverReports: NetworkMetrics? + let snapshotExpectation = XCTestExpectation(description: "Wait for QUIC connection to receive metrics") + context.async { + clientReports = clientHarness.getMetrics(type: .dataTransferSnapshot) + serverReports = serverHarness.getMetrics(type: .dataTransferSnapshot) + XCTAssertNotNil(clientReports) + XCTAssertNotNil(serverReports) + snapshotExpectation.fulfill() + } + wait(for: [snapshotExpectation], timeout: 2.0) + // Now validate the protocol establishment report + clientReports = nil + serverReports = nil + let protocolEstablishmentReportExpectation = XCTestExpectation( + description: "Wait for QUIC connection to receive metrics" + ) + context.async { + clientReports = clientHarness.getMetrics(type: .protocolEstablishmentReports) + serverReports = serverHarness.getMetrics(type: .protocolEstablishmentReports) + XCTAssertNotNil(clientReports) + XCTAssertNotNil(serverReports) + protocolEstablishmentReportExpectation.fulfill() + } + wait(for: [protocolEstablishmentReportExpectation], timeout: 2.0) + } else { + XCTFail("There should be saved server and client harnesses") + } + } + // If applicationError is present, act upon that here if let applicationError, let applicationErrorReason { if let serverHarness = state?.serverHarness, let clientHarness = state?.clientHarness { diff --git a/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift b/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift index 056a78f..5249209 100644 --- a/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift +++ b/Tests/SwiftNetworkTests/SwiftNetworkQUICHarnessTests.swift @@ -398,6 +398,15 @@ final class SwiftNetworkQUICHarnessTests: NetTestCase { QUICTestHarness().runQUICTest(streamCount: 4, blockSize: 10240, blockCount: 4) } + func testQUICEcho40KiBMultistreamWithMetrics() { + QUICTestHarness().runQUICTest( + streamCount: 8, + blockSize: 10240, + blockCount: 8, + validateMetrics: true + ) + } + func testQUIC13AutomaticStreams() { let serverOptions = QUICProtocol.options() serverOptions.connectionOptions.initialMaxStreamsBidirectional = 5 From 296dbb01ff13db9821e815baae7f126fc1b7377e Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Thu, 11 Jun 2026 05:08:09 -0700 Subject: [PATCH 2/3] agnosticdev/RTTMetrics: Switched to RequestedNetworkMetrics --- Sources/SwiftNetwork/Connection/NetworkMetrics.swift | 2 +- Sources/SwiftNetwork/Protocols/BottomProtocol.swift | 2 +- Sources/SwiftNetwork/Protocols/HarnessProtocols.swift | 2 +- Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift | 6 +++--- Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift | 2 +- .../SwiftNetwork/Protocols/ProtocolControlHandlers.swift | 4 ++-- Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift | 2 +- Sources/SwiftNetwork/QUIC/Crypto.swift | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftNetwork/Connection/NetworkMetrics.swift b/Sources/SwiftNetwork/Connection/NetworkMetrics.swift index e954afb..2e486bd 100644 --- a/Sources/SwiftNetwork/Connection/NetworkMetrics.swift +++ b/Sources/SwiftNetwork/Connection/NetworkMetrics.swift @@ -14,7 +14,7 @@ @_spi(ProtocolProvider) @available(Network 0.1.0, *) -public enum NetworkMetricsType { +public enum RequestedNetworkMetrics { case protocolEstablishmentReports case dataTransferSnapshot } diff --git a/Sources/SwiftNetwork/Protocols/BottomProtocol.swift b/Sources/SwiftNetwork/Protocols/BottomProtocol.swift index 3f8b2ba..064442b 100644 --- a/Sources/SwiftNetwork/Protocols/BottomProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/BottomProtocol.swift @@ -251,7 +251,7 @@ extension BottomProtocolHandler where Self: ~Copyable { return nil } - public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { do { try validate(upper: from, #function) } catch { return nil } switch type { case .protocolEstablishmentReports: diff --git a/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift b/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift index a545124..0d1b840 100644 --- a/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift +++ b/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift @@ -927,7 +927,7 @@ public class NewFlowHarness NetworkMetrics? { + final public func getMetrics(type: RequestedNetworkMetrics) -> NetworkMetrics? { fromExternal { lower.invokeGetMetrics(reference, type: type) } diff --git a/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift b/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift index 1c02817..dcff65f 100644 --- a/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift @@ -296,7 +296,7 @@ extension ManyToManyProtocolHandler { public func updateDataTransferSnapshot(flow: MultiplexedFlowIdentifier, _ snapshot: inout DataTransferSnapshot) {} public var protocolEstablishmentReport: ProtocolEstablishmentReport? { nil } - public func getMetrics(flow: MultiplexedFlowIdentifier, type: NetworkMetricsType) -> NetworkMetrics? { + public func getMetrics(flow: MultiplexedFlowIdentifier, type: RequestedNetworkMetrics) -> NetworkMetrics? { switch type { case .protocolEstablishmentReports: guard let report = protocolEstablishmentReport else { return nil } @@ -308,7 +308,7 @@ extension ManyToManyProtocolHandler { } } - public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { getMetrics(flow: .allFlows, type: type) } @@ -1232,7 +1232,7 @@ extension MultiplexedFlow { return parentProtocol.getMetadata(flow: identifier) } - public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { do { try validate(upper: from, #function) } catch { return nil } return parentProtocol.getMetrics(flow: identifier, type: type) } diff --git a/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift b/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift index a578150..2a91bc1 100644 --- a/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift @@ -385,7 +385,7 @@ extension OneToOneProtocolHandler where Self: ~Copyable { #endif } - public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { do { try validate(upper: from, #function) } catch { return nil } let lowerMetrics = lower.invokeGetMetrics(effectiveSelfReference, type: type) switch type { diff --git a/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift b/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift index 2c7df11..2b4b5ff 100644 --- a/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift +++ b/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift @@ -142,7 +142,7 @@ public protocol LowerProtocolHandler: ~Copyable, ProtocolInstance mutating func handleApplicationEvent(_ from: ProtocolInstanceReference, event: ApplicationEvent) func getMetadata(_ from: ProtocolInstanceReference) -> ProtocolMetadata

? - func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? + func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? } extension ProtocolInstanceReference { @@ -890,7 +890,7 @@ extension ProtocolInstanceReference { } } - public func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { self.handleCallFromUpperProtocol { switch self.reference { case .none: return nil diff --git a/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift b/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift index 3e37691..2e64e3d 100644 --- a/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift +++ b/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift @@ -214,7 +214,7 @@ extension LowerProtocolLinkage { reference.getMetadata(from) } - public func invokeGetMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + public func invokeGetMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { reference.getMetrics(from, type: type) } } diff --git a/Sources/SwiftNetwork/QUIC/Crypto.swift b/Sources/SwiftNetwork/QUIC/Crypto.swift index c923a54..49b418b 100644 --- a/Sources/SwiftNetwork/QUIC/Crypto.swift +++ b/Sources/SwiftNetwork/QUIC/Crypto.swift @@ -508,7 +508,7 @@ extension QUICCrypto: OutboundStreamHandler { ) {} func getMetadata

(_ from: ProtocolInstanceReference) -> ProtocolMetadata

? where P: NetworkProtocol { nil } - func getMetrics(_ from: ProtocolInstanceReference, type: NetworkMetricsType) -> NetworkMetrics? { + func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { nil } func levelForReference(_ from: ProtocolInstanceReference) -> SwiftTLSOptions.EncryptionLevel? { From 2b87a6d69072766734aed778f0fa3b617fbe79d4 Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Thu, 11 Jun 2026 06:23:33 -0700 Subject: [PATCH 3/3] agnosticdev/RTTMetrics: moved from type: to requestedNetworkMetric: --- .../Protocols/BottomProtocol.swift | 7 +++- .../Protocols/HarnessProtocols.swift | 4 +- .../Protocols/ManyToManyProtocol.swift | 21 +++++++--- .../Protocols/OneToOneProtocol.swift | 12 ++++-- .../Protocols/ProtocolControlHandlers.swift | 41 +++++++++++++------ .../Protocols/ProtocolLinkage.swift | 7 +++- Sources/SwiftNetwork/QUIC/Crypto.swift | 5 ++- Tests/SwiftNetworkTests/QUICTestHarness.swift | 8 ++-- 8 files changed, 72 insertions(+), 33 deletions(-) diff --git a/Sources/SwiftNetwork/Protocols/BottomProtocol.swift b/Sources/SwiftNetwork/Protocols/BottomProtocol.swift index 064442b..3cbaaa8 100644 --- a/Sources/SwiftNetwork/Protocols/BottomProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/BottomProtocol.swift @@ -251,9 +251,12 @@ extension BottomProtocolHandler where Self: ~Copyable { return nil } - public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { + public func getMetrics( + _ from: ProtocolInstanceReference, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? { do { try validate(upper: from, #function) } catch { return nil } - switch type { + switch requestedNetworkMetric { case .protocolEstablishmentReports: guard let report = protocolEstablishmentReport else { return nil } return .protocolEstablishmentReports([report]) diff --git a/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift b/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift index 0d1b840..429a8c1 100644 --- a/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift +++ b/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift @@ -927,9 +927,9 @@ public class NewFlowHarness NetworkMetrics? { + final public func getMetrics(requestedNetworkMetric: RequestedNetworkMetrics) -> NetworkMetrics? { fromExternal { - lower.invokeGetMetrics(reference, type: type) + lower.invokeGetMetrics(reference, requestedNetworkMetric: requestedNetworkMetric) } } } diff --git a/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift b/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift index dcff65f..eaa1235 100644 --- a/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift @@ -296,8 +296,11 @@ extension ManyToManyProtocolHandler { public func updateDataTransferSnapshot(flow: MultiplexedFlowIdentifier, _ snapshot: inout DataTransferSnapshot) {} public var protocolEstablishmentReport: ProtocolEstablishmentReport? { nil } - public func getMetrics(flow: MultiplexedFlowIdentifier, type: RequestedNetworkMetrics) -> NetworkMetrics? { - switch type { + public func getMetrics( + flow: MultiplexedFlowIdentifier, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? { + switch requestedNetworkMetric { case .protocolEstablishmentReports: guard let report = protocolEstablishmentReport else { return nil } return .protocolEstablishmentReports([report]) @@ -308,8 +311,11 @@ extension ManyToManyProtocolHandler { } } - public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { - getMetrics(flow: .allFlows, type: type) + public func getMetrics( + _ from: ProtocolInstanceReference, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? { + getMetrics(flow: .allFlows, requestedNetworkMetric: requestedNetworkMetric) } public func handleInboundDataAvailableEvent(path: MultiplexingPathIdentifier) {} @@ -1232,9 +1238,12 @@ extension MultiplexedFlow { return parentProtocol.getMetadata(flow: identifier) } - public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { + public func getMetrics( + _ from: ProtocolInstanceReference, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? { do { try validate(upper: from, #function) } catch { return nil } - return parentProtocol.getMetrics(flow: identifier, type: type) + return parentProtocol.getMetrics(flow: identifier, requestedNetworkMetric: requestedNetworkMetric) } fileprivate func deliverConnectedEvent() { diff --git a/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift b/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift index 2a91bc1..bb49273 100644 --- a/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift @@ -385,10 +385,16 @@ extension OneToOneProtocolHandler where Self: ~Copyable { #endif } - public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { + public func getMetrics( + _ from: ProtocolInstanceReference, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? { do { try validate(upper: from, #function) } catch { return nil } - let lowerMetrics = lower.invokeGetMetrics(effectiveSelfReference, type: type) - switch type { + let lowerMetrics = lower.invokeGetMetrics( + effectiveSelfReference, + requestedNetworkMetric: requestedNetworkMetric + ) + switch requestedNetworkMetric { case .protocolEstablishmentReports: var reports = [ProtocolEstablishmentReport]() if case .protocolEstablishmentReports(let protocolEstablishmentReports) = lowerMetrics { diff --git a/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift b/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift index 2b4b5ff..0cf04e1 100644 --- a/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift +++ b/Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift @@ -142,7 +142,10 @@ public protocol LowerProtocolHandler: ~Copyable, ProtocolInstance mutating func handleApplicationEvent(_ from: ProtocolInstanceReference, event: ApplicationEvent) func getMetadata(_ from: ProtocolInstanceReference) -> ProtocolMetadata

? - func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? + func getMetrics( + _ from: ProtocolInstanceReference, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? } extension ProtocolInstanceReference { @@ -890,27 +893,39 @@ extension ProtocolInstanceReference { } } - public func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { + public func getMetrics( + _ from: ProtocolInstanceReference, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? { self.handleCallFromUpperProtocol { switch self.reference { case .none: return nil - case .udp(let index): return context.udpInstances[index].getMetrics(from, type: type) - case .ip(let index): return context.ipInstances[index].getMetrics(from, type: type) - case .tcp(let instance): return instance.getMetrics(from, type: type) - case .tls(let instance): return instance.getMetrics(from, type: type) + case .udp(let index): + return context.udpInstances[index].getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) + case .ip(let index): + return context.ipInstances[index].getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) + case .tcp(let instance): return instance.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) + case .tls(let instance): return instance.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) #if !NETWORK_NO_SWIFT_QUIC - case .quic(let instance): return instance.getMetrics(from, type: type) - case .quicStream(let instance): return instance.getMetrics(from, type: type) - case .quicDatagram(let instance): return instance.getMetrics(from, type: type) - case .quicCrypto(let instance): return instance.getMetrics(from, type: type) + case .quic(let instance): return instance.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) + case .quicStream(let instance): + return instance.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) + case .quicDatagram(let instance): + return instance.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) + case .quicCrypto(let instance): + return instance.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) #if !NETWORK_NO_TESTING_HARNESS - case .datagramLowerHarness(let instance): return instance.getMetrics(from, type: type) - case .streamLowerHarness(let instance): return instance.getMetrics(from, type: type) + case .datagramLowerHarness(let instance): + return instance.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) + case .streamLowerHarness(let instance): + return instance.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) #endif #endif #if !NETWORK_EMBEDDED case .custom(let container, let index): - return container.accessLower(at: index) { $0.getMetrics(from, type: type) } + return container.accessLower(at: index) { + $0.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) + } #endif default: fatalError("Protocol cannot accept getMetrics call") } diff --git a/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift b/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift index 2e64e3d..d2699f3 100644 --- a/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift +++ b/Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift @@ -214,8 +214,11 @@ extension LowerProtocolLinkage { reference.getMetadata(from) } - public func invokeGetMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { - reference.getMetrics(from, type: type) + public func invokeGetMetrics( + _ from: ProtocolInstanceReference, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? { + reference.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric) } } diff --git a/Sources/SwiftNetwork/QUIC/Crypto.swift b/Sources/SwiftNetwork/QUIC/Crypto.swift index 49b418b..cebbe8d 100644 --- a/Sources/SwiftNetwork/QUIC/Crypto.swift +++ b/Sources/SwiftNetwork/QUIC/Crypto.swift @@ -508,7 +508,10 @@ extension QUICCrypto: OutboundStreamHandler { ) {} func getMetadata

(_ from: ProtocolInstanceReference) -> ProtocolMetadata

? where P: NetworkProtocol { nil } - func getMetrics(_ from: ProtocolInstanceReference, type: RequestedNetworkMetrics) -> NetworkMetrics? { + func getMetrics( + _ from: ProtocolInstanceReference, + requestedNetworkMetric: RequestedNetworkMetrics + ) -> NetworkMetrics? { nil } func levelForReference(_ from: ProtocolInstanceReference) -> SwiftTLSOptions.EncryptionLevel? { diff --git a/Tests/SwiftNetworkTests/QUICTestHarness.swift b/Tests/SwiftNetworkTests/QUICTestHarness.swift index 08433c5..1d90781 100644 --- a/Tests/SwiftNetworkTests/QUICTestHarness.swift +++ b/Tests/SwiftNetworkTests/QUICTestHarness.swift @@ -1103,8 +1103,8 @@ final class QUICTestHarness { var serverReports: NetworkMetrics? let snapshotExpectation = XCTestExpectation(description: "Wait for QUIC connection to receive metrics") context.async { - clientReports = clientHarness.getMetrics(type: .dataTransferSnapshot) - serverReports = serverHarness.getMetrics(type: .dataTransferSnapshot) + clientReports = clientHarness.getMetrics(requestedNetworkMetric: .dataTransferSnapshot) + serverReports = serverHarness.getMetrics(requestedNetworkMetric: .dataTransferSnapshot) XCTAssertNotNil(clientReports) XCTAssertNotNil(serverReports) snapshotExpectation.fulfill() @@ -1117,8 +1117,8 @@ final class QUICTestHarness { description: "Wait for QUIC connection to receive metrics" ) context.async { - clientReports = clientHarness.getMetrics(type: .protocolEstablishmentReports) - serverReports = serverHarness.getMetrics(type: .protocolEstablishmentReports) + clientReports = clientHarness.getMetrics(requestedNetworkMetric: .protocolEstablishmentReports) + serverReports = serverHarness.getMetrics(requestedNetworkMetric: .protocolEstablishmentReports) XCTAssertNotNil(clientReports) XCTAssertNotNil(serverReports) protocolEstablishmentReportExpectation.fulfill()