From f9c9864a3f27f81d467a4c993187cec0f7196dda Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Fri, 12 Jun 2026 12:06:15 -0700 Subject: [PATCH] agnostivdev/StreamMetadata: Make QUICStreamMetadata a non-copyable struct --- .../EndpointFlow/EndpointFlowProtocols.swift | 4 +- Sources/SwiftNetwork/Protocols/Frame.swift | 13 ++-- .../Protocols/HarnessProtocols.swift | 4 +- .../SwiftNetwork/Protocols/IPProtocol.swift | 6 +- .../Protocols/ProtocolDefinition.swift | 2 +- .../Protocols/ProtocolMetadata.swift | 57 +++++++++------ .../Protocols/QUICStreamProtocol.swift | 69 +++++++++++++++---- .../SwiftNetwork/QUIC/QUICConnection.swift | 29 ++++---- Sources/SwiftNetwork/QUIC/QUICStream.swift | 6 +- Sources/SwiftNetwork/QUIC/QUICStreamID.swift | 6 +- 10 files changed, 129 insertions(+), 67 deletions(-) diff --git a/Sources/SwiftNetwork/EndpointFlow/EndpointFlowProtocols.swift b/Sources/SwiftNetwork/EndpointFlow/EndpointFlowProtocols.swift index 0e64ac1..76d6e93 100644 --- a/Sources/SwiftNetwork/EndpointFlow/EndpointFlowProtocols.swift +++ b/Sources/SwiftNetwork/EndpointFlow/EndpointFlowProtocols.swift @@ -241,8 +241,8 @@ class EndpointFlowProtocol: ProtocolInstanceCon public func setApplicationError(_ applicationError: UInt64, applicationErrorReason: String) { if let metadata: ProtocolMetadata = self.getMetadata() { - metadata.perProtocolMetadata?.quicConnectionMetadata?.applicationError = applicationError - metadata.perProtocolMetadata?.quicConnectionMetadata?.applicationErrorReason = applicationErrorReason + metadata.connectionMetadata?.applicationError = applicationError + metadata.connectionMetadata?.applicationErrorReason = applicationErrorReason } } } diff --git a/Sources/SwiftNetwork/Protocols/Frame.swift b/Sources/SwiftNetwork/Protocols/Frame.swift index 9dbec5e..2cbe4fc 100644 --- a/Sources/SwiftNetwork/Protocols/Frame.swift +++ b/Sources/SwiftNetwork/Protocols/Frame.swift @@ -708,11 +708,14 @@ public struct Frame: ~Copyable { #if !NETWORK_EMBEDDED public mutating func setMetadata(metadata: AbstractProtocolMetadata?, isInput: Bool, isComplete: Bool) { - if let metadata = metadata as? ProtocolMetadata, let ipMetadata = metadata.perProtocolMetadata { - ecnFlag = ipMetadata.ecnFlag - dscpValue = ipMetadata.dscpValue - serviceClass = ipMetadata.serviceClass - fragmentationOverride = ipMetadata.fragmentationEnabled + if let metadata = metadata as? ProtocolMetadata { + metadata.withPerProtocolMetadata { ipMetadata in + guard let ipMetadata else { return } + ecnFlag = ipMetadata.ecnFlag + dscpValue = ipMetadata.dscpValue + serviceClass = ipMetadata.serviceClass + fragmentationOverride = ipMetadata.fragmentationEnabled + } } if protocolMetadatas.isEmpty { diff --git a/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift b/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift index 429a8c1..3e83872 100644 --- a/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift +++ b/Sources/SwiftNetwork/Protocols/HarnessProtocols.swift @@ -238,8 +238,8 @@ public class UpperHarness: UpperHarnessProtocol public func setApplicationError(_ applicationError: UInt64, applicationErrorReason: String) { if let metadata: ProtocolMetadata = self.getMetadata() { - metadata.perProtocolMetadata?.quicConnectionMetadata?.applicationError = applicationError - metadata.perProtocolMetadata?.quicConnectionMetadata?.applicationErrorReason = applicationErrorReason + metadata.connectionMetadata?.applicationError = applicationError + metadata.connectionMetadata?.applicationErrorReason = applicationErrorReason } } diff --git a/Sources/SwiftNetwork/Protocols/IPProtocol.swift b/Sources/SwiftNetwork/Protocols/IPProtocol.swift index 4a976fb..cca10a1 100644 --- a/Sources/SwiftNetwork/Protocols/IPProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/IPProtocol.swift @@ -1060,8 +1060,10 @@ public struct IPProtocol: NetworkProtocol { #if !NETWORK_EMBEDDED internal static func _staticMetadata(ecnFlag: ECN) -> ProtocolMetadata { let metadata = IPProtocol.definition.protocolMetadata() - metadata.perProtocolMetadata?.ecnFlag = ecnFlag - metadata.perProtocolMetadata?.isStatic = true + metadata.mutablePerProtocolMetadata { ipMetadata in + ipMetadata?.ecnFlag = ecnFlag + ipMetadata?.isStatic = true + } return metadata } diff --git a/Sources/SwiftNetwork/Protocols/ProtocolDefinition.swift b/Sources/SwiftNetwork/Protocols/ProtocolDefinition.swift index 2666414..5c923d8 100644 --- a/Sources/SwiftNetwork/Protocols/ProtocolDefinition.swift +++ b/Sources/SwiftNetwork/Protocols/ProtocolDefinition.swift @@ -207,7 +207,7 @@ public struct ProtocolIdentifier: Hashable, Sendable { @available(Network 0.1.0, *) public protocol NetworkProtocol: Sendable { associatedtype Options: PerProtocolOptions - associatedtype Metadata: PerProtocolMetadata + associatedtype Metadata: PerProtocolMetadata & ~Copyable init() func newPerProtocolOptions() -> Options? diff --git a/Sources/SwiftNetwork/Protocols/ProtocolMetadata.swift b/Sources/SwiftNetwork/Protocols/ProtocolMetadata.swift index ad608f3..96a8ece 100644 --- a/Sources/SwiftNetwork/Protocols/ProtocolMetadata.swift +++ b/Sources/SwiftNetwork/Protocols/ProtocolMetadata.swift @@ -18,8 +18,8 @@ internal import Synchronization @_spi(ProtocolProvider) @available(Network 0.1.0, *) -public protocol PerProtocolMetadata: Equatable { - func isEqual(to: Self, for: ProtocolCompareMode) -> Bool +public protocol PerProtocolMetadata: ~Copyable, Equatable { + func isEqual(to: borrowing Self, for: ProtocolCompareMode) -> Bool #if NETWORK_PRIVATE var cProtocolDefinition: nw_protocol_definition_t? { get } #endif @@ -62,24 +62,31 @@ public class AbstractProtocolMetadata: PerProtocolMetadata { public final class ProtocolMetadata: AbstractProtocolMetadata, @unchecked Sendable { private let lock = NetworkMutex(()) private var _perProtocolMetadata: P.Metadata? = nil - public var perProtocolMetadata: P.Metadata? { - get { - lock.withLock { _ in - _perProtocolMetadata - } - } - set { - lock.withLock { _ in - _perProtocolMetadata = newValue - } - } + + public func withPerProtocolMetadata(_ body: (borrowing P.Metadata?) -> R) -> R { + lock.withLock { _ in body(_perProtocolMetadata) } + } + + public func mutablePerProtocolMetadata(_ body: (inout P.Metadata?) -> R) -> R { + lock.withLock { _ in body(&_perProtocolMetadata) } } #if NETWORK_PRIVATE - public override var cProtocolDefinition: nw_protocol_definition_t? { perProtocolMetadata?.cProtocolDefinition } + public override var cProtocolDefinition: nw_protocol_definition_t? { + withPerProtocolMetadata { metadata in + switch metadata { + case .none: return nil + case .some(let protocolMetadata): return protocolMetadata.cProtocolDefinition + } + } + } #endif - init(protocolIdentifier: ProtocolIdentifier, perProtocolMetadata: P.Metadata?, messageIdentifier: SystemUUID) { + init( + protocolIdentifier: ProtocolIdentifier, + perProtocolMetadata: consuming P.Metadata?, + messageIdentifier: SystemUUID + ) { _perProtocolMetadata = perProtocolMetadata super.init(protocolIdentifier: protocolIdentifier, messageIdentifier: messageIdentifier) } @@ -88,12 +95,22 @@ public final class ProtocolMetadata: AbstractProtocolMetadat guard self.protocolIdentifier == other.protocolIdentifier else { return false } - if let lh = self.perProtocolMetadata, let rh = other.perProtocolMetadata { - return lh.isEqual(to: rh, for: compareMode) - } else if self.perProtocolMetadata == nil, other.perProtocolMetadata == nil { - return true + return withPerProtocolMetadata { lhMetadata in + other.withPerProtocolMetadata { rhMetadata in + switch lhMetadata { + case .some(let lhVal): + switch rhMetadata { + case .some(let rhVal): return lhVal.isEqual(to: rhVal, for: compareMode) + case .none: return false + } + case .none: + switch rhMetadata { + case .some: return false + case .none: return true + } + } + } } - return false } public override func isEqual(to other: AbstractProtocolMetadata, for compareMode: ProtocolCompareMode) -> Bool { diff --git a/Sources/SwiftNetwork/Protocols/QUICStreamProtocol.swift b/Sources/SwiftNetwork/Protocols/QUICStreamProtocol.swift index ded9b05..9d64aa1 100644 --- a/Sources/SwiftNetwork/Protocols/QUICStreamProtocol.swift +++ b/Sources/SwiftNetwork/Protocols/QUICStreamProtocol.swift @@ -269,7 +269,7 @@ public struct QUICStreamProtocol: NetworkProtocol { // QUICStreamMetadata - - public final class QUICStreamMetadata: PerProtocolMetadata { + public struct QUICStreamMetadata: ~Copyable, PerProtocolMetadata { var streamID: UInt64 = 0 @@ -295,15 +295,27 @@ public struct QUICStreamProtocol: NetworkProtocol { public init() {} - public func isEqual(to other: QUICStreamMetadata, for: ProtocolCompareMode) -> Bool { + public func isEqual(to other: borrowing QUICStreamMetadata, for: ProtocolCompareMode) -> Bool { true } func deepCopy() -> Self { - self + var explicitCopy = QUICStreamMetadata() + explicitCopy.streamID = self.streamID + explicitCopy.datagramFlowID = self.datagramFlowID + explicitCopy.applicationError = self.applicationError + explicitCopy.reliableSize = self.reliableSize + #if !NETWORK_EMBEDDED + explicitCopy.setApplicationErrorHandler = self.setApplicationErrorHandler + #endif + explicitCopy.quicConnectionMetadata = self.quicConnectionMetadata + explicitCopy.usableDatagramFrameSize = self.usableDatagramFrameSize + explicitCopy.streamType = self.streamType + explicitCopy.isDatagramFlow = self.isDatagramFlow + return explicitCopy } - static public func == (lhs: QUICStreamMetadata, rhs: QUICStreamMetadata) -> Bool { + static public func == (lhs: borrowing QUICStreamMetadata, rhs: borrowing QUICStreamMetadata) -> Bool { if lhs.streamID == rhs.streamID && lhs.datagramFlowID == rhs.datagramFlowID && lhs.applicationError == rhs.applicationError && lhs.quicConnectionMetadata == rhs.quicConnectionMetadata @@ -329,7 +341,7 @@ public struct QUICStreamProtocol: NetworkProtocol { } } - func setConnectionMetadata(connectionMetadata: QUICConnectionProtocol.QUICConnectionMetadata) { + mutating func setConnectionMetadata(connectionMetadata: QUICConnectionProtocol.QUICConnectionMetadata) { mutex.withLock { _ in self.quicConnectionMetadata = connectionMetadata } @@ -344,7 +356,7 @@ public struct QUICStreamProtocol: NetworkProtocol { } } - func setApplicationError(handler: @escaping QUICMetadataSetterHandler) { + mutating func setApplicationError(handler: @escaping QUICMetadataSetterHandler) { mutex.withLock { _ in self.setApplicationErrorHandler = handler } @@ -413,16 +425,43 @@ extension ProtocolOptions { } extension ProtocolMetadata { - public var streamID: UInt64? { perProtocolMetadata?.streamID } - public var datagramFlowID: UInt64? { perProtocolMetadata?.datagramFlowID } + public internal(set) var streamID: UInt64? { + get { mutablePerProtocolMetadata { $0?.streamID } } + set { + mutablePerProtocolMetadata { + guard let newValue else { return } + $0?.streamID = newValue + } + } + } public var applicationError: UInt64? { - get { perProtocolMetadata?.applicationError } - set { perProtocolMetadata?.applicationError = newValue } + get { mutablePerProtocolMetadata { $0?.applicationError } } + set { mutablePerProtocolMetadata { $0?.applicationError = newValue } } + } + public var isBidirectional: Bool { + !isDatagram && (mutablePerProtocolMetadata { $0?.streamType == .bidirectional }) + } + public var isUnidirectional: Bool { + !isDatagram && (mutablePerProtocolMetadata { $0?.streamType == .unidirectional }) + } + public internal(set) var connectionMetadata: QUICConnectionProtocol.QUICConnectionMetadata? { + get { mutablePerProtocolMetadata { $0?.quicConnectionMetadata } } + set { mutablePerProtocolMetadata { $0?.quicConnectionMetadata = newValue } } + } + public internal(set) var streamType: QUICStreamType { + get { mutablePerProtocolMetadata { $0?.streamType ?? .bidirectional } } + set { mutablePerProtocolMetadata { $0?.streamType = newValue } } + } + public internal(set) var datagramFlowID: UInt64? { + get { mutablePerProtocolMetadata { $0?.datagramFlowID } } + set { mutablePerProtocolMetadata { $0?.datagramFlowID = newValue } } + } + public internal(set) var usableDatagramFrameSize: UInt16 { + get { mutablePerProtocolMetadata { $0?.usableDatagramFrameSize ?? 0 } } + set { mutablePerProtocolMetadata { $0?.usableDatagramFrameSize = newValue } } } - public var isDatagram: Bool { perProtocolMetadata?.isDatagramFlow ?? false } - public var isBidirectional: Bool { !isDatagram && perProtocolMetadata?.streamType == .bidirectional } - public var isUnidirectional: Bool { !isDatagram && perProtocolMetadata?.streamType == .unidirectional } - public var connectionMetadata: QUICConnectionProtocol.QUICConnectionMetadata? { - perProtocolMetadata?.quicConnectionMetadata + public internal(set) var isDatagram: Bool { + get { mutablePerProtocolMetadata { $0?.isDatagramFlow ?? false } } + set { mutablePerProtocolMetadata { $0?.isDatagramFlow = newValue } } } } diff --git a/Sources/SwiftNetwork/QUIC/QUICConnection.swift b/Sources/SwiftNetwork/QUIC/QUICConnection.swift index 433b836..dc5a260 100644 --- a/Sources/SwiftNetwork/QUIC/QUICConnection.swift +++ b/Sources/SwiftNetwork/QUIC/QUICConnection.swift @@ -2471,18 +2471,18 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, if case .allFlows = flowID { let metadata = QUICProtocol.metadata() - metadata.perProtocolMetadata?.quicConnectionMetadata = self.connectionMetadata + metadata.connectionMetadata = self.connectionMetadata return metadata as? ProtocolMetadata

} if let datagramFlow = secondaryFlow(for: flowID) { let metadata = QUICProtocol.metadata() - metadata.perProtocolMetadata?.datagramFlowID = datagramFlow.flowID - metadata.perProtocolMetadata?.usableDatagramFrameSize = UInt16( + metadata.datagramFlowID = datagramFlow.flowID + metadata.usableDatagramFrameSize = UInt16( datagramFlow.usableDatagramSize ) - metadata.perProtocolMetadata?.isDatagramFlow = true - metadata.perProtocolMetadata?.quicConnectionMetadata = self.connectionMetadata + metadata.isDatagram = true + metadata.connectionMetadata = self.connectionMetadata return metadata as? ProtocolMetadata

} @@ -2493,11 +2493,11 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, } let metadata = QUICProtocol.metadata() - metadata.perProtocolMetadata = stream.streamMetadata - metadata.perProtocolMetadata?.quicConnectionMetadata = self.connectionMetadata - metadata.perProtocolMetadata?.streamID = streamID.value + //metadata.perProtocolMetadata = stream.streamMetadata + metadata.connectionMetadata = self.connectionMetadata + metadata.streamID = streamID.value if let streamType = stream.streamType { - metadata.perProtocolMetadata?.streamType = streamType + metadata.streamType = streamType } return metadata as? ProtocolMetadata

} @@ -2590,7 +2590,7 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, streamID = nil } - stream.streamMetadata.quicConnectionMetadata = self.connectionMetadata + stream.streamMetadata.connectionMetadata = self.connectionMetadata stream.setup(streamID: streamID, logPrefixer: logPrefixer) if let remoteTransportParameters { @@ -5376,7 +5376,7 @@ extension QUICConnection { // create all the missing streams and flows. All will be left invalid until payload or application // triggers a transition to active let newStream = QUICStreamInstance(parent: self, inbound: true) - newStream.streamMetadata.quicConnectionMetadata = self.connectionMetadata + newStream.streamMetadata.connectionMetadata = self.connectionMetadata let newFlowIdentifier = newStream.identifier multiplexedFlows[newFlowIdentifier] = newStream @@ -5388,12 +5388,7 @@ extension QUICConnection { ) newStream.unidirectional = newStreamID.isUnidirectional - let abstractMetadata: AbstractProtocolMetadata = ProtocolMetadata( - protocolIdentifier: QUICStreamProtocol.identifier, - perProtocolMetadata: newStream.streamMetadata, - messageIdentifier: SystemUUID(insecure: true) - ) - deliverNewInboundFlowEvent(newStream.reference, flowMetadata: abstractMetadata) + deliverNewInboundFlowEvent(newStream.reference, flowMetadata: newStream.streamMetadata) log.debug( "set stream \(newStreamID.description) for flow \(newFlowIdentifier.debugDescription)" diff --git a/Sources/SwiftNetwork/QUIC/QUICStream.swift b/Sources/SwiftNetwork/QUIC/QUICStream.swift index ee5d45c..9773564 100644 --- a/Sources/SwiftNetwork/QUIC/QUICStream.swift +++ b/Sources/SwiftNetwork/QUIC/QUICStream.swift @@ -445,7 +445,11 @@ public final class QUICStreamInstance: MultiplexedStreamFlow, { private(set) var streamID: QUICStreamID? var logPrefix: String = "" - var streamMetadata = QUICStreamProtocol.QUICStreamMetadata() + var streamMetadata = ProtocolMetadata( + protocolIdentifier: QUICStreamProtocol.identifier, + perProtocolMetadata: QUICStreamProtocol.QUICStreamMetadata(), + messageIdentifier: SystemUUID(insecure: true) + ) @_optimize(speed) override public var reference: ProtocolInstanceReference { diff --git a/Sources/SwiftNetwork/QUIC/QUICStreamID.swift b/Sources/SwiftNetwork/QUIC/QUICStreamID.swift index 2076605..ad5c95f 100644 --- a/Sources/SwiftNetwork/QUIC/QUICStreamID.swift +++ b/Sources/SwiftNetwork/QUIC/QUICStreamID.swift @@ -19,11 +19,13 @@ internal import Logging internal import os #endif -enum QUICStreamType: CustomStringConvertible { +@_spi(Essentials) +@available(Network 0.1.0, *) +public enum QUICStreamType: CustomStringConvertible { case bidirectional case unidirectional - var description: String { + public var description: String { switch self { case .bidirectional: return "bidirectional" case .unidirectional: return "unidirectional"