blob: 5c8c73bc70e071a032caeadb23bded621b949236 [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Darwin
import Foundation
import _SwiftNetworkOverlayShims
internal extension sockaddr_in {
init(_ address:in_addr, _ port: in_port_t) {
self.init(sin_len: UInt8(MemoryLayout<sockaddr_in>.size), sin_family: sa_family_t(AF_INET), sin_port: port,
sin_addr: address, sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
}
func withSockAddr<ReturnType>(_ body: (_ sa: UnsafePointer<sockaddr>) throws -> ReturnType) rethrows -> ReturnType {
// We need to create a mutable copy of `self` so that we can pass it to `withUnsafePointer(to:_:)`.
var sin = self
return try withUnsafePointer(to: &sin) {
try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
try body($0)
}
}
}
}
internal extension sockaddr_in6 {
init(_ address:in6_addr, _ port: in_port_t, flow: UInt32 = 0, scope: UInt32 = 0) {
self.init(sin6_len: UInt8(MemoryLayout<sockaddr_in6>.size), sin6_family: sa_family_t(AF_INET6), sin6_port: port,
sin6_flowinfo: flow, sin6_addr: address, sin6_scope_id: scope)
}
func withSockAddr<ReturnType>(_ body: (_ sa: UnsafePointer<sockaddr>) throws -> ReturnType) rethrows -> ReturnType {
// We need to create a mutable copy of `self` so that we can pass it to `withUnsafePointer(to:_:)`.
var sin6 = self
return try withUnsafePointer(to: &sin6) {
try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
try body($0)
}
}
}
}
internal extension in_addr {
init(address: UInt32) {
self.init()
self.s_addr = address.bigEndian
}
}
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
private func getaddrinfo_numeric(_ string: String, family: Int32 = 0) -> NWEndpoint.Host? {
// Determine if this string has an interface scope specified "127.0.0.1%lo0" or "fe80::1%lo0"
var string = string
var interface : NWInterface? = nil
if let range = string.range(of: "%", options: String.CompareOptions.backwards) {
interface = NWInterface(String(string[range.upperBound...]))
if interface != nil {
string.removeSubrange(range.lowerBound...)
}
}
// call getaddrinfo
var hints = addrinfo(ai_flags: AI_NUMERICHOST, ai_family: family, ai_socktype: SOCK_STREAM, ai_protocol: 0,
ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil)
var resolved : UnsafeMutablePointer<addrinfo>? = nil
// After this point we must ensure we free addrinfo before we return
guard getaddrinfo(string, nil, &hints, &resolved) == 0, let addrinfo = resolved else {
return nil
}
var result: NWEndpoint.Host? = nil
if let sa = addrinfo.pointee.ai_addr {
if sa.pointee.sa_family == AF_INET {
sa.withMemoryRebound(to: sockaddr_in.self, capacity: 1, { (sin) -> Void in
result = NWEndpoint.Host.ipv4(IPv4Address(sin.pointee.sin_addr, interface))
})
} else if sa.pointee.sa_family == AF_INET6 {
sa.withMemoryRebound(to: sockaddr_in6.self, capacity: 1, { (sin6) -> Void in
if sin6.pointee.sin6_scope_id != 0 {
interface = NWInterface(Int(sin6.pointee.sin6_scope_id))
}
let ipv6 = IPv6Address(sin6.pointee.sin6_addr, interface);
if ipv6.isIPv4Mapped && family == AF_UNSPEC, let ipv4 = ipv6.asIPv4 {
// Treat IPv4 mapped as IPv4
result = NWEndpoint.Host.ipv4(ipv4)
} else {
result = NWEndpoint.Host.ipv6(ipv6)
}
})
}
}
freeaddrinfo(addrinfo)
return result
}
private func getnameinfo_numeric(address: UnsafeRawPointer) -> String {
let sa = address.assumingMemoryBound(to: sockaddr.self)
var result : String? = nil
let maxLen = socklen_t(100)
let storage = UnsafeMutablePointer<Int8>.allocate(capacity: Int(maxLen))
if getnameinfo(sa, socklen_t(sa.pointee.sa_len), storage, maxLen, nil, 0, NI_NUMERICHOST) == 0 {
result = String(cString: storage)
}
storage.deallocate()
return result ?? "?"
}
/// An IP address
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
public protocol IPAddress {
/// Fetch the raw address as data
var rawValue: Data { get }
/// Create an IP address from data. The length of the data must
/// match the expected length of addresses in the address family
/// (four bytes for IPv4, and sixteen bytes for IPv6)
init?(_ rawValue: Data, _ interface: NWInterface?)
/// Create an IP address from an address literal string.
/// If the string contains '%' to indicate an interface, the interface will be
/// associated with the address, such as "::1%lo0" being associated with the loopback
/// interface.
/// This function does not perform host name to address resolution. This is the same as calling getaddrinfo
/// and using AI_NUMERICHOST.
init?(_ string: String)
/// The interface the address is scoped to, if any.
var interface: NWInterface? { get }
/// Indicates if this address is loopback
var isLoopback : Bool { get }
/// Indicates if this address is link-local
var isLinkLocal : Bool { get }
/// Indicates if this address is multicast
var isMulticast : Bool { get }
}
/// IPv4Address
/// Base type to hold an IPv4 address and convert between strings and raw bytes.
/// Note that an IPv4 address may be scoped to an interface.
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
public struct IPv4Address: IPAddress, Hashable, CustomDebugStringConvertible {
/// The IPv4 any address used for listening
public static let any = IPv4Address(in_addr(address: INADDR_ANY), nil)
/// The IPv4 broadcast address used to broadcast to all hosts
public static let broadcast = IPv4Address(in_addr(address: INADDR_BROADCAST), nil)
/// The IPv4 loopback address
public static let loopback = IPv4Address(in_addr(address: INADDR_LOOPBACK), nil)
/// The IPv4 all hosts multicast group
public static let allHostsGroup = IPv4Address(in_addr(address: INADDR_ALLHOSTS_GROUP), nil)
/// The IPv4 all routers multicast group
public static let allRoutersGroup = IPv4Address(in_addr(address: INADDR_ALLRTRS_GROUP), nil)
/// The IPv4 all reports multicast group for ICMPv3 membership reports
public static let allReportsGroup = IPv4Address(in_addr(address: INADDR_ALLRPTS_GROUP), nil)
/// The IPv4 multicast DNS group. (Note: Use the dns_sd APIs instead of creating your own responder/resolver)
public static let mdnsGroup = IPv4Address(in_addr(address: INADDR_ALLMDNS_GROUP), nil)
/// Indicates if this IPv4 address is loopback (127.0.0.1)
public var isLoopback : Bool {
return self == IPv4Address.loopback
}
/// Indicates if this IPv4 address is link-local
public var isLinkLocal : Bool {
let linkLocalMask: UInt32 = IN_CLASSB_NET
let linkLocalCompare: UInt32 = IN_LINKLOCALNETNUM
return (self.address.s_addr & linkLocalMask.bigEndian) == linkLocalCompare.bigEndian
}
/// Indicates if this IPv4 address is multicast
public var isMulticast : Bool {
let multicastMask: UInt32 = IN_CLASSD_NET
let multicastCompare: UInt32 = INADDR_UNSPEC_GROUP
return (self.address.s_addr & multicastMask.bigEndian) == multicastCompare.bigEndian
}
/// Fetch the raw address (four bytes)
public var rawValue: Data {
var temporary = self.address
return withUnsafeBytes(of: &temporary) { (bytes) -> Data in
Data(bytes)
}
}
internal init(_ address: in_addr, _ interface: NWInterface?) {
self.address = address
self.interface = interface
}
/// Create an IPv4 address from a 4-byte data. Optionally specify an interface.
///
/// - Parameter rawValue: The raw bytes of the IPv4 address, must be exactly 4 bytes or init will fail.
/// - Parameter interface: An optional network interface to scope the address to. Defaults to nil.
/// - Returns: An IPv4Address or nil if the Data parameter did not contain an IPv4 address.
public init?(_ rawValue: Data, _ interface: NWInterface? = nil) {
if rawValue.count != MemoryLayout<in_addr>.size {
return nil
}
let v4 = rawValue.withUnsafeBytes { (ptr: UnsafePointer<in_addr>) -> in_addr in
return ptr.pointee
}
self.init(v4, interface)
}
/// Create an IPv4 address from an address literal string.
///
/// This function does not perform host name to address resolution. This is the same as calling getaddrinfo
/// and using AI_NUMERICHOST.
///
/// - Parameter string: An IPv4 address literal string such as "127.0.0.1", "169.254.8.8%en0".
/// - Returns: An IPv4Address or nil if the string parameter did not
/// contain an IPv4 address literal.
public init?(_ string: String) {
guard let result = getaddrinfo_numeric(string, family: AF_INET) else {
return nil
}
guard case .ipv4(let address) = result else {
return nil
}
self = address
}
fileprivate let address : in_addr
/// The interface the address is scoped to, if any.
public let interface: NWInterface?
// Hashable
public static func == (lhs: IPv4Address, rhs: IPv4Address) -> Bool {
return lhs.address.s_addr == rhs.address.s_addr && lhs.interface == rhs.interface
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.address.s_addr)
hasher.combine(self.interface)
}
// CustomDebugStringConvertible
public var debugDescription: String {
var sin = sockaddr_in(self.address, 0)
let addressString = getnameinfo_numeric(address: &sin)
if let interface = self.interface {
return String("\(addressString)%\(interface)")
} else {
return addressString
}
}
}
/// IPv6Address
/// Base type to hold an IPv6 address and convert between strings and raw bytes.
/// Note that an IPv6 address may be scoped to an interface.
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
public struct IPv6Address: IPAddress, Hashable, CustomDebugStringConvertible {
/// IPv6 any address
public static let any = IPv6Address(in6addr_any, nil)
/// IPv6 broadcast address
public static let broadcast = IPv6Address(in6addr_any, nil)
/// IPv6 loopback address
public static let loopback = IPv6Address(in6addr_loopback, nil)
/// IPv6 all node local nodes multicast
public static let nodeLocalNodes = IPv6Address(in6addr_nodelocal_allnodes, nil)
/// IPv6 all link local nodes multicast
public static let linkLocalNodes = IPv6Address(in6addr_linklocal_allnodes, nil)
/// IPv6 all link local routers multicast
public static let linkLocalRouters = IPv6Address(in6addr_linklocal_allrouters, nil)
public enum Scope: UInt8 {
case nodeLocal = 1
case linkLocal = 2
case siteLocal = 5
case organizationLocal = 8
case global = 0x0e
}
/// Is the Any address "::0"
public var isAny : Bool {
return self.address.__u6_addr.__u6_addr32.0 == 0 &&
self.address.__u6_addr.__u6_addr32.1 == 0 &&
self.address.__u6_addr.__u6_addr32.2 == 0 &&
self.address.__u6_addr.__u6_addr32.3 == 0
}
/// Is the looback address "::1"
public var isLoopback : Bool {
return self.address.__u6_addr.__u6_addr32.0 == 0 &&
self.address.__u6_addr.__u6_addr32.1 == 0 &&
self.address.__u6_addr.__u6_addr32.2 == 0 &&
self.address.__u6_addr.__u6_addr32.3 != 0 &&
self.address.__u6_addr.__u6_addr32.3 == UInt32(1).bigEndian
}
/// Is an IPv4 compatible address
public var isIPv4Compatabile : Bool {
return self.address.__u6_addr.__u6_addr32.0 == 0 &&
self.address.__u6_addr.__u6_addr32.1 == 0 &&
self.address.__u6_addr.__u6_addr32.2 == 0 &&
self.address.__u6_addr.__u6_addr32.3 != 0 &&
self.address.__u6_addr.__u6_addr32.3 != UInt32(1).bigEndian
}
/// Is an IPv4 mapped address such as "::ffff:1.2.3.4"
public var isIPv4Mapped : Bool {
return self.address.__u6_addr.__u6_addr32.0 == 0 &&
self.address.__u6_addr.__u6_addr32.1 == 0 &&
self.address.__u6_addr.__u6_addr32.2 == UInt32(0x0000ffff).bigEndian
}
/// For IPv6 addresses that are IPv4 mapped, returns the IPv4 address
///
/// - Returns: nil unless the IPv6 address was mapped or compatible, in which case the IPv4 address is
/// returned.
public var asIPv4 : IPv4Address? {
guard self.isIPv4Mapped || self.isIPv4Compatabile else {
return nil
}
return IPv4Address(in_addr(address: self.address.__u6_addr.__u6_addr32.3.bigEndian),
self.interface)
}
/// Is a 6to4 IPv6 address
public var is6to4 : Bool {
return self.address.__u6_addr.__u6_addr16.0 == UInt16(0x2002).bigEndian
}
/// Is a link-local address
public var isLinkLocal : Bool {
return self.address.__u6_addr.__u6_addr8.0 == UInt8(0xfe) &&
(self.address.__u6_addr.__u6_addr8.1 & 0xc0) == 0x80
}
/// Is multicast
public var isMulticast : Bool {
return self.address.__u6_addr.__u6_addr8.0 == 0xff
}
/// Returns the multicast scope
public var multicastScope : IPv6Address.Scope? {
if (self.isMulticast) {
return IPv6Address.Scope(rawValue: self.address.__u6_addr.__u6_addr8.1 & 0x0f)
}
return nil
}
internal init(_ ip6: in6_addr, _ interface: NWInterface?) {
self.address = ip6
self.interface = interface
}
/// Create an IPv6 from a raw 16 byte value and optional interface
///
/// - Parameter rawValue: A 16 byte IPv6 address
/// - Parameter interface: An optional interface the address is scoped to. Defaults to nil.
/// - Returns: nil unless the raw data contained an IPv6 address
public init?(_ rawValue: Data, _ interface: NWInterface? = nil) {
if rawValue.count != MemoryLayout<in6_addr>.size {
return nil
}
let v6 = rawValue.withUnsafeBytes { (ptr: UnsafePointer<in6_addr>) -> in6_addr in
return ptr.pointee
}
self.init(v6, interface)
}
/// Create an IPv6 address from a string literal such as "fe80::1%lo0" or "2001:DB8::5"
///
/// This function does not perform hostname resolution. This is similar to calling getaddrinfo with
/// AI_NUMERICHOST.
///
/// - Parameter string: An IPv6 address literal string.
/// - Returns: nil unless the string contained an IPv6 literal
public init?(_ string: String) {
guard let result = getaddrinfo_numeric(string, family: AF_INET6) else {
return nil
}
guard case .ipv6(let address) = result else {
return nil
}
self = address
}
fileprivate let address: in6_addr
/// The interface the address is scoped to, if any.
public let interface: NWInterface?
/// Fetch the raw address (sixteen bytes)
public var rawValue: Data {
var temporary = self.address
return withUnsafeBytes(of: &temporary) { (bytes) -> Data in
Data(bytes)
}
}
// Hashable
public static func ==(lhs: IPv6Address, rhs: IPv6Address) -> Bool {
return lhs.address.__u6_addr.__u6_addr32.0 == rhs.address.__u6_addr.__u6_addr32.0 &&
lhs.address.__u6_addr.__u6_addr32.1 == rhs.address.__u6_addr.__u6_addr32.1 &&
lhs.address.__u6_addr.__u6_addr32.2 == rhs.address.__u6_addr.__u6_addr32.2 &&
lhs.address.__u6_addr.__u6_addr32.3 == rhs.address.__u6_addr.__u6_addr32.3 &&
lhs.interface == rhs.interface
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.address.__u6_addr.__u6_addr32.0)
hasher.combine(self.address.__u6_addr.__u6_addr32.1)
hasher.combine(self.address.__u6_addr.__u6_addr32.2)
hasher.combine(self.address.__u6_addr.__u6_addr32.3)
hasher.combine(self.interface)
}
// CustomDebugStringConvertible
public var debugDescription: String {
var sin6 = sockaddr_in6(self.address, 0)
let addressString = getnameinfo_numeric(address: &sin6)
if let interface = self.interface {
return String("\(addressString)%\(interface)")
} else {
return addressString
}
}
}
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
/// NWEndpoint represents something that can be connected to.
public enum NWEndpoint: Hashable, CustomDebugStringConvertible {
// Depends on non-exhaustive enum support for forward compatibility - in the event we need to add
// a new case in the future
// https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
/// A host port endpoint represents an endpoint defined by the host and port.
case hostPort(host: NWEndpoint.Host, port: NWEndpoint.Port)
/// A service endpoint represents a Bonjour service
case service(name: String, type: String, domain: String, interface: NWInterface?)
/// A unix endpoint represents a path that supports connections using AF_UNIX domain sockets.
case unix(path: String)
/// A Host is a name or address
public enum Host: Hashable, CustomDebugStringConvertible, ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public func hash(into hasher: inout Hasher) {
switch self {
case .name(let hostName, let hostInterface):
hasher.combine(hostInterface)
hasher.combine(hostName)
case .ipv4(let v4Address):
hasher.combine(v4Address)
case .ipv6(let v6Address):
hasher.combine(v6Address)
}
}
/// A host specified as a name and optional interface scope
case name(String, NWInterface?)
/// A host specified as an IPv4 address
case ipv4(IPv4Address)
/// A host specified an an IPv6 address
case ipv6(IPv6Address)
public init(stringLiteral: StringLiteralType) {
self.init(stringLiteral)
}
/// Create a host from a string.
///
/// This is the preferred way to create a host. If the string is an IPv4 address literal ("198.51.100.2"), an
/// IPv4 host will be created. If the string is an IPv6 address literal ("2001:DB8::2", "fe80::1%lo", etc), an IPv6
/// host will be created. If the string is an IPv4 mapped IPv6 address literal ("::ffff:198.51.100.2") an IPv4
/// host will be created. Otherwise, a named host will be created.
///
/// - Parameter string: An IPv4 address literal, an IPv6 address literal, or a hostname.
/// - Returns: A Host object
public init(_ string: String) {
if let result = getaddrinfo_numeric(string) {
self = result
} else {
if let range = string.range(of: "%", options: String.CompareOptions.backwards),
let interface = NWInterface(String(string[range.upperBound...])){
self = .name(String(string[..<range.lowerBound]), interface)
} else {
self = .name(string, nil)
}
}
}
/// Returns the interface the host is scoped to if any
public var interface : NWInterface? {
switch self {
case .ipv4(let ip4):
return ip4.interface
case .ipv6(let ip6):
return ip6.interface
case .name(_, let interface):
return interface
}
}
public var debugDescription: String {
switch self {
case .ipv4(let ip4):
return ip4.debugDescription
case .ipv6(let ip6):
return ip6.debugDescription
case .name(let name, let interface):
if let interface = interface {
return String("\(name)%\(interface)")
} else {
return name
}
}
}
}
/// A network port (TCP or UDP)
public struct Port : Hashable, CustomDebugStringConvertible, ExpressibleByIntegerLiteral, RawRepresentable {
public typealias IntegerLiteralType = UInt16
fileprivate let port: IntegerLiteralType
public static let any : NWEndpoint.Port = 0
public static let ssh : NWEndpoint.Port = 22
public static let smtp : NWEndpoint.Port = 25
public static let http : NWEndpoint.Port = 80
public static let pop : NWEndpoint.Port = 110
public static let imap : NWEndpoint.Port = 143
public static let https : NWEndpoint.Port = 443
public static let imaps : NWEndpoint.Port = 993
public static let socks : NWEndpoint.Port = 1080
public var rawValue: UInt16 {
return self.port
}
/// Create a port from a string.
///
/// Supports common service names such as "http" as well as number strings such as "80".
///
/// - Parameter service: A service string such as "http" or a number string such as "80"
/// - Returns: A port if the string can be converted to a port, nil otherwise.
public init?(_ service: String) {
var hints = addrinfo(ai_flags: AI_DEFAULT, ai_family: AF_INET6, ai_socktype: SOCK_STREAM, ai_protocol: 0,
ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil)
var resolved : UnsafeMutablePointer<addrinfo>? = nil
if getaddrinfo(nil, service, &hints, &resolved) != 0 {
return nil
}
// Check that it didn't return NULL.
guard let addrinfo = resolved else {
return nil
}
// After this point we must ensure we free addrinfo before we return
guard let sa = addrinfo.pointee.ai_addr, sa.pointee.sa_family == AF_INET6 else {
freeaddrinfo(addrinfo)
return nil
}
self.port = sa.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) {sin6 in
return sin6.pointee.sin6_port.bigEndian
}
freeaddrinfo(addrinfo)
}
public init(integerLiteral value: IntegerLiteralType) {
self.port = value
}
public init?(rawValue: UInt16) {
self.port = rawValue
}
internal init(_ value: UInt16) {
self.init(integerLiteral: value)
}
public var debugDescription: String {
return String(port)
}
}
/// Returns the interface the endpoint is scoped to if any
public var interface : NWInterface? {
switch self {
case .hostPort(host: let host, port: _):
return host.interface
case .service(name: _, type: _, domain: _, interface: let interface):
return interface
case .unix(_):
return nil
}
}
internal init?(_ nw: nw_endpoint_t?) {
guard let nw = nw else {
return nil
}
var interface: NWInterface? = nil
if let nwinterface = nw_endpoint_copy_interface(nw) {
interface = NWInterface(nwinterface)
}
if nw_endpoint_get_type(nw) == Network.nw_endpoint_type_host {
let host = NWEndpoint.Host.name(String(cString: nw_endpoint_get_hostname(nw)), interface)
self = .hostPort(host: host, port: NWEndpoint.Port(nw_endpoint_get_port(nw)))
} else if nw_endpoint_get_type(nw) == Network.nw_endpoint_type_address {
let port = NWEndpoint.Port(nw_endpoint_get_port(nw))
let address = nw_endpoint_get_address(nw)
if address.pointee.sa_family == AF_INET && address.pointee.sa_len == MemoryLayout<sockaddr_in>.size {
let host = address.withMemoryRebound(to: sockaddr_in.self, capacity: 1) {
(sin: UnsafePointer<sockaddr_in>) -> NWEndpoint.Host in
return NWEndpoint.Host.ipv4(IPv4Address(sin.pointee.sin_addr, interface))
}
self = .hostPort(host: host, port: port)
} else if address.pointee.sa_family == AF_INET6 &&
address.pointee.sa_len == MemoryLayout<sockaddr_in6>.size {
let host = address.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) {
(sin6) -> NWEndpoint.Host in
if interface == nil && sin6.pointee.sin6_scope_id != 0 {
interface = NWInterface(Int(sin6.pointee.sin6_scope_id))
}
return NWEndpoint.Host.ipv6(IPv6Address(sin6.pointee.sin6_addr,
interface))
}
self = .hostPort(host: host, port: port)
} else if address.pointee.sa_family == AF_UNIX {
// sockaddr_un is very difficult to deal with in swift. Fortunately, nw_endpoint_copy_address_string
// already does exactly what we need.
let path = nw_endpoint_copy_address_string(nw)
self = .unix(path: String(cString: path))
path.deallocate()
} else {
return nil
}
} else if nw_endpoint_get_type(nw) == Network.nw_endpoint_type_bonjour_service {
self = .service(name: String(cString: nw_endpoint_get_bonjour_service_name(nw)),
type: String(cString: nw_endpoint_get_bonjour_service_type(nw)),
domain: String(cString: nw_endpoint_get_bonjour_service_domain(nw)),
interface: interface)
} else {
return nil
}
}
public func hash(into hasher: inout Hasher) {
switch self {
case .hostPort(host: let host, port: let port):
hasher.combine(host)
hasher.combine(port)
case .service(name: let name, type: let type, domain: let domain, interface: let interface):
hasher.combine(name)
hasher.combine(type)
hasher.combine(domain)
hasher.combine(interface)
case .unix(let path):
hasher.combine(path)
}
}
public var debugDescription: String {
switch self {
case .hostPort(host: let host, port: let port):
var separator = ":"
if case .ipv6 = host {
separator = "."
}
return String("\(host)\(separator)\(port)")
case .service(name: let name, type: let type, domain: let domain, interface: let interface):
if let interface = interface {
return String("\(name).\(type)\(domain)%\(interface)")
}
return String("\(name).\(type)\(domain)")
case .unix(let path):
return path
}
}
internal var nw: nw_endpoint_t? {
var endpoint: nw_endpoint_t? = nil
var interface: NWInterface? = nil
switch self {
case .hostPort(host: let host, port: let port):
switch host {
case .ipv4(let ipv4):
let sin = sockaddr_in(ipv4.address, port.port.bigEndian)
endpoint = sin.withSockAddr { (sa) -> nw_endpoint_t in
nw_endpoint_create_address(sa)
}
interface = ipv4.interface
case .ipv6(let ipv6):
let sin6 = sockaddr_in6(ipv6.address, port.port.bigEndian)
endpoint = sin6.withSockAddr { (sa) -> nw_endpoint_t? in
nw_endpoint_create_address(sa)
}
interface = ipv6.interface
case .name(let host, let hostInterface):
endpoint = nw_endpoint_create_host(host, port.debugDescription)
interface = hostInterface
}
case .service(name: let name, type: let type, domain: let domain, interface: let bonjourInterface):
endpoint = nw_endpoint_create_bonjour_service(name, type, domain)
interface = bonjourInterface
case .unix(let path):
endpoint = nw_endpoint_create_unix(path)
}
if interface != nil && endpoint != nil {
nw_endpoint_set_interface(endpoint!, interface!.nw)
}
return endpoint
}
}