From ca16971e4b1432c15c578bd439ffec91def3947f Mon Sep 17 00:00:00 2001 From: Tommy Pauly Date: Thu, 1 Oct 2026 15:59:00 -0700 Subject: [PATCH] Move fields from Endpoint class to EndpointCommon struct --- .../Endpoint/AddressEndpoint.swift | 5 +- .../Endpoint/BonjourEndpoint.swift | 2 +- Sources/SwiftNetwork/Endpoint/Endpoint.swift | 112 ++++++++--------- .../Endpoint/EndpointCommon.swift | 115 ++++++++++++++++-- 4 files changed, 167 insertions(+), 67 deletions(-) diff --git a/Sources/SwiftNetwork/Endpoint/AddressEndpoint.swift b/Sources/SwiftNetwork/Endpoint/AddressEndpoint.swift index edd5556a..1ef308c9 100644 --- a/Sources/SwiftNetwork/Endpoint/AddressEndpoint.swift +++ b/Sources/SwiftNetwork/Endpoint/AddressEndpoint.swift @@ -54,7 +54,6 @@ public struct AddressEndpoint: EndpointProtocol, EndpointCommonProtocol, Sendabl public var type: AddressEndpointType public var priority: UInt16 = 0 public var weight: UInt16 = 0 - public var ethernetAddress: EthernetAddress? var originalFd: Int32? = nil var scope: UInt32 = 0 @@ -291,7 +290,7 @@ public struct AddressEndpoint: EndpointProtocol, EndpointCommonProtocol, Sendabl if !matchInterface { return true } - guard let interface else { + guard let interface = self.interface else { return false } return interface.index == interfaceIndex @@ -327,7 +326,7 @@ public struct AddressEndpoint: EndpointProtocol, EndpointCommonProtocol, Sendabl if !matchInterface { return true } - guard let interface else { + guard let interface = self.interface else { return false } return interface.index == interfaceIndex diff --git a/Sources/SwiftNetwork/Endpoint/BonjourEndpoint.swift b/Sources/SwiftNetwork/Endpoint/BonjourEndpoint.swift index 0d8ccb8a..bf9c5946 100644 --- a/Sources/SwiftNetwork/Endpoint/BonjourEndpoint.swift +++ b/Sources/SwiftNetwork/Endpoint/BonjourEndpoint.swift @@ -174,7 +174,7 @@ public struct BonjourEndpoint: EndpointProtocol, EndpointCommonProtocol, Sendabl public func descriptionInternal(redacted: Bool) -> String { var suffix = "" - if let interface { + if let interface = self.interface { suffix = "@" + interface.name } if redacted { diff --git a/Sources/SwiftNetwork/Endpoint/Endpoint.swift b/Sources/SwiftNetwork/Endpoint/Endpoint.swift index 8912384d..aa47881d 100644 --- a/Sources/SwiftNetwork/Endpoint/Endpoint.swift +++ b/Sources/SwiftNetwork/Endpoint/Endpoint.swift @@ -32,6 +32,9 @@ public class EndpointParent: Hashable, Equatable { @_spi(Essentials) @available(Network 0.1.0, *) +#if !NETWORK_EMBEDDED +@dynamicMemberLookup +#endif public final class Endpoint: EndpointParent, EndpointProtocol { public enum EndpointType: Sendable { case address(AddressEndpoint) @@ -43,14 +46,62 @@ public final class Endpoint: EndpointParent, EndpointProtocol { } public var type: EndpointType - var alternatePort: UInt16? = nil - var cnames: [Endpoint]? = nil - public var parentEndpoint: Endpoint? = nil + + /// Mutates `type` in place. This takes exclusive access to `type` for the + /// duration of `body`, so `body` must not read this same endpoint. + func modifyType(_ body: (inout EndpointType) -> Void) { + body(&type) + } #if NETWORK_PRIVATE || NETWORK_DRIVERKIT var endpointPrivate = Endpoint.EndpointPrivate() #endif + // MARK: -- Common state -- + + /// The common state shared by every endpoint type, forwarded to whichever + /// case `type` currently holds (unwrapped by `EndpointType.common`). All + /// individual common fields are reached through the `dynamicMember` + /// subscript below rather than through per-field switches, and writes go + /// through `modifyCommon` so they modify `type` in place. + var common: EndpointCommon { + get { type.common } + set { modifyType { $0.common = newValue } } + } + + /// Mutates the common state in place. As with `modifyType`, `body` must not + /// read this same endpoint. + func modifyCommon(_ body: (inout EndpointCommon) -> Void) { + modifyType { body(&$0.common) } + } + + #if !NETWORK_EMBEDDED + /// Reads and writes any ``EndpointCommon`` field directly on the endpoint, + /// e.g. `endpoint.alternatePort` or `endpoint.parentEndpoint`. + public subscript(dynamicMember keyPath: WritableKeyPath) -> Value { + get { common[keyPath: keyPath] } + set { modifyCommon { $0[keyPath: keyPath] = newValue } } + } + #else + // Embedded Swift has no key paths; forward the common fields explicitly. + var alternatePort: UInt16? { + get { common.alternatePort } + set { modifyCommon { $0.alternatePort = newValue } } + } + var cnames: [Endpoint]? { + get { common.cnames } + set { modifyCommon { $0.cnames = newValue } } + } + public var parentEndpoint: Endpoint? { + get { common.parentEndpoint } + set { modifyCommon { $0.parentEndpoint = newValue } } + } + var ethernetAddress: EthernetAddress? { + get { common.ethernetAddress } + set { modifyCommon { $0.ethernetAddress = newValue } } + } + #endif + // MARK: -- Initializers -- public init(_ address: AddressEndpoint) { self.type = .address(address) @@ -199,20 +250,6 @@ public final class Endpoint: EndpointParent, EndpointProtocol { #if !NETWORK_PRIVATE func isEqual(to other: Endpoint, flags: EndpointEqualityFlags) -> Bool { - if flags.contains(.alternatives) { - let alternatePort = alternatePort ?? 0 - let otherAlternatePort = other.alternatePort ?? 0 - if alternatePort != otherAlternatePort { - return false - } - } - - if flags.contains(.parent) { - if parentEndpoint != other.parentEndpoint { - return false - } - } - switch self.type { case .address(let endpoint): if case let .address(otherEndpoint) = other.type { @@ -297,45 +334,8 @@ public final class Endpoint: EndpointParent, EndpointProtocol { } var interface: Interface? { - get { - switch self.type { - case .address(let endpoint): - return endpoint.interface - case .applicationService(let endpoint): - return endpoint.interface - case .bonjour(let endpoint): - return endpoint.interface - case .host(let endpoint): - return endpoint.interface - case .srv(let endpoint): - return endpoint.interface - case .url(let endpoint): - return endpoint.interface - } - } - - set { - switch self.type { - case .address(var endpoint): - endpoint.interface = newValue - self.type = .address(endpoint) - case .applicationService(var endpoint): - endpoint.interface = newValue - self.type = .applicationService(endpoint) - case .bonjour(var endpoint): - endpoint.interface = newValue - self.type = .bonjour(endpoint) - case .host(var endpoint): - endpoint.interface = newValue - self.type = .host(endpoint) - case .srv(var endpoint): - endpoint.interface = newValue - self.type = .srv(endpoint) - case .url(var endpoint): - endpoint.interface = newValue - self.type = .url(endpoint) - } - } + get { common.interface } + set { modifyCommon { $0.interface = newValue } } } // MARK: -- Hashing -- diff --git a/Sources/SwiftNetwork/Endpoint/EndpointCommon.swift b/Sources/SwiftNetwork/Endpoint/EndpointCommon.swift index d2f917c3..56b94647 100644 --- a/Sources/SwiftNetwork/Endpoint/EndpointCommon.swift +++ b/Sources/SwiftNetwork/Endpoint/EndpointCommon.swift @@ -51,6 +51,42 @@ extension Endpoint.EndpointType { case applicationService = 6 } + /// The common state of whichever endpoint this case holds. + var common: EndpointCommon { + get { + switch self { + case .address(let endpoint): return endpoint.common + case .applicationService(let endpoint): return endpoint.common + case .bonjour(let endpoint): return endpoint.common + case .host(let endpoint): return endpoint.common + case .srv(let endpoint): return endpoint.common + case .url(let endpoint): return endpoint.common + } + } + set { + switch self { + case .address(var endpoint): + endpoint.common = newValue + self = .address(endpoint) + case .applicationService(var endpoint): + endpoint.common = newValue + self = .applicationService(endpoint) + case .bonjour(var endpoint): + endpoint.common = newValue + self = .bonjour(endpoint) + case .host(var endpoint): + endpoint.common = newValue + self = .host(endpoint) + case .srv(var endpoint): + endpoint.common = newValue + self = .srv(endpoint) + case .url(var endpoint): + endpoint.common = newValue + self = .url(endpoint) + } + } + } + func toRawValue() -> UInt32 { switch self { case .address(_): @@ -84,17 +120,23 @@ extension Endpoint.EndpointType { @available(Network 0.1.0, *) protocol EndpointProtocol: CustomStringConvertible { - var interface: Interface? { get } func isEqual(to other: Self, flags: EndpointEqualityFlags) -> Bool func serialize() -> [UInt8]? } @_spi(Essentials) @available(Network 0.1.0, *) -public struct EndpointCommon: Equatable, Hashable, Sendable { - let interface: Interface? +// `cnames` and `parentEndpoint` hold `Endpoint`, a mutable class, so the sendability of this +// struct is only as good as the confinement of those endpoints. The package builds in a language +// mode that checks this; libnetcore does not, which is why it carries the same fields unannotated. +public struct EndpointCommon: Equatable, Hashable, @unchecked Sendable { + var interface: Interface? + var alternatePort: UInt16? + var cnames: [Endpoint]? + var parentEndpoint: Endpoint? + var ethernetAddress: EthernetAddress? #if NETWORK_PRIVATE - let commonPrivate: EndpointCommon_Private? + var commonPrivate: EndpointCommon_Private? #endif init(interface: Interface? = nil) { @@ -111,8 +153,26 @@ public struct EndpointCommon: Equatable, Hashable, Sendable { } } + if flags.contains(.alternatives) { + if (self.alternatePort ?? 0) != (other.alternatePort ?? 0) { + return false + } + } + + if flags.contains(.parent) { + if self.parentEndpoint != other.parentEndpoint { + return false + } + } + + if flags.contains(.interface) { + if self.ethernetAddress != other.ethernetAddress { + return false + } + } + #if NETWORK_PRIVATE - if commonPrivate != other.commonPrivate { + if !self.isPrivateEqual(to: other, flags: flags) { return false } #endif @@ -125,23 +185,64 @@ public struct EndpointCommon: Equatable, Hashable, Sendable { hasher.combine(interface.hashValue) } } + + public static func == (lhs: EndpointCommon, rhs: EndpointCommon) -> Bool { + lhs.isEqual(to: rhs, flags: .all) + } } +/// Exposes every ``EndpointCommon`` field on the conforming type as if it were +/// declared there directly, so that adding a field to `EndpointCommon` (or to +/// `EndpointCommon_Private`) needs no per-field accessor plumbing on each of the +/// endpoint types. +/// +/// Embedded Swift does not support key paths, so that build gets explicit +/// forwarding accessors below instead of the `dynamicMember` subscript. @_spi(Essentials) @available(Network 0.1.0, *) +#if !NETWORK_EMBEDDED +@dynamicMemberLookup +#endif public protocol EndpointCommonProtocol: Hashable, Equatable { var common: EndpointCommon { get set } } -#if !NETWORK_PRIVATE +#if !NETWORK_EMBEDDED +@_spi(Essentials) +@available(Network 0.1.0, *) +extension EndpointCommonProtocol { + public subscript(dynamicMember keyPath: WritableKeyPath) -> Value { + get { common[keyPath: keyPath] } + set { common[keyPath: keyPath] = newValue } + } +} +#else @available(Network 0.1.0, *) extension EndpointCommonProtocol { var interface: Interface? { get { common.interface } - set { common = EndpointCommon(interface: newValue) } + set { common.interface = newValue } + } + var alternatePort: UInt16? { + get { common.alternatePort } + set { common.alternatePort = newValue } + } + var cnames: [Endpoint]? { + get { common.cnames } + set { common.cnames = newValue } + } + var parentEndpoint: Endpoint? { + get { common.parentEndpoint } + set { common.parentEndpoint = newValue } + } + var ethernetAddress: EthernetAddress? { + get { common.ethernetAddress } + set { common.ethernetAddress = newValue } } } +#endif +#if !NETWORK_PRIVATE @available(Network 0.1.0, *) extension EndpointCommon { init?(_ data: inout [UInt8]) {