Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Sources/SwiftNetwork/Connection/DataTransferSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,4 +50,3 @@ struct DataTransferSnapshot: Equatable {
var migrationToOtherCount: UInt64 = 0
var migrationToFallbackCount: UInt64 = 0
}
#endif
27 changes: 27 additions & 0 deletions Sources/SwiftNetwork/Connection/NetworkMetrics.swift
Original file line number Diff line number Diff line change
@@ -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 RequestedNetworkMetrics {
case protocolEstablishmentReports
case dataTransferSnapshot
}

@_spi(ProtocolProvider)
@available(Network 0.1.0, *)
public enum NetworkMetrics {
case protocolEstablishmentReports([ProtocolEstablishmentReport])
case dataTransferSnapshot(DataTransferSnapshot)
}
104 changes: 104 additions & 0 deletions Sources/SwiftNetwork/Connection/ProtocolEstablishmentReport.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
26 changes: 26 additions & 0 deletions Sources/SwiftNetwork/Protocols/BottomProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -245,6 +251,22 @@ extension BottomProtocolHandler where Self: ~Copyable {
return nil
}

public func getMetrics(
_ from: ProtocolInstanceReference,
requestedNetworkMetric: RequestedNetworkMetrics
) -> NetworkMetrics? {
do { try validate(upper: from, #function) } catch { return nil }
switch requestedNetworkMetric {
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<T>(from parameters: Parameters) -> ProtocolOptions<T>? {
parameters.protocolOptions(for: self.reference)
Expand Down Expand Up @@ -279,6 +301,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 {
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftNetwork/Protocols/HarnessProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,12 @@ public class NewFlowHarness<LinkageType: InboundFlowLinkage, HarnessType: UpperH
return metadata
}
}

final public func getMetrics(requestedNetworkMetric: RequestedNetworkMetrics) -> NetworkMetrics? {
fromExternal {
lower.invokeGetMetrics(reference, requestedNetworkMetric: requestedNetworkMetric)
}
}
}

@_spi(ProtocolProvider)
Expand Down
35 changes: 35 additions & 0 deletions Sources/SwiftNetwork/Protocols/ManyToManyProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public protocol ManyToManyProtocolHandler: ListenerHandler, LoggableProtocol {
func teardown(flow: MultiplexedFlowIdentifier)
func handleApplicationEvent(flow: MultiplexedFlowIdentifier, event: ApplicationEvent) -> HandleNetworkEventResult
func getMetadata<P>(flow: MultiplexedFlowIdentifier) -> ProtocolMetadata<P>? where P: NetworkProtocol
func updateDataTransferSnapshot(flow: MultiplexedFlowIdentifier, _ snapshot: inout DataTransferSnapshot)
var protocolEstablishmentReport: ProtocolEstablishmentReport? { get }

// MARK: Per-path events to implement
func handleConnectedEvent(path: MultiplexingPathIdentifier)
Expand Down Expand Up @@ -291,6 +293,31 @@ extension ManyToManyProtocolHandler {
getMetadata(flow: .allFlows)
}

public func updateDataTransferSnapshot(flow: MultiplexedFlowIdentifier, _ snapshot: inout DataTransferSnapshot) {}
public var protocolEstablishmentReport: ProtocolEstablishmentReport? { nil }

public func getMetrics(
flow: MultiplexedFlowIdentifier,
requestedNetworkMetric: RequestedNetworkMetrics
) -> NetworkMetrics? {
switch requestedNetworkMetric {
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,
requestedNetworkMetric: RequestedNetworkMetrics
) -> NetworkMetrics? {
getMetrics(flow: .allFlows, requestedNetworkMetric: requestedNetworkMetric)
}

public func handleInboundDataAvailableEvent(path: MultiplexingPathIdentifier) {}
public func handleOutboundRoomAvailableEvent(path: MultiplexingPathIdentifier) {}

Expand Down Expand Up @@ -1211,6 +1238,14 @@ extension MultiplexedFlow {
return parentProtocol.getMetadata(flow: identifier)
}

public func getMetrics(
_ from: ProtocolInstanceReference,
requestedNetworkMetric: RequestedNetworkMetrics
) -> NetworkMetrics? {
do { try validate(upper: from, #function) } catch { return nil }
return parentProtocol.getMetrics(flow: identifier, requestedNetworkMetric: requestedNetworkMetric)
}

fileprivate func deliverConnectedEvent() {
if upper.isDetached {
// Enqueue pending event instead of delivering immediately.
Expand Down
40 changes: 40 additions & 0 deletions Sources/SwiftNetwork/Protocols/OneToOneProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -379,6 +385,36 @@ extension OneToOneProtocolHandler where Self: ~Copyable {
#endif
}

public func getMetrics(
_ from: ProtocolInstanceReference,
requestedNetworkMetric: RequestedNetworkMetrics
) -> NetworkMetrics? {
do { try validate(upper: from, #function) } catch { return nil }
let lowerMetrics = lower.invokeGetMetrics(
effectiveSelfReference,
requestedNetworkMetric: requestedNetworkMetric
)
switch requestedNetworkMetric {
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<SwiftTLSProtocol>? {
parameters.tlsOptions(for: self.reference)
}
Expand Down Expand Up @@ -429,6 +465,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 {
Expand Down
43 changes: 43 additions & 0 deletions Sources/SwiftNetwork/Protocols/ProtocolControlHandlers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ public protocol LowerProtocolHandler<UpperProtocol>: ~Copyable, ProtocolInstance
mutating func handleApplicationEvent(_ from: ProtocolInstanceReference, event: ApplicationEvent)

func getMetadata<P: NetworkProtocol>(_ from: ProtocolInstanceReference) -> ProtocolMetadata<P>?
func getMetrics(
_ from: ProtocolInstanceReference,
requestedNetworkMetric: RequestedNetworkMetrics
) -> NetworkMetrics?
}

extension ProtocolInstanceReference {
Expand Down Expand Up @@ -888,4 +892,43 @@ extension ProtocolInstanceReference {
}
}
}

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, 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, 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, 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, requestedNetworkMetric: requestedNetworkMetric)
}
#endif
default: fatalError("Protocol cannot accept getMetrics call")
}
}
}
}
7 changes: 7 additions & 0 deletions Sources/SwiftNetwork/Protocols/ProtocolLinkage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,13 @@ extension LowerProtocolLinkage {
public func invokeGetMetadata<P: NetworkProtocol>(_ from: ProtocolInstanceReference) -> ProtocolMetadata<P>? {
reference.getMetadata(from)
}

public func invokeGetMetrics(
_ from: ProtocolInstanceReference,
requestedNetworkMetric: RequestedNetworkMetrics
) -> NetworkMetrics? {
reference.getMetrics(from, requestedNetworkMetric: requestedNetworkMetric)
}
}

@_spi(ProtocolProvider)
Expand Down
Loading
Loading