diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 43ef3b1e..329f1c23 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -25,6 +25,20 @@ public struct FileDescriptor: RawRepresentable, Hashable, Codable { public init(rawValue: CInt) { self.rawValue = rawValue } } +internal extension FileDescriptor { + + init(socket: CInterop.SocketDescriptor) { + // On Unix a file descriptor can be a file, pipe, socket, etc + // On windows file descriptors and sockets are different types + #if os(Windows) + #warning("Windows sockets not implemented") + fatalError() + #else + self.init(rawValue: socket) + #endif + } +} + // Standard file descriptors extension FileDescriptor { /// The standard input file descriptor, with a numeric value of 0. diff --git a/Sources/System/FileDescriptorSet.swift b/Sources/System/FileDescriptorSet.swift new file mode 100644 index 00000000..1e2d4552 --- /dev/null +++ b/Sources/System/FileDescriptorSet.swift @@ -0,0 +1,179 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 - 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +public extension FileDescriptor { + + /// Set of file descriptors + @frozen + struct Set { + + @_alwaysEmitIntoClient + internal private(set) var bytes: CInterop.FileDescriptorSet + + @_alwaysEmitIntoClient + public init(_ bytes: CInterop.FileDescriptorSet) { + self.bytes = bytes + } + + @_alwaysEmitIntoClient + public init() { + self.init(CInterop.FileDescriptorSet()) + } + } +} + +public extension FileDescriptor.Set { + + @_alwaysEmitIntoClient + init(_ sequence: S) where S: Sequence, S.Element == FileDescriptor { + self.init() + for element in sequence { + bytes.set(element.rawValue) + } + } + + @_alwaysEmitIntoClient + mutating func append(_ element: FileDescriptor) { + bytes.set(element.rawValue) + } + + @_alwaysEmitIntoClient + mutating func remove(_ element: FileDescriptor) { + bytes.clear(element.rawValue) + } + + @_alwaysEmitIntoClient + mutating func contains(_ element: FileDescriptor) -> Bool { + bytes.isSet(element.rawValue) + } + + @_alwaysEmitIntoClient + mutating func removeAll() { + self.bytes.zero() + } + + #if os(Windows) + @_alwaysEmitIntoClient + var count: Int { + return numericCast(bytes.fd_count) + } + #endif +} + +extension FileDescriptor.Set: ExpressibleByArrayLiteral { + + public init(arrayLiteral elements: FileDescriptor...) { + assert(elements.count <= _fd_set_count, + "FileDescriptor.Set can only contain \(_fd_set_count) elements") + self.init(elements) + } +} + +extension FileDescriptor.Set: CustomStringConvertible, CustomDebugStringConvertible { + + @inline(never) + public var description: String { + return "FileDescriptor.Set()" + } + + public var debugDescription: String { + return description + } +} + +public extension FileDescriptor.Set { + + @_alwaysEmitIntoClient + mutating func withUnsafeMutablePointer(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { + return try bytes.withUnsafeMutablePointer(body) + } +} + +internal extension CInterop.FileDescriptorSet { + + @usableFromInline + mutating func zero() { + withUnsafeMutablePointer { + $0.initialize(repeating: 0, count: _fd_set_count) + } + } + + /// + /// Set an fd in an fd_set + /// + /// - Parameter fd: The fd to add to the fd_set + /// + @usableFromInline + mutating func set(_ fd: CInt) { + let (index, mask) = Self.offset(for: fd) + withUnsafeMutablePointer { $0[index] |= mask } + } + + /// + /// Clear an fd from an fd_set + /// + /// - Parameter fd: The fd to clear from the fd_set + /// + @usableFromInline + mutating func clear(_ fd: CInt) { + let (index, mask) = Self.offset(for: fd) + withUnsafeMutablePointer { $0[index] &= ~mask } + } + + /// + /// Check if an fd is present in an fd_set + /// + /// - Parameter fd: The fd to check + /// + /// - Returns: `True` if present, `false` otherwise. + /// + @usableFromInline + mutating func isSet(_ fd: CInt) -> Bool { + let (index, mask) = Self.offset(for: fd) + return withUnsafeMutablePointer { $0[index] & mask != 0 } + } + + @usableFromInline + static func offset(for fd: CInt) -> (offset: Int, mask: CInt) { + var intOffset = Int(fd) / _fd_set_count + #if _endian(big) + if intOffset % 2 == 0 { + intOffset += 1 + } else { + intOffset -= 1 + } + #endif + let bitOffset = Int(fd) % _fd_set_count + let mask = CInt(bitPattern: UInt32(1 << bitOffset)) + return (intOffset, mask) + } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + @usableFromInline + mutating func withUnsafeMutablePointer(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { + return try Swift.withUnsafeMutablePointer(to: &fds_bits) { + try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: CInt.self)) + } + } +#elseif os(Linux) || os(FreeBSD) || os(Android) + @usableFromInline + mutating func withUnsafeMutablePointer(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { + return try Swift.withUnsafeMutablePointer(to: &__fds_bits) { + try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: CInt.self)) + } + } +#elseif os(Windows) + @usableFromInline + mutating func withUnsafeMutablePointer(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { + return try Swift.withUnsafeMutablePointer(to: &fds_bits) { + try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: CInt.self)) + } + } +#endif +} diff --git a/Sources/System/FileEvent.swift b/Sources/System/FileEvent.swift new file mode 100644 index 00000000..5f3abc65 --- /dev/null +++ b/Sources/System/FileEvent.swift @@ -0,0 +1,76 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 - 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +/// File Events bitmask +@frozen +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +public struct FileEvents: OptionSet, Hashable, Codable { + + /// The raw C file events. + @_alwaysEmitIntoClient + public let rawValue: CInterop.FileEvent + + /// Create a strongly-typed file events from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.FileEvent) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ raw: CInt) { self.init(rawValue: numericCast(raw)) } +} + +public extension FileEvents { + + /// There is data to read. + @_alwaysEmitIntoClient + static var read: FileEvents { FileEvents(_POLLIN) } + + /// There is urgent data to read (e.g., out-of-band data on TCP socket; + /// pseudoterminal master in packet mode has seen state change in slave). + @_alwaysEmitIntoClient + static var readUrgent: FileEvents { FileEvents(_POLLPRI) } + + /// Writing now will not block. + @_alwaysEmitIntoClient + static var write: FileEvents { FileEvents(_POLLOUT) } + + /// Error condition. + @_alwaysEmitIntoClient + static var error: FileEvents { FileEvents(_POLLERR) } + + /// Hang up. + @_alwaysEmitIntoClient + static var hangup: FileEvents { FileEvents(_POLLHUP) } + + /// Error condition. + @_alwaysEmitIntoClient + static var invalidRequest: FileEvents { FileEvents(_POLLNVAL) } +} + + +extension FileEvents + : CustomStringConvertible, CustomDebugStringConvertible +{ + /// A textual representation of the file permissions. + @inline(never) + public var description: String { + let descriptions: [(Element, StaticString)] = [ + (.read, ".read"), + (.readUrgent, ".readUrgent"), + (.write, ".write"), + (.error, ".error"), + (.hangup, ".hangup"), + (.invalidRequest, ".invalidRequest") + ] + + return _buildDescription(descriptions) + } + + /// A textual representation of the file permissions, suitable for debugging. + public var debugDescription: String { self.description } +} diff --git a/Sources/System/FileHelpers.swift b/Sources/System/FileHelpers.swift index d083c101..5f80e06a 100644 --- a/Sources/System/FileHelpers.swift +++ b/Sources/System/FileHelpers.swift @@ -9,6 +9,7 @@ // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FileDescriptor { + /// Runs a closure and then closes the file descriptor, even if an error occurs. /// /// - Parameter body: The closure to run. @@ -32,7 +33,36 @@ extension FileDescriptor { try self.close() return result } - + + /// Runs a closure and then closes the file descriptor if an error occurs. + /// + /// - Parameter body: The closure to run. + /// If the closure throws an error, + /// this method closes the file descriptor before it rethrows that error. + /// + /// - Returns: The value returned by the closure. + /// + /// If `body` throws an error + /// this method rethrows that error. + @_alwaysEmitIntoClient + public func closeIfThrows(_ body: () throws -> R) throws -> R { + do { + return try body() + } catch { + _ = self._close() // Squash close error and throw closure's + throw error + } + } + + @usableFromInline + internal func _closeIfThrows(_ body: () -> Result) -> Result { + return body().mapError { + // close if error is thrown + let _ = _close() + return $0 + } + } + /// Writes a sequence of bytes to the current offset /// and then updates the offset. /// @@ -123,3 +153,15 @@ extension FileDescriptor { } } } + +internal extension Result where Success == FileDescriptor, Failure == Errno { + + @usableFromInline + func _closeIfThrows(_ body: (FileDescriptor) -> Result) -> Result { + return flatMap { fileDescriptor in + fileDescriptor._closeIfThrows { + body(fileDescriptor) + } + } + } +} diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 7f3b96d7..a9721bd4 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -49,7 +49,7 @@ public enum CInterop { /// Windows, this is `UInt16` (a "wide" character). public typealias PlatformChar = CInterop.Char #endif - + #if os(Windows) /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on /// Windows it is UTF-16. Native strings may contain invalid Unicode, @@ -63,4 +63,43 @@ public enum CInterop { /// on API. public typealias PlatformUnicodeEncoding = UTF8 #endif + + /// The platform file descriptor set. + public typealias FileDescriptorSet = fd_set + + public typealias PollFileDescriptor = pollfd + + public typealias FileDescriptorCount = nfds_t + + public typealias FileEvent = Int16 + + #if os(Windows) + /// The platform socket descriptor. + public typealias SocketDescriptor = SOCKET + #else + /// The platform socket descriptor, which is the same as a file desciptor on Unix systems. + public typealias SocketDescriptor = CInt + #endif + + /// The C `msghdr` type + public typealias MessageHeader = msghdr + + /// The C `sa_family_t` type + public typealias SocketAddressFamily = sa_family_t + + /// Socket Type + #if os(Linux) + public typealias SocketType = __socket_type + #else + public typealias SocketType = CInt + #endif + + /// The C `addrinfo` type + public typealias AddressInfo = addrinfo + + /// The C `sockaddr_in` type + public typealias SocketAddress = sockaddr + + /// The C `sockaddr_in` type + public typealias UnixSocketAddress = sockaddr_un } diff --git a/Sources/System/Internals/CSocketAddress.swift b/Sources/System/Internals/CSocketAddress.swift new file mode 100644 index 00000000..2644e89a --- /dev/null +++ b/Sources/System/Internals/CSocketAddress.swift @@ -0,0 +1,43 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +@usableFromInline +internal protocol CSocketAddress { + + static var family: SocketAddressFamily { get } + + init() +} + +internal extension CSocketAddress { + + @usableFromInline + func withUnsafePointer( + _ body: (UnsafePointer, UInt32) throws -> Result + ) rethrows -> Result { + return try Swift.withUnsafeBytes(of: self) { + return try body($0.baseAddress!.assumingMemoryBound(to: CInterop.SocketAddress.self), UInt32(MemoryLayout.size)) + } + } + + @usableFromInline + mutating func withUnsafeMutablePointer( + _ body: (UnsafeMutablePointer, UInt32) throws -> Result + ) rethrows -> Result { + return try Swift.withUnsafeMutableBytes(of: &self) { + return try body($0.baseAddress!.assumingMemoryBound(to: CInterop.SocketAddress.self), UInt32(MemoryLayout.size)) + } + } +} + +extension CInterop.UnixSocketAddress: CSocketAddress { + + @_alwaysEmitIntoClient + static var family: SocketAddressFamily { .unix } +} diff --git a/Sources/System/Internals/Compatibility.swift b/Sources/System/Internals/Compatibility.swift new file mode 100644 index 00000000..e219787e --- /dev/null +++ b/Sources/System/Internals/Compatibility.swift @@ -0,0 +1,48 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +internal extension String { + + @usableFromInline + init( + _unsafeUninitializedCapacity capacity: Int, + initializingUTF8With body: (UnsafeMutableBufferPointer) throws -> Int + ) rethrows { +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if #available(macOS 11, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { + self = try String( + unsafeUninitializedCapacity: capacity, + initializingUTF8With: body) + return + } else { + self = try Self.withUnsafeUninitialized(capacity: capacity, initializing: body) + } +#elseif swift(>=5.5) + self = try String( + unsafeUninitializedCapacity: capacity, + initializingUTF8With: body + ) +#else // older Swift + self = try Self.withUnsafeUninitialized(capacity: capacity, initializing: body) +#endif + } + + static func withUnsafeUninitialized( + capacity: Int, + initializing body: (UnsafeMutableBufferPointer) throws -> Int + ) rethrows -> String { + let array = try Array( + unsafeUninitializedCapacity: capacity + ) { buffer, count in + count = try body(buffer) + } + return String(decoding: array, as: UTF8.self) + } +} + diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index a4dbb89c..aba25821 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -529,3 +529,317 @@ internal var _SEEK_HOLE: CInt { SEEK_HOLE } internal var _SEEK_DATA: CInt { SEEK_DATA } #endif +@_alwaysEmitIntoClient +internal var _POLLIN: CInt { POLLIN } + +@_alwaysEmitIntoClient +internal var _POLLPRI: CInt { POLLPRI } + +@_alwaysEmitIntoClient +internal var _POLLOUT: CInt { POLLOUT } + +@_alwaysEmitIntoClient +internal var _POLLRDNORM: CInt { POLLRDNORM } + +@_alwaysEmitIntoClient +internal var _POLLWRNORM: CInt { POLLWRNORM } + +@_alwaysEmitIntoClient +internal var _POLLRDBAND: CInt { POLLRDBAND } + +@_alwaysEmitIntoClient +internal var _POLLWRBAND: CInt { POLLWRBAND } + +@_alwaysEmitIntoClient +internal var _POLLERR: CInt { POLLERR } + +@_alwaysEmitIntoClient +internal var _POLLHUP: CInt { POLLHUP } + +@_alwaysEmitIntoClient +internal var _POLLNVAL: CInt { POLLNVAL } + +@_alwaysEmitIntoClient +internal var _AF_UNIX: CInt { AF_UNIX } + +@_alwaysEmitIntoClient +internal var _AF_INET: CInt { AF_INET } + +@_alwaysEmitIntoClient +internal var _AF_INET6: CInt { AF_INET6 } + +@_alwaysEmitIntoClient +internal var _AF_IPX: CInt { AF_IPX } + +@_alwaysEmitIntoClient +internal var _AF_APPLETALK: CInt { AF_APPLETALK } + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _AF_DECnet: CInt { AF_DECnet } + +@_alwaysEmitIntoClient +internal var _AF_VSOCK: CInt { AF_VSOCK } + +@_alwaysEmitIntoClient +internal var _AF_ISDN: CInt { AF_ISDN } +#endif + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _AF_IMPLINK: CInt { AF_IMPLINK } + +@_alwaysEmitIntoClient +internal var _AF_PUP: CInt { AF_PUP } + +@_alwaysEmitIntoClient +internal var _AF_CHAOS: CInt { AF_CHAOS } + +@_alwaysEmitIntoClient +internal var _AF_NS: CInt { AF_NS } + +@_alwaysEmitIntoClient +internal var _AF_ISO: CInt { AF_ISO } + +@_alwaysEmitIntoClient +internal var _AF_PPP: CInt { AF_PPP } + +@_alwaysEmitIntoClient +internal var _AF_LINK: CInt { AF_LINK } + +@_alwaysEmitIntoClient +internal var _AF_NETBIOS: CInt { AF_NETBIOS } +#endif + +#if os(Linux) +@_alwaysEmitIntoClient +internal var _AF_AX25: CInt { AF_AX25 } + +@_alwaysEmitIntoClient +internal var _AF_X25: CInt { AF_X25 } + +@_alwaysEmitIntoClient +internal var _AF_KEY: CInt { AF_KEY } + +@_alwaysEmitIntoClient +internal var _AF_NETLINK: CInt { AF_NETLINK } + +@_alwaysEmitIntoClient +internal var _AF_PACKET: CInt { AF_PACKET } + +@_alwaysEmitIntoClient +internal var _AF_ATMSVC: CInt { AF_ATMSVC } + +@_alwaysEmitIntoClient +internal var _AF_RDS: CInt { AF_RDS } + +@_alwaysEmitIntoClient +internal var _AF_PPPOX: CInt { AF_PPPOX } + +@_alwaysEmitIntoClient +internal var _AF_WANPIPE: CInt { AF_WANPIPE } + +@_alwaysEmitIntoClient +internal var _AF_LLC: CInt { AF_LLC } + +@_alwaysEmitIntoClient +internal var _AF_IB: CInt { AF_IB } + +@_alwaysEmitIntoClient +internal var _AF_MPLS: CInt { AF_MPLS } + +@_alwaysEmitIntoClient +internal var _AF_CAN: CInt { AF_CAN } + +@_alwaysEmitIntoClient +internal var _AF_TIPC: CInt { AF_TIPC } + +@_alwaysEmitIntoClient +internal var _AF_BLUETOOTH: CInt { AF_BLUETOOTH } + +@_alwaysEmitIntoClient +internal var _AF_IUCV: CInt { AF_IUCV } + +@_alwaysEmitIntoClient +internal var _AF_RXRPC: CInt { AF_RXRPC } + +@_alwaysEmitIntoClient +internal var _AF_PHONET: CInt { AF_PHONET } + +@_alwaysEmitIntoClient +internal var _AF_IEEE802154: CInt { AF_IEEE802154 } + +@_alwaysEmitIntoClient +internal var _AF_CAIF: CInt { AF_CAIF } + +@_alwaysEmitIntoClient +internal var _AF_ALG: CInt { AF_ALG } + +@_alwaysEmitIntoClient +internal var _AF_KCM: CInt { AF_KCM } + +@_alwaysEmitIntoClient +internal var _AF_QIPCRTR: CInt { AF_QIPCRTR } + +@_alwaysEmitIntoClient +internal var _AF_SMC: CInt { AF_SMC } + +@_alwaysEmitIntoClient +internal var _AF_XDP: CInt { AF_XDP } +#endif + +#if os(Windows) +@_alwaysEmitIntoClient +internal var _AF_IRDA: CInt { AF_IRDA } + +@_alwaysEmitIntoClient +internal var _AF_BTH: CInt { AF_BTH } +#endif + +@_alwaysEmitIntoClient +internal var _SOCK_STREAM: CInterop.SocketType { SOCK_STREAM } + +@_alwaysEmitIntoClient +internal var _SOCK_DGRAM: CInterop.SocketType { SOCK_DGRAM } + +@_alwaysEmitIntoClient +internal var _SOCK_RAW: CInterop.SocketType { SOCK_RAW } + +@_alwaysEmitIntoClient +internal var _SOCK_RDM: CInterop.SocketType { SOCK_RDM } + +@_alwaysEmitIntoClient +internal var _SOCK_SEQPACKET: CInterop.SocketType { SOCK_SEQPACKET } + +#if os(Linux) +@_alwaysEmitIntoClient +internal var _SOCK_DCCP: CInterop.SocketType { SOCK_DCCP } + +@_alwaysEmitIntoClient +internal var _SOCK_CLOEXEC: CInterop.SocketType { SOCK_CLOEXEC } + +@_alwaysEmitIntoClient +internal var _SOCK_NONBLOCK: CInterop.SocketType { SOCK_NONBLOCK } +#endif + +@_alwaysEmitIntoClient +internal var _IPPROTO_RAW: CInt { numericCast(IPPROTO_RAW) } + +@_alwaysEmitIntoClient +internal var _IPPROTO_TCP: CInt { numericCast(IPPROTO_TCP) } + +@_alwaysEmitIntoClient +internal var _IPPROTO_UDP: CInt { numericCast(IPPROTO_UDP) } + +@_alwaysEmitIntoClient +internal var _SOL_SOCKET: CInt { SOL_SOCKET } + +@_alwaysEmitIntoClient +internal var _SO_DEBUG: CInt { SO_DEBUG } + +@_alwaysEmitIntoClient +internal var _SO_ACCEPTCONN: CInt { SO_ACCEPTCONN } + +@_alwaysEmitIntoClient +internal var _SO_REUSEADDR: CInt { SO_REUSEADDR } + +@_alwaysEmitIntoClient +internal var _SO_KEEPALIVE: CInt { SO_KEEPALIVE } + +@_alwaysEmitIntoClient +internal var _SO_DONTROUTE: CInt { SO_DONTROUTE } + +@_alwaysEmitIntoClient +internal var _SO_BROADCAST: CInt { SO_BROADCAST } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _SO_USELOOPBACK: CInt { SO_USELOOPBACK } +#endif + +@_alwaysEmitIntoClient +internal var _SO_LINGER: CInt { SO_LINGER } + +#if os(Linux) +@_alwaysEmitIntoClient +internal var _SOL_NETLINK: CInt { SOL_NETLINK } + +@_alwaysEmitIntoClient +internal var _SOL_BLUETOOTH: CInt { SOL_BLUETOOTH } + +@_alwaysEmitIntoClient +internal var _SOL_L2CAP: CInt { 6 } +#endif + +@_alwaysEmitIntoClient +internal var _MSG_DONTROUTE: CInt { numericCast(MSG_DONTROUTE) } /* send without using routing tables */ + +@_alwaysEmitIntoClient +internal var _MSG_EOR: CInt { numericCast(MSG_EOR) } /* data completes record */ + +@_alwaysEmitIntoClient +internal var _MSG_OOB: CInt { numericCast(MSG_OOB) } /* process out-of-band data */ + +@_alwaysEmitIntoClient +internal var _MSG_PEEK: CInt { numericCast(MSG_PEEK) } /* peek at incoming message */ +@_alwaysEmitIntoClient +internal var _MSG_TRUNC: CInt { numericCast(MSG_TRUNC) } /* data discarded before delivery */ +@_alwaysEmitIntoClient +internal var _MSG_CTRUNC: CInt { numericCast(MSG_CTRUNC) } /* control data lost before delivery */ +@_alwaysEmitIntoClient +internal var _MSG_WAITALL: CInt { numericCast(MSG_WAITALL) } /* wait for full request or error */ + +@_alwaysEmitIntoClient +internal var _MSG_DONTWAIT: CInt { numericCast(MSG_DONTWAIT) } /* this message should be nonblocking */ + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _MSG_EOF: CInt { numericCast(MSG_EOF) } /* data completes connection */ + +@_alwaysEmitIntoClient +internal var _MSG_WAITSTREAM: CInt { numericCast(MSG_WAITSTREAM) } /* wait up to full request.. may return partial */ + +@_alwaysEmitIntoClient +internal var _MSG_FLUSH: CInt { numericCast(MSG_FLUSH) } /* Start of 'hold' seq; dump so_temp, deprecated */ +@_alwaysEmitIntoClient +internal var _MSG_HOLD: CInt { numericCast(MSG_HOLD) } /* Hold frag in so_temp, deprecated */ +@_alwaysEmitIntoClient +internal var _MSG_SEND: CInt { numericCast(MSG_SEND) } /* Send the packet in so_temp, deprecated */ +@_alwaysEmitIntoClient +internal var _MSG_HAVEMORE: CInt { numericCast(MSG_HAVEMORE) } /* Data ready to be read */ +@_alwaysEmitIntoClient +internal var _MSG_RCVMORE: CInt { numericCast(MSG_RCVMORE) } /* Data remains in current pkt */ + +@_alwaysEmitIntoClient +internal var _MSG_NEEDSA: CInt { numericCast(MSG_NEEDSA) } /* Fail receive if socket address cannot be allocated */ + +@_alwaysEmitIntoClient +internal var _MSG_NOSIGNAL: CInt { numericCast(MSG_NOSIGNAL) } /* do not generate SIGPIPE on EOF */ +#endif + +#if os(Linux) +@_alwaysEmitIntoClient +internal var _MSG_CONFIRM: CInt { numericCast(MSG_CONFIRM) } + +@_alwaysEmitIntoClient +internal var _MSG_MORE: CInt { numericCast(MSG_MORE) } +#endif + +@_alwaysEmitIntoClient +internal var _fd_set_count: Int { +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + // __DARWIN_FD_SETSIZE is number of *bits*, so divide by number bits in each element to get element count + // at present this is 1024 / 32 == 32 + return Int(__DARWIN_FD_SETSIZE) / 32 +#elseif os(Linux) || os(FreeBSD) || os(Android) +#if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le) + return 32 +#elseif arch(i386) || arch(arm) + return 16 +#else +#error("This architecture isn't known. Add it to the 32-bit or 64-bit line.") +#endif +#elseif os(Windows) + return 32 +#endif +} diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 453c02fc..1127eef2 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -20,6 +20,13 @@ import ucrt // Interacting with the mocking system, tracing, etc., is a potentially significant // amount of code size, so we hand outline that code for every syscall +internal func system_strcpy(_ destination: UnsafeMutablePointer, _ source: UnsafePointer) -> UnsafeMutablePointer { + #if ENABLE_MOCKING + // FIXME + #endif + return strcpy(destination, source) +} + // open internal func system_open( _ path: UnsafePointer, _ oflag: Int32 @@ -115,6 +122,219 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { #endif return dup2(fd, fd2) } + +internal func system_socket(_ fd: Int32, _ fd2: Int32, _ fd3: Int32) -> Int32 { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, fd2, fd3) } + #endif + return socket(fd, fd2, fd3) +} + +internal func system_setsockopt(_ fd: Int32, _ fd2: Int32, _ fd3: Int32, _ pointer: UnsafeRawPointer, _ dataLength: UInt32) -> Int32 { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, fd2, fd3, pointer, dataLength) } + #endif + return setsockopt(fd, fd2, fd3, pointer, dataLength) +} + +internal func system_getsockopt( + _ socket: CInt, + _ level: CInt, + _ option: CInt, + _ value: UnsafeMutableRawPointer?, + _ length: UnsafeMutablePointer? +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(socket, level, option, value, length) } + #endif + return getsockopt(socket, level, option, value, length) +} + +internal func system_bind( + _ socket: CInt, + _ address: UnsafePointer, + _ length: UInt32 +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(socket, address, length) } + #endif + return bind(socket, address, length) +} + +internal func system_connect( + _ socket: CInt, + _ addr: UnsafePointer?, + _ len: socklen_t +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(socket, addr, len) } + #endif + return connect(socket, addr, len) +} + +internal func system_accept( + _ socket: CInt, + _ addr: UnsafeMutablePointer?, + _ len: UnsafeMutablePointer? +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(socket, addr, len) } + #endif + return accept(socket, addr, len) +} + +internal func system_getaddrinfo( + _ hostname: UnsafePointer?, + _ servname: UnsafePointer?, + _ hints: UnsafePointer?, + _ res: UnsafeMutablePointer?>? +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { + return _mock(hostname, + servname, + hints, res) + } + #endif + return getaddrinfo(hostname, servname, hints, res) +} + +internal func system_getnameinfo( + _ sa: UnsafePointer?, + _ salen: UInt32, + _ host: UnsafeMutablePointer?, + _ hostlen: UInt32, + _ serv: UnsafeMutablePointer?, + _ servlen: UInt32, + _ flags: CInt +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { + return _mock(sa, salen, host, hostlen, serv, servlen, flags) + } + #endif + return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) +} + +internal func system_freeaddrinfo( + _ addrinfo: UnsafeMutablePointer? +) { + #if ENABLE_MOCKING + if mockingEnabled { + _ = _mock(addrinfo) + return + } + #endif + return freeaddrinfo(addrinfo) +} + +internal func system_gai_strerror(_ error: CInt) -> UnsafePointer { + #if ENABLE_MOCKING + // FIXME + #endif + return gai_strerror(error) +} + +internal func system_shutdown(_ socket: CInt, _ how: CInt) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(socket, how) } + #endif + return shutdown(socket, how) +} + +internal func system_listen(_ socket: CInt, _ backlog: CInt) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(socket, backlog) } + #endif + return listen(socket, backlog) +} + +internal func system_send( + _ socket: Int32, _ buffer: UnsafeRawPointer?, _ len: Int, _ flags: Int32 +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { return _mockInt(socket, buffer, len, flags) } + #endif + return send(socket, buffer, len, flags) +} + +internal func system_recv( + _ socket: Int32, + _ buffer: UnsafeMutableRawPointer?, + _ len: Int, + _ flags: Int32 +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { return _mockInt(socket, buffer, len, flags) } + #endif + return recv(socket, buffer, len, flags) +} + +internal func system_sendto( + _ socket: CInt, + _ buffer: UnsafeRawPointer?, + _ length: Int, + _ flags: CInt, + _ dest_addr: UnsafePointer?, + _ dest_len: UInt32 +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return _mockInt(socket, buffer, length, flags, dest_addr, dest_len) + } + #endif + return sendto(socket, buffer, length, flags, dest_addr, dest_len) +} + +internal func system_recvfrom( + _ socket: CInt, + _ buffer: UnsafeMutableRawPointer?, + _ length: Int, + _ flags: CInt, + _ address: UnsafeMutablePointer?, + _ addres_len: UnsafeMutablePointer? +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return _mockInt(socket, buffer, length, flags, address, addres_len) + } + #endif + return recvfrom(socket, buffer, length, flags, address, addres_len) +} + +internal func system_poll( + _ fileDescriptors: UnsafeMutablePointer, + _ fileDescriptorsCount: CInterop.FileDescriptorCount, + _ timeout: CInt +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { return _mock(fileDescriptors, fileDescriptorsCount, timeout) } + #endif + return poll(fileDescriptors, fileDescriptorsCount, timeout) +} + +internal func system_sendmsg( + _ socket: CInt, + _ message: UnsafePointer?, + _ flags: CInt +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { return _mockInt(socket, message, flags) } + #endif + return sendmsg(socket, message, flags) +} + +internal func system_recvmsg( + _ socket: CInt, + _ message: UnsafeMutablePointer?, + _ flags: CInt +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { return _mockInt(socket, message, flags) } + #endif + return recvmsg(socket, message, flags) +} + #if !os(Windows) internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { #if ENABLE_MOCKING diff --git a/Sources/System/Socket/MessageFlags.swift b/Sources/System/Socket/MessageFlags.swift new file mode 100644 index 00000000..b85ede7f --- /dev/null +++ b/Sources/System/Socket/MessageFlags.swift @@ -0,0 +1,59 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +/// Message Flags +@frozen +public struct MessageFlags: OptionSet, Hashable, Codable { + + /// The raw C file permissions. + @_alwaysEmitIntoClient + public let rawValue: CInt + + /// Create a strongly-typed file permission from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ raw: CInt) { self.init(rawValue: raw) } +} + +public extension MessageFlags { + + @_alwaysEmitIntoClient + static var outOfBand: MessageFlags { MessageFlags(_MSG_OOB) } + + @_alwaysEmitIntoClient + static var peek: MessageFlags { MessageFlags(_MSG_PEEK) } + + @_alwaysEmitIntoClient + static var noRoute: MessageFlags { MessageFlags(_MSG_DONTROUTE) } + + @_alwaysEmitIntoClient + static var endOfReadline: MessageFlags { MessageFlags(_MSG_EOR) } +} + +extension MessageFlags + : CustomStringConvertible, CustomDebugStringConvertible +{ + /// A textual representation of the file permissions. + @inline(never) + public var description: String { + let descriptions: [(Element, StaticString)] = [ + (.outOfBand, ".outOfBand"), + (.peek, ".peek"), + (.noRoute, ".noRoute"), + (.endOfReadline, ".endOfReadline") + ] + + return _buildDescription(descriptions) + } + + /// A textual representation of the file permissions, suitable for debugging. + public var debugDescription: String { self.description } +} diff --git a/Sources/System/Socket/Poll.swift b/Sources/System/Socket/Poll.swift new file mode 100644 index 00000000..2ff31fcb --- /dev/null +++ b/Sources/System/Socket/Poll.swift @@ -0,0 +1,143 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +public extension FileDescriptor { + + /// Poll File Descriptor + struct Poll { + + internal private(set) var bytes: CInterop.PollFileDescriptor + + internal init(_ bytes: CInterop.PollFileDescriptor) { + self.bytes = bytes + } + + // Initialize an events request. + public init(fileDescriptor: FileDescriptor, events: FileEvents) { + self.init(CInterop.PollFileDescriptor(fileDescriptor: fileDescriptor, events: events)) + } + + public var fileDescriptor: FileDescriptor { + return FileDescriptor(rawValue: bytes.fd) + } + + public var events: FileEvents { + return FileEvents(rawValue: bytes.events) + } + + public var returnedEvents: FileEvents { + return FileEvents(rawValue: bytes.revents) + } + } +} + +internal extension CInterop.PollFileDescriptor { + + init(fileDescriptor: FileDescriptor, events: FileEvents) { + self.init(fd: fileDescriptor.rawValue, events: events.rawValue, revents: 0) + } +} + +// MARK: - Poll Operations + +extension FileDescriptor { + + /// Wait for some event on a file descriptor. + /// + /// - Parameters: + /// - events: A bit mask specifying the events the application is interested in for the file descriptor. + /// - timeout: Specifies the minimum number of milliseconds that this method will block. Specifying a negative value in timeout means an infinite timeout. Specifying a timeout of zero causes this method to return immediately. + /// - retryOnInterrupt: Whether to retry the receive operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: A bitmask filled by the kernel with the events that actually occurred. + /// + /// The corresponding C function is `poll`. + public func poll( + for events: FileEvents, + timeout: Int = 0, + retryOnInterrupt: Bool = true + ) throws -> FileEvents { + try _poll( + events: events, + timeout: CInt(timeout), + retryOnInterrupt: retryOnInterrupt + ).get() + } + + /// `poll()` + /// + /// Wait for some event on a file descriptor. + @usableFromInline + internal func _poll( + events: FileEvents, + timeout: CInt, + retryOnInterrupt: Bool + ) -> Result { + var pollFD = CInterop.PollFileDescriptor( + fd: self.rawValue, + events: events.rawValue, + revents: 0 + ) + return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_poll(&pollFD, 1, timeout) + }.map { FileEvents(rawValue: events.rawValue) } + } +} + +extension FileDescriptor { + + /// wait for some event on file descriptors + @usableFromInline + internal static func _poll( + _ pollFDs: inout [Poll], + timeout: CInt, + retryOnInterrupt: Bool + ) -> Result<(), Errno> { + assert(pollFDs.isEmpty == false) + let count = CInterop.FileDescriptorCount(pollFDs.count) + return pollFDs.withUnsafeMutableBufferPointer { buffer in + buffer.withMemoryRebound(to: CInterop.PollFileDescriptor.self) { cBuffer in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_poll( + cBuffer.baseAddress!, + count, + timeout + ) + } + } + } + } +} + + +extension Array where Element == FileDescriptor.Poll { + + /// Wait for some event on a set of file descriptors. + /// + /// - Parameters: + /// - fileDescriptors: An array of bit mask specifying the events the application is interested in for the file descriptors. + /// - timeout: Specifies the minimum number of milliseconds that this method will block. Specifying a negative value in timeout means an infinite timeout. Specifying a timeout of zero causes this method to return immediately. + /// - retryOnInterrupt: Whether to retry the receive operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns:A array of bitmasks filled by the kernel with the events that actually occurred + /// for the corresponding file descriptors. + /// + /// The corresponding C function is `poll`. + mutating func poll( + timeout: Int = 0, + retryOnInterrupt: Bool = true + ) throws { + guard isEmpty else { return } + try FileDescriptor._poll(&self, timeout: CInt(timeout), retryOnInterrupt: retryOnInterrupt).get() + } +} diff --git a/Sources/System/Socket/SocketAddress.swift b/Sources/System/Socket/SocketAddress.swift new file mode 100644 index 00000000..76167dd3 --- /dev/null +++ b/Sources/System/Socket/SocketAddress.swift @@ -0,0 +1,71 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +/// Socket Address +public protocol SocketAddress { + + /// Socket Protocol + associatedtype ProtocolID: SocketProtocol + + /// Unsafe pointer closure + func withUnsafePointer( + _ body: (UnsafePointer, UInt32) throws -> Result + ) rethrows -> Result + + static func withUnsafePointer( + _ body: (UnsafeMutablePointer, UInt32) throws -> () + ) rethrows -> Self +} + +public extension SocketAddress { + + @_alwaysEmitIntoClient + static var family: SocketAddressFamily { + return ProtocolID.family + } +} + +/// Unix Socket Address +public struct UnixSocketAddress: SocketAddress, Equatable, Hashable { + + public typealias ProtocolID = UnixProtocol + + public var path: FilePath + + @_alwaysEmitIntoClient + public init(path: FilePath) { + self.path = path + } + + public func withUnsafePointer( + _ body: (UnsafePointer, UInt32) throws -> Result + ) rethrows -> Result { + return try path.withPlatformString { platformString in + var socketAddress = CInterop.UnixSocketAddress() + socketAddress.sun_family = numericCast(Self.family.rawValue) + withUnsafeMutableBytes(of: &socketAddress.sun_path) { pathBytes in + pathBytes + .bindMemory(to: CInterop.PlatformChar.self) + .baseAddress! + .assign(from: platformString, count: path.length) + } + return try socketAddress.withUnsafePointer(body) + } + } + + public static func withUnsafePointer( + _ body: (UnsafeMutablePointer, UInt32) throws -> () + ) rethrows -> Self { + var socketAddress = CInterop.UnixSocketAddress() + try socketAddress.withUnsafeMutablePointer(body) + return withUnsafeBytes(of: socketAddress.sun_path) { pathPointer in + Self.init(path: FilePath(platformString: pathPointer.baseAddress!.assumingMemoryBound(to: CInterop.PlatformChar.self))) + } + } +} diff --git a/Sources/System/Socket/SocketAddressFamily.swift b/Sources/System/Socket/SocketAddressFamily.swift new file mode 100644 index 00000000..6d840b32 --- /dev/null +++ b/Sources/System/Socket/SocketAddressFamily.swift @@ -0,0 +1,224 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +/// POSIX Socket Address Family +@frozen +public struct SocketAddressFamily: RawRepresentable, Hashable, Codable { + + /// The raw socket address family identifier. + @_alwaysEmitIntoClient + public let rawValue: CInt + + /// Creates a strongly-typed socket address family from a raw address family identifier. + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ raw: CInt) { self.init(rawValue: raw) } +} + +public extension SocketAddressFamily { + + /// Local communication + @_alwaysEmitIntoClient + static var unix: SocketAddressFamily { SocketAddressFamily(_AF_UNIX) } + + /// IPv4 Internet protocol + @_alwaysEmitIntoClient + static var ipv4: SocketAddressFamily { SocketAddressFamily(_AF_INET) } + + /// IPv6 Internet protocol + @_alwaysEmitIntoClient + static var ipv6: SocketAddressFamily { SocketAddressFamily(_AF_INET6) } + + /// IPX - Novell protocol + @_alwaysEmitIntoClient + static var ipx: SocketAddressFamily { SocketAddressFamily(_AF_IPX) } + + /// AppleTalk protocol + @_alwaysEmitIntoClient + static var appleTalk: SocketAddressFamily { SocketAddressFamily(_AF_APPLETALK) } +} + +#if !os(Windows) +public extension SocketAddressFamily { + + /// DECet protocol sockets + @_alwaysEmitIntoClient + static var decnet: SocketAddressFamily { SocketAddressFamily(_AF_DECnet) } + + /// VSOCK (originally "VMWare VSockets") protocol for hypervisor-guest communication + @_alwaysEmitIntoClient + static var vsock: SocketAddressFamily { SocketAddressFamily(_AF_VSOCK) } + + /// Integrated Services Digital Network protocol + @_alwaysEmitIntoClient + static var isdn: SocketAddressFamily { SocketAddressFamily(_AF_ISDN) } +} +#endif + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +public extension SocketAddressFamily { + + /// NetBIOS protocol + @_alwaysEmitIntoClient + static var netbios: SocketAddressFamily { SocketAddressFamily(_AF_NETBIOS) } + + /// + @_alwaysEmitIntoClient + static var implink: SocketAddressFamily { SocketAddressFamily(_AF_IMPLINK) } + + /// + @_alwaysEmitIntoClient + static var pup: SocketAddressFamily { SocketAddressFamily(_AF_PUP) } + + /// + @_alwaysEmitIntoClient + static var chaos: SocketAddressFamily { SocketAddressFamily(_AF_CHAOS) } + + /// + @_alwaysEmitIntoClient + static var ns: SocketAddressFamily { SocketAddressFamily(_AF_NS) } + + /// + @_alwaysEmitIntoClient + static var iso: SocketAddressFamily { SocketAddressFamily(_AF_ISO) } + + /// Generic PPP transport layer, for setting up L2 tunnels (L2TP and PPPoE). + @_alwaysEmitIntoClient + static var ppp: SocketAddressFamily { SocketAddressFamily(_AF_PPP) } + + /// + @_alwaysEmitIntoClient + static var link: SocketAddressFamily { SocketAddressFamily(_AF_LINK) } +} +#endif + +#if os(Linux) +public extension SocketAddressFamily { + + /// Amateur radio AX.25 protocol + @_alwaysEmitIntoClient + static var ax25: SocketAddressFamily { SocketAddressFamily(_AF_AX25) } + + /// ITU-T X.25 / ISO-8208 protocol + @_alwaysEmitIntoClient + static var x25: SocketAddressFamily { SocketAddressFamily(_AF_X25) } + + /// Key management protocol + @_alwaysEmitIntoClient + static var key: SocketAddressFamily { SocketAddressFamily(_AF_KEY) } + + /// Kernel user interface device + @_alwaysEmitIntoClient + static var netlink: SocketAddressFamily { SocketAddressFamily(_AF_NETLINK) } + + /// Low-level packet interface + @_alwaysEmitIntoClient + static var packet: SocketAddressFamily { SocketAddressFamily(_AF_PACKET) } + + /// Access to ATM Switched Virtual Circuits + @_alwaysEmitIntoClient + static var atm: SocketAddressFamily { SocketAddressFamily(_AF_ATMSVC) } + + /// Reliable Datagram Sockets (RDS) protocol + @_alwaysEmitIntoClient + static var rds: SocketAddressFamily { SocketAddressFamily(_AF_RDS) } + + /// Generic PPP transport layer, for setting up L2 tunnels (L2TP and PPPoE). + @_alwaysEmitIntoClient + static var ppp: SocketAddressFamily { SocketAddressFamily(_AF_PPPOX) } + + /// Legacy protocol for wide area network (WAN) connectivity that was used by Sangoma WAN cards. + @_alwaysEmitIntoClient + static var wanpipe: SocketAddressFamily { SocketAddressFamily(_AF_WANPIPE) } + + /// Logical link control (IEEE 802.2 LLC) protocol, upper part of data link layer of ISO/OSI networking protocol stack. + @_alwaysEmitIntoClient + static var link: SocketAddressFamily { SocketAddressFamily(_AF_LLC) } + + /// InfiniBand native addressing. + @_alwaysEmitIntoClient + static var ib: SocketAddressFamily { SocketAddressFamily(_AF_IB) } + + /// Multiprotocol Label Switching + @_alwaysEmitIntoClient + static var mpls: SocketAddressFamily { SocketAddressFamily(_AF_MPLS) } + + /// Controller Area Network automotive bus protocol + @_alwaysEmitIntoClient + static var can: SocketAddressFamily { SocketAddressFamily(_AF_CAN) } + + /// TIPC, "cluster domain sockets" protocol + @_alwaysEmitIntoClient + static var tipc: SocketAddressFamily { SocketAddressFamily(_AF_TIPC) } + + /// Bluetooth protocol + @_alwaysEmitIntoClient + static var bluetooth: SocketAddressFamily { SocketAddressFamily(_AF_BLUETOOTH) } + + /// IUCV (inter-user communication vehicle) z/VM protocol for hypervisor-guest interaction + @_alwaysEmitIntoClient + static var iucv: SocketAddressFamily { SocketAddressFamily(_AF_IUCV) } + + /// Rx, Andrew File System remote procedure call protocol + @_alwaysEmitIntoClient + static var rxrpc: SocketAddressFamily { SocketAddressFamily(_AF_RXRPC) } + + /// Nokia cellular modem IPC/RPC interface + @_alwaysEmitIntoClient + static var phonet: SocketAddressFamily { SocketAddressFamily(_AF_PHONET) } + + /// IEEE 802.15.4 WPAN (wireless personal area network) raw packet protocol + @_alwaysEmitIntoClient + static var ieee802154: SocketAddressFamily { SocketAddressFamily(_AF_IEEE802154) } + + /// Ericsson's Communication CPU to Application CPU interface (CAIF) protocol + @_alwaysEmitIntoClient + static var caif: SocketAddressFamily { SocketAddressFamily(_AF_CAIF) } + + /// Interface to kernel crypto API + @_alwaysEmitIntoClient + static var crypto: SocketAddressFamily { SocketAddressFamily(_AF_ALG) } + + /// KCM (kernel connection multiplexer) interface + @_alwaysEmitIntoClient + static var kcm: SocketAddressFamily { SocketAddressFamily(_AF_KCM) } + + /// Qualcomm IPC router interface protocol + @_alwaysEmitIntoClient + static var qipcrtr: SocketAddressFamily { SocketAddressFamily(_AF_QIPCRTR) } + + /// SMC-R (shared memory communications over RDMA) protocol + /// and SMC-D (shared memory communications, direct memory access) protocol for intra-node z/VM quest interaction. + @_alwaysEmitIntoClient + static var smc: SocketAddressFamily { SocketAddressFamily(_AF_SMC) } + + /// XDP (express data path) interface. + @_alwaysEmitIntoClient + static var xdp: SocketAddressFamily { SocketAddressFamily(_AF_XDP) } +} +#endif + +#if os(Windows) +public extension SocketAddressFamily { + + /// NetBIOS protocol + @_alwaysEmitIntoClient + static var netbios: SocketAddressFamily { SocketAddressFamily(_AF_NETBIOS) } + + /// IrDA protocol + @_alwaysEmitIntoClient + static var irda: SocketAddressFamily { SocketAddressFamily(_AF_IRDA) } + + /// Bluetooth protocol + @_alwaysEmitIntoClient + static var bluetooth: SocketAddressFamily { SocketAddressFamily(_AF_BTH) } +} +#endif diff --git a/Sources/System/Socket/SocketFlags.swift b/Sources/System/Socket/SocketFlags.swift new file mode 100644 index 00000000..e2061434 --- /dev/null +++ b/Sources/System/Socket/SocketFlags.swift @@ -0,0 +1,56 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 - 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +#if os(Linux) +/// Flags when opening sockets. +@frozen +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +public struct SocketFlags: OptionSet, Hashable, Codable { + + /// The raw C file events. + @_alwaysEmitIntoClient + public let rawValue: CInt + + /// Create a strongly-typed file events from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ cValue: CInterop.SocketType) { self.init(rawValue: numericCast(cValue.rawValue)) } +} + +public extension SocketFlags { + + /// Set the `O_NONBLOCK` file status flag on the open file description referred to by the new file + /// descriptor. Using this flag saves extra calls to `fcntl()` to achieve the same result. + @_alwaysEmitIntoClient + static var nonBlocking: SocketFlags { SocketFlags(_SOCK_NONBLOCK) } + + /// Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. + @_alwaysEmitIntoClient + static var closeOnExec: SocketFlags { SocketFlags(_SOCK_CLOEXEC) } +} + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +extension SocketFlags: CustomStringConvertible, CustomDebugStringConvertible +{ + /// A textual representation of the open options. + @inline(never) + public var description: String { + let descriptions: [(Element, StaticString)] = [ + (.nonBlocking, ".nonBlocking"), + (.closeOnExec, ".closeOnExec") + ] + return _buildDescription(descriptions) + } + + /// A textual representation of the open options, suitable for debugging. + public var debugDescription: String { self.description } +} +#endif diff --git a/Sources/System/Socket/SocketOperations.swift b/Sources/System/Socket/SocketOperations.swift new file mode 100644 index 00000000..de4e2e37 --- /dev/null +++ b/Sources/System/Socket/SocketOperations.swift @@ -0,0 +1,508 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +extension FileDescriptor { + + /// Creates an endpoint for communication and returns a descriptor. + /// + /// - Parameters: + /// - protocolID: The protocol which will be used for communication. + /// - retryOnInterrupt: Whether to retry the read operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The file descriptor of the opened socket. + /// + @_alwaysEmitIntoClient + public static func socket( + _ protocolID: T, + retryOnInterrupt: Bool = true + ) throws -> FileDescriptor { + try _socket(T.family, type: protocolID.type.rawValue, protocol: protocolID.rawValue, retryOnInterrupt: retryOnInterrupt).get() + } + + #if os(Linux) + /// Creates an endpoint for communication and returns a descriptor. + /// + /// - Parameters: + /// - protocolID: The protocol which will be used for communication. + /// - flags: Flags to set when opening the socket. + /// - retryOnInterrupt: Whether to retry the read operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The file descriptor of the opened socket. + /// + @_alwaysEmitIntoClient + public static func socket( + _ protocolID: T, + flags: SocketFlags, + retryOnInterrupt: Bool = true + ) throws -> FileDescriptor { + try _socket(T.family, type: protocolID.type.rawValue | flags.rawValue, protocol: protocolID.rawValue, retryOnInterrupt: retryOnInterrupt).get() + } + #endif + + @usableFromInline + internal static func _socket( + _ family: SocketAddressFamily, + type: CInt, + protocol protocolID: Int32, + retryOnInterrupt: Bool + ) -> Result { + valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_socket(family.rawValue, type, protocolID) + }.map(FileDescriptor.init(socket:)) + } + + /// Creates an endpoint for communication and returns a descriptor. + /// + /// - Parameters: + /// - protocolID: The protocol which will be used for communication. + /// - flags: Flags to set when opening the socket. + /// - retryOnInterrupt: Whether to retry the read operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The file descriptor of the opened socket. + /// + @_alwaysEmitIntoClient + public static func socket( + _ protocolID: Address.ProtocolID, + bind address: Address, + retryOnInterrupt: Bool = true + ) throws -> FileDescriptor { + return try _socket( + address: address, + type: protocolID.type.rawValue, + protocol: protocolID.rawValue, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + #if os(Linux) + /// Creates an endpoint for communication and returns a descriptor. + /// + /// - Parameters: + /// - protocolID: The protocol which will be used for communication. + /// - flags: Flags to set when opening the socket. + /// - retryOnInterrupt: Whether to retry the read operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The file descriptor of the opened socket. + /// + @_alwaysEmitIntoClient + public static func socket( + _ protocolID: Address.ProtocolID, + bind address: Address, + flags: SocketFlags, + retryOnInterrupt: Bool = true + ) throws -> FileDescriptor { + return try _socket( + address: address, + type: protocolID.type.rawValue | flags.rawValue, + protocol: protocolID.rawValue, + retryOnInterrupt: retryOnInterrupt + ).get() + } + #endif + + @usableFromInline + internal static func _socket( + address: Address, + type: CInt, + protocol protocolID: Int32, + retryOnInterrupt: Bool + ) -> Result { + return _socket( + Address.family, + type: type, + protocol: protocolID, + retryOnInterrupt: retryOnInterrupt + )._closeIfThrows { fileDescriptor in + fileDescriptor + ._bind(address, retryOnInterrupt: retryOnInterrupt) + .map { fileDescriptor } + } + } + + /// Assigns the address specified to the socket referred to by the file descriptor. + /// + /// - Parameter address: Specifies the address to bind the socket. + /// - Parameter retryOnInterrupt: Whether to retry the open operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// + /// The corresponding C function is `bind`. + @_alwaysEmitIntoClient + public func bind( + _ address: Address, + retryOnInterrupt: Bool = true + ) throws { + try _bind(address, retryOnInterrupt: retryOnInterrupt).get() + } + + @usableFromInline + internal func _bind( + _ address: T, + retryOnInterrupt: Bool + ) -> Result<(), Errno> { + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + address.withUnsafePointer { (addressPointer, length) in + system_bind(T.family.rawValue, addressPointer, length) + } + } + } + + /// Set the option specified for the socket associated with the file descriptor. + /// + /// - Parameter option: Socket option value to set. + /// - Parameter retryOnInterrupt: Whether to retry the open operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// + /// The method corresponds to the C function `setsockopt`. + @_alwaysEmitIntoClient + public func setSocketOption( + _ option: T, + retryOnInterrupt: Bool = true + ) throws { + try _setSocketOption(option, retryOnInterrupt: retryOnInterrupt).get() + } + + @usableFromInline + internal func _setSocketOption( + _ option: T, + retryOnInterrupt: Bool + ) -> Result<(), Errno> { + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + option.withUnsafeBytes { bufferPointer in + system_setsockopt(self.rawValue, T.ID.optionLevel.rawValue, T.id.rawValue, bufferPointer.baseAddress!, UInt32(bufferPointer.count)) + } + } + } + + /// Retrieve the value associated with the option specified for the socket associated with the file descriptor. + /// + /// - Parameter option: Type of `SocketOption` to retrieve. + /// - Parameter retryOnInterrupt: Whether to retry the open operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// + /// The method corresponds to the C function `getsockopt`. + @_alwaysEmitIntoClient + public func getSocketOption( + _ option: T.Type, + retryOnInterrupt: Bool = true + ) throws -> T { + return try _getSocketOption(option, retryOnInterrupt: retryOnInterrupt) + } + + @usableFromInline + internal func _getSocketOption( + _ option: T.Type, + retryOnInterrupt: Bool + ) throws -> T { + return try T.withUnsafeBytes { bufferPointer in + var length = UInt32(bufferPointer.count) + guard system_getsockopt(self.rawValue, T.ID.optionLevel.rawValue, T.id.rawValue, bufferPointer.baseAddress!, &length) != -1 else { + throw Errno.current + } + } + } + + /// Send a message from a socket. + /// + /// - Parameters: + /// - buffer: The region of memory that contains the data being sent. + /// - flags: see `send(2)` + /// - retryOnInterrupt: Whether to retry the send operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The number of bytes that were sent. + /// + /// The corresponding C function is `send`. + @_alwaysEmitIntoClient + public func send( + _ buffer: UnsafeRawBufferPointer, + flags: MessageFlags = [], + retryOnInterrupt: Bool = true + ) throws -> Int { + try _send(buffer, flags: flags, retryOnInterrupt: retryOnInterrupt).get() + } + + /// Send a message from a socket. + /// + /// - Parameters: + /// - data: The sequence of bytes being sent. + /// - address: Address of destination client. + /// - flags: see `send(2)` + /// - retryOnInterrupt: Whether to retry the send operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The number of bytes that were sent. + /// + /// The corresponding C function is `send`. + public func send( + _ data: Data, + flags: MessageFlags = [], + retryOnInterrupt: Bool = true + ) throws -> Int where Data: Sequence, Data.Element == UInt8 { + try data._withRawBufferPointer { dataPointer in + _send(dataPointer, flags: flags, retryOnInterrupt: retryOnInterrupt) + }.get() + } + + @usableFromInline + internal func _send( + _ buffer: UnsafeRawBufferPointer, + flags: MessageFlags, + retryOnInterrupt: Bool + ) -> Result { + valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_send(self.rawValue, buffer.baseAddress!, buffer.count, flags.rawValue) + } + } + + /// Send a message from a socket. + /// + /// - Parameters: + /// - buffer: The region of memory that contains the data being sent. + /// - address: Address of destination client. + /// - flags: see `send(2)` + /// - retryOnInterrupt: Whether to retry the send operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The number of bytes that were sent. + /// + /// The corresponding C function is `send`. + @_alwaysEmitIntoClient + public func send( + _ buffer: UnsafeRawBufferPointer, + to address: Address, + flags: MessageFlags = [], + retryOnInterrupt: Bool = true + ) throws -> Int { + try _send(buffer, to: address, flags: flags, retryOnInterrupt: retryOnInterrupt).get() + } + + /// Send a message from a socket. + /// + /// - Parameters: + /// - data: The sequence of bytes being sent. + /// - address: Address of destination client. + /// - flags: see `send(2)` + /// - retryOnInterrupt: Whether to retry the send operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The number of bytes that were sent. + /// + /// The corresponding C function is `send`. + public func send( + _ data: Data, + to address: Address, + flags: MessageFlags = [], + retryOnInterrupt: Bool = true + ) throws -> Int where Address: SocketAddress, Data: Sequence, Data.Element == UInt8 { + try data._withRawBufferPointer { dataPointer in + _send(dataPointer, to: address, flags: flags, retryOnInterrupt: retryOnInterrupt) + }.get() + } + + /// `send()` + @usableFromInline + internal func _send( + _ data: UnsafeRawBufferPointer, + to address: T, + flags: MessageFlags, + retryOnInterrupt: Bool + ) -> Result { + valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + address.withUnsafePointer { (addressPointer, addressLength) in + system_sendto(self.rawValue, data.baseAddress, data.count, flags.rawValue, addressPointer, addressLength) + } + } + } + + /// Receive a message from a socket. + /// + /// - Parameters: + /// - buffer: The region of memory to receive into. + /// - flags: see `recv(2)` + /// - retryOnInterrupt: Whether to retry the receive operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The number of bytes that were received. + /// + /// The corresponding C function is `recv`. + @_alwaysEmitIntoClient + public func receive( + into buffer: UnsafeMutableRawBufferPointer, + flags: MessageFlags = [], + retryOnInterrupt: Bool = true + ) throws -> Int { + try _receive( + into: buffer, flags: flags, retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _receive( + into buffer: UnsafeMutableRawBufferPointer, + flags: MessageFlags, + retryOnInterrupt: Bool + ) -> Result { + valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_recv(self.rawValue, buffer.baseAddress!, buffer.count, flags.rawValue) + } + } + + @usableFromInline + internal func _recieve( + _ dataBuffer: UnsafeMutableRawBufferPointer, + flags: MessageFlags, + retryOnInterrupt: Bool + ) -> Result { + valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_recv(self.rawValue, dataBuffer.baseAddress, dataBuffer.count, flags.rawValue) + } + } + + /// Listen for connections on a socket. + /// + /// Only applies to sockets of connection type `.stream`. + /// + /// - Parameters: + /// - backlog: the maximum length for the queue of pending connections + /// - retryOnInterrupt: Whether to retry the receive operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// + /// The corresponding C function is `listen`. + @_alwaysEmitIntoClient + public func listen( + backlog: Int, + retryOnInterrupt: Bool = true + ) throws { + try _listen(backlog: Int32(backlog), retryOnInterrupt: retryOnInterrupt).get() + } + + @usableFromInline + internal func _listen( + backlog: Int32, + retryOnInterrupt: Bool + ) -> Result<(), Errno> { + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_listen(self.rawValue, backlog) + } + } + + /// Accept a connection on a socket. + /// + /// - Parameters: + /// - address: The type of the `SocketAddress` expected for the new connection. + /// - retryOnInterrupt: Whether to retry the receive operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: A tuple containing the file descriptor and address of the new connection. + /// + /// The corresponding C function is `accept`. + @_alwaysEmitIntoClient + public func accept( + _ address: Address.Type, + retryOnInterrupt: Bool = true + ) throws -> (FileDescriptor, Address) { + return try _accept(Address.self, retryOnInterrupt: retryOnInterrupt).get() + } + + @usableFromInline + internal func _accept( + _ address: Address.Type, + retryOnInterrupt: Bool + ) -> Result<(FileDescriptor, Address), Errno> { + var result: Result = .success(0) + let address = Address.withUnsafePointer { socketPointer, socketLength in + var length = socketLength + result = valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_accept(self.rawValue, socketPointer, &length) + } + } + return result.map { (FileDescriptor(socket: $0), address) } + } + + /// Accept a connection on a socket. + /// + /// - Parameters: + /// - retryOnInterrupt: Whether to retry the receive operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The file descriptor of the new connection. + /// + /// The corresponding C function is `accept`. + @_alwaysEmitIntoClient + public func accept( + retryOnInterrupt: Bool = true + ) throws -> FileDescriptor { + return try _accept(retryOnInterrupt: retryOnInterrupt).get() + } + + @usableFromInline + internal func _accept( + retryOnInterrupt: Bool + ) -> Result { + var length: UInt32 = 0 + return valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_accept(self.rawValue, nil, &length) + }.map(FileDescriptor.init(socket:)) + } + + /// Initiate a connection on a socket. + /// + /// - Parameters: + /// - address: The peer address. + /// - retryOnInterrupt: Whether to retry the receive operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The file descriptor of the new connection. + /// + /// The corresponding C function is `connect`. + @_alwaysEmitIntoClient + public func connect( + to address: Address, + retryOnInterrupt: Bool = true + ) throws { + try _connect(to: address, retryOnInterrupt: retryOnInterrupt).get() + } + + /// The `connect()` function shall attempt to make a connection on a socket. + @usableFromInline + internal func _connect( + to address: Address, + retryOnInterrupt: Bool + ) -> Result<(), Errno> { + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + address.withUnsafePointer { (addressPointer, addressLength) in + system_connect(self.rawValue, addressPointer, addressLength) + } + } + } +} diff --git a/Sources/System/Socket/SocketOption.swift b/Sources/System/Socket/SocketOption.swift new file mode 100644 index 00000000..7627b8bd --- /dev/null +++ b/Sources/System/Socket/SocketOption.swift @@ -0,0 +1,84 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +/// POSIX Socket Option ID +public protocol SocketOption { + + associatedtype ID: SocketOptionID + + static var id: ID { get } + + func withUnsafeBytes(_ pointer: ((UnsafeRawBufferPointer) throws -> (Result))) rethrows -> Result + + static func withUnsafeBytes( + _ body: (UnsafeMutableRawBufferPointer) throws -> () + ) rethrows -> Self +} + +public protocol BooleanSocketOption: SocketOption { + + init(_ boolValue: Bool) + + var boolValue: Bool { get } +} + +extension BooleanSocketOption where Self: ExpressibleByBooleanLiteral { + + @_alwaysEmitIntoClient + public init(booleanLiteral boolValue: Bool) { + self.init(boolValue) + } +} + +public extension BooleanSocketOption { + + func withUnsafeBytes(_ pointer: ((UnsafeRawBufferPointer) throws -> (Result))) rethrows -> Result { + return try Swift.withUnsafeBytes(of: boolValue.cInt) { bufferPointer in + try pointer(bufferPointer) + } + } + + static func withUnsafeBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> ()) rethrows -> Self { + var value: CInt = 0 + try Swift.withUnsafeMutableBytes(of: &value, body) + return Self.init(Bool(value)) + } +} + +/// Platform Socket Option +public extension GenericSocketOption { + + @frozen + struct Debug: BooleanSocketOption, Equatable, Hashable, ExpressibleByBooleanLiteral { + + @_alwaysEmitIntoClient + public static var id: GenericSocketOption { .debug } + + public var boolValue: Bool + + @_alwaysEmitIntoClient + public init(_ boolValue: Bool) { + self.boolValue = boolValue + } + } + + @frozen + struct KeepAlive: BooleanSocketOption, Equatable, Hashable, ExpressibleByBooleanLiteral { + + @_alwaysEmitIntoClient + public static var id: GenericSocketOption { .keepAlive } + + public var boolValue: Bool + + @_alwaysEmitIntoClient + public init(_ boolValue: Bool) { + self.boolValue = boolValue + } + } +} diff --git a/Sources/System/Socket/SocketOptionID.swift b/Sources/System/Socket/SocketOptionID.swift new file mode 100644 index 00000000..80af54f2 --- /dev/null +++ b/Sources/System/Socket/SocketOptionID.swift @@ -0,0 +1,45 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +/// POSIX Socket Option ID +public protocol SocketOptionID: RawRepresentable { + + static var optionLevel: SocketOptionLevel { get } + + init?(rawValue: Int32) + + var rawValue: Int32 { get } +} + +@frozen +public struct GenericSocketOption: RawRepresentable, Equatable, Hashable, SocketOptionID { + + @_alwaysEmitIntoClient + public static var optionLevel: SocketOptionLevel { .default } + + /// The raw socket address family identifier. + @_alwaysEmitIntoClient + public let rawValue: CInt + + /// Creates a strongly-typed socket address family from a raw address family identifier. + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ raw: CInt) { self.init(rawValue: raw) } +} + +public extension GenericSocketOption { + + @_alwaysEmitIntoClient + static var debug: GenericSocketOption { GenericSocketOption(_SO_DEBUG) } + + @_alwaysEmitIntoClient + static var keepAlive: GenericSocketOption { GenericSocketOption(_SO_KEEPALIVE) } +} diff --git a/Sources/System/Socket/SocketOptionLevel.swift b/Sources/System/Socket/SocketOptionLevel.swift new file mode 100644 index 00000000..c52128de --- /dev/null +++ b/Sources/System/Socket/SocketOptionLevel.swift @@ -0,0 +1,41 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +// POSIX Socket Option Level +@frozen +public struct SocketOptionLevel: RawRepresentable, Hashable, Codable { + + /// The raw socket address family identifier. + @_alwaysEmitIntoClient + public let rawValue: CInt + + /// Creates a strongly-typed socket address family from a raw address family identifier. + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ raw: CInt) { self.init(rawValue: raw) } +} + +public extension SocketOptionLevel { + + @_alwaysEmitIntoClient + static var `default`: SocketOptionLevel { SocketOptionLevel(_SOL_SOCKET) } +} + +#if os(Linux) +public extension SocketOptionLevel { + + @_alwaysEmitIntoClient + static var netlink: SocketOptionLevel { SocketOptionLevel(_SOL_NETLINK) } + + @_alwaysEmitIntoClient + static var bluetooth: SocketOptionLevel { SocketOptionLevel(_SOL_BLUETOOTH) } +} +#endif diff --git a/Sources/System/Socket/SocketProtocol.swift b/Sources/System/Socket/SocketProtocol.swift new file mode 100644 index 00000000..d8eb870d --- /dev/null +++ b/Sources/System/Socket/SocketProtocol.swift @@ -0,0 +1,36 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +/// POSIX Socket Protocol +public protocol SocketProtocol: RawRepresentable { + + static var family: SocketAddressFamily { get } + + var type: SocketType { get } + + init?(rawValue: Int32) + + var rawValue: Int32 { get } +} + +/// Unix Protocol Family +public enum UnixProtocol: Int32, Codable, SocketProtocol { + + case raw = 0 + + @_alwaysEmitIntoClient + public static var family: SocketAddressFamily { .unix } + + @_alwaysEmitIntoClient + public var type: SocketType { + switch self { + case .raw: return .raw + } + } +} diff --git a/Sources/System/Socket/SocketType.swift b/Sources/System/Socket/SocketType.swift new file mode 100644 index 00000000..32fd09dd --- /dev/null +++ b/Sources/System/Socket/SocketType.swift @@ -0,0 +1,69 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +/// POSIX Socket Type +@frozen +public struct SocketType: RawRepresentable, Hashable, Codable { + + /// The raw socket type identifier. + @_alwaysEmitIntoClient + public let rawValue: CInt + + /// Creates a strongly-typed socket type from a raw socket type identifier. + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ cValue: CInterop.SocketType) { + #if os(Linux) + self.init(rawValue: numericCast(cValue.rawValue)) + #else + self.init(rawValue: cValue) + #endif + } +} + +public extension SocketType { + + /// Stream socket + /// + /// Provides sequenced, reliable, two-way, connection-based byte streams. + /// An out-of-band data transmission mechanism may be supported. + @_alwaysEmitIntoClient + static var stream: SocketType { SocketType(_SOCK_STREAM) } + + /// Supports datagrams (connectionless, unreliable messages of a fixed maximum length). + @_alwaysEmitIntoClient + static var datagram: SocketType { SocketType(_SOCK_DGRAM) } + + /// Provides raw network protocol access. + @_alwaysEmitIntoClient + static var raw: SocketType { SocketType(_SOCK_RAW) } + + /// Provides a reliable datagram layer that does not guarantee ordering. + @_alwaysEmitIntoClient + static var reliableDatagramMessage: SocketType { SocketType(_SOCK_RDM) } + + /// Provides a sequenced, reliable, two-way connection-based data transmission + /// path for datagrams of fixed maximum length; a consumer is required to read + /// an entire packet with each input system call. + @_alwaysEmitIntoClient + static var sequencedPacket: SocketType { SocketType(_SOCK_SEQPACKET) } +} + +#if os(Linux) +public extension SocketType { + + /// Datagram Congestion Control Protocol + /// + /// Linux specific way of getting packets at the dev level. + @_alwaysEmitIntoClient + static var datagramCongestionControlProtocol: SocketType { SocketType(_SOCK_DCCP) } +} +#endif diff --git a/Sources/System/Util.swift b/Sources/System/Util.swift index c038d461..72f49994 100644 --- a/Sources/System/Util.swift +++ b/Sources/System/Util.swift @@ -127,3 +127,16 @@ extension MutableCollection where Element: Equatable { } } } + +internal extension Bool { + + @usableFromInline + init(_ cInt: CInt) { + self = cInt != 0 + } + + @usableFromInline + var cInt: CInt { + self ? 1 : 0 + } +}