diff --git a/Sources/NIO/BaseStreamSocketChannel.swift b/Sources/NIO/BaseStreamSocketChannel.swift index 89dc9510..cb467c52 100644 --- a/Sources/NIO/BaseStreamSocketChannel.swift +++ b/Sources/NIO/BaseStreamSocketChannel.swift @@ -247,7 +247,7 @@ class BaseStreamSocketChannel: BaseSocketChannel return } - let data = data.forceAsIOData() + let data = self.unwrapData(data, as: IOData.self) if !self.pendingWrites.add(data: data, promise: promise) { self.pipeline.syncOperations.fireChannelWritabilityChanged() diff --git a/Sources/NIO/Bootstrap.swift b/Sources/NIO/Bootstrap.swift index 8f96fdd4..667be61e 100644 --- a/Sources/NIO/Bootstrap.swift +++ b/Sources/NIO/Bootstrap.swift @@ -362,7 +362,7 @@ public final class ServerBootstrap { ctxEventLoop.assertInEventLoop() future.flatMap { (_) -> EventLoopFuture in ctxEventLoop.assertInEventLoop() - guard !context.pipeline.destroyed else { + guard context.channel.isActive else { return context.eventLoop.makeFailedFuture(ChannelError.ioOnClosedChannel) } context.fireChannelRead(data) diff --git a/Sources/NIO/Embedded.swift b/Sources/NIO/Embedded.swift index 940fe824..b88f2d3e 100644 --- a/Sources/NIO/Embedded.swift +++ b/Sources/NIO/Embedded.swift @@ -237,7 +237,8 @@ class EmbeddedChannelCore: ChannelCore { } deinit { - assert(self.pipeline.destroyed, "leaked an open EmbeddedChannel, maybe forgot to call channel.finish()?") + assert(!self.isOpen && !self.isActive, + "leaked an open EmbeddedChannel, maybe forgot to call channel.finish()?") isOpen = false closePromise.succeed(()) } @@ -631,7 +632,7 @@ public final class EmbeddedChannel: Channel { } let elem = buffer.removeFirst() guard let t = self._channelCore.tryUnwrapData(elem, as: T.self) else { - throw WrongTypeError(expected: T.self, actual: type(of: elem.forceAs(type: Any.self))) + throw WrongTypeError(expected: T.self, actual: type(of: self._channelCore.tryUnwrapData(elem, as: Any.self)!)) } return t } diff --git a/Sources/NIO/FileDescriptor.swift b/Sources/NIO/FileDescriptor.swift index 8d808183..450c8f10 100644 --- a/Sources/NIO/FileDescriptor.swift +++ b/Sources/NIO/FileDescriptor.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,26 +12,6 @@ // //===----------------------------------------------------------------------===// -public protocol FileDescriptor { - - /// Will be called with the file descriptor if still open, if not it will - /// throw an `IOError`. - /// - /// The ownership of the file descriptor must not escape the `body` as it's completely managed by the - /// implementation of the `FileDescriptor` protocol. - /// - /// - parameters: - /// - body: The closure to execute if the `FileDescriptor` is still open. - /// - throws: If either the `FileDescriptor` was closed before or the closure throws by itself. - func withUnsafeFileDescriptor(_ body: (CInt) throws -> T) throws -> T - - /// `true` if this `FileDescriptor` is open (which means it was not closed yet). - var isOpen: Bool { get } - - /// Close this `FileDescriptor`. - func close() throws -} - extension FileDescriptor { internal static func setNonBlocking(fileDescriptor: CInt) throws { let flags = try Posix.fcntl(descriptor: fileDescriptor, command: F_GETFL, value: 0) diff --git a/Sources/NIO/Interfaces.swift b/Sources/NIO/NetworkDevices.swift similarity index 68% rename from Sources/NIO/Interfaces.swift rename to Sources/NIO/NetworkDevices.swift index ca34f7f7..077110e5 100644 --- a/Sources/NIO/Interfaces.swift +++ b/Sources/NIO/NetworkDevices.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -11,12 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -// -// Interfaces.swift -// NIO -// -// Created by Cory Benfield on 27/02/2018. -// #if os(Linux) || os(FreeBSD) || os(Android) import CNIOLinux @@ -371,158 +365,3 @@ extension NIONetworkDevice: Hashable { hasher.combine(self.interfaceIndex) } } - -/// A representation of a single network interface on a system. -@available(*, deprecated, renamed: "NIONetworkDevice") -public final class NIONetworkInterface { - // This is a class because in almost all cases this will carry - // four structs that are backed by classes, and so will incur 4 - // refcount operations each time it is copied. - - /// The name of the network interface. - public let name: String - - /// The address associated with the given network interface. - public let address: SocketAddress - - /// The netmask associated with this address, if any. - public let netmask: SocketAddress? - - /// The broadcast address associated with this socket interface, if it has one. Some - /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. - public let broadcastAddress: SocketAddress? - - /// The address of the peer on a point-to-point interface, if this is one. Some - /// interfaces do not have such an address: most of those have a `broadcastAddress` - /// instead. - public let pointToPointDestinationAddress: SocketAddress? - - /// If the Interface supports Multicast - public let multicastSupported: Bool - - /// The index of the interface, as provided by `if_nametoindex`. - public let interfaceIndex: Int - - /// Create a brand new network interface. - /// - /// This constructor will fail if NIO does not understand the format of the underlying - /// socket address family. This is quite common: for example, Linux will return AF_PACKET - /// addressed interfaces on most platforms, which NIO does not currently understand. -#if os(Windows) - internal init?(_ pAdapter: UnsafeMutablePointer, - _ pAddress: UnsafeMutablePointer) { - self.name = String(decodingCString: pAdapter.pointee.FriendlyName, - as: UTF16.self) - - guard let address = pAddress.pointee.Address.lpSockaddr.convert() else { - return nil - } - self.address = address - - // TODO: convert the prefix length to the mask itself - let v4mask: (UINT8) -> SocketAddress? = { _ in - var buffer: [CChar] = - Array(repeating: 0, count: Int(INET_ADDRSTRLEN)) - var mask: sockaddr_in = sockaddr_in() - mask.sin_family = ADDRESS_FAMILY(AF_INET) - _ = buffer.withUnsafeMutableBufferPointer { - try! NIOBSDSocket.inet_ntop(af: .inet, src: &mask, - dst: $0.baseAddress!, - size: INET_ADDRSTRLEN) - } - return SocketAddress(mask) - } - let v6mask: (UINT8) -> SocketAddress? = { _ in - var buffer: [CChar] = - Array(repeating: 0, count: Int(INET6_ADDRSTRLEN)) - var mask: sockaddr_in6 = sockaddr_in6() - mask.sin6_family = ADDRESS_FAMILY(AF_INET6) - _ = buffer.withUnsafeMutableBufferPointer { - try! NIOBSDSocket.inet_ntop(af: .inet6, src: &mask, - dst: $0.baseAddress!, - size: INET6_ADDRSTRLEN) - } - return SocketAddress(mask) - } - - switch pAddress.pointee.Address.lpSockaddr.pointee.sa_family { - case ADDRESS_FAMILY(AF_INET): - self.netmask = v4mask(pAddress.pointee.OnLinkPrefixLength) - self.interfaceIndex = Int(pAdapter.pointee.IfIndex) - case ADDRESS_FAMILY(AF_INET6): - self.netmask = v6mask(pAddress.pointee.OnLinkPrefixLength) - self.interfaceIndex = Int(pAdapter.pointee.Ipv6IfIndex) - default: - return nil - } - - // TODO(compnerd) handle broadcast/ppp/multicast information - self.broadcastAddress = nil - self.pointToPointDestinationAddress = nil - self.multicastSupported = false - } -#else - internal init?(_ caddr: ifaddrs) { - self.name = String(cString: caddr.ifa_name) - - guard caddr.ifa_addr != nil else { - return nil - } - - guard let address = caddr.ifa_addr!.convert() else { - return nil - } - self.address = address - - if let netmask = caddr.ifa_netmask { - self.netmask = netmask.convert() - } else { - self.netmask = nil - } - - if (caddr.ifa_flags & UInt32(IFF_BROADCAST)) != 0, let addr = caddr.broadaddr { - self.broadcastAddress = addr.convert() - self.pointToPointDestinationAddress = nil - } else if (caddr.ifa_flags & UInt32(IFF_POINTOPOINT)) != 0, let addr = caddr.dstaddr { - self.broadcastAddress = nil - self.pointToPointDestinationAddress = addr.convert() - } else { - self.broadcastAddress = nil - self.pointToPointDestinationAddress = nil - } - - if (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0 { - self.multicastSupported = true - } else { - self.multicastSupported = false - } - - do { - self.interfaceIndex = Int(try Posix.if_nametoindex(caddr.ifa_name)) - } catch { - return nil - } - } -#endif -} - -@available(*, deprecated, renamed: "NIONetworkDevice") -extension NIONetworkInterface: CustomDebugStringConvertible { - public var debugDescription: String { - let baseString = "Interface \(self.name): address \(self.address)" - let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : "" - return baseString + maskString - } -} - -@available(*, deprecated, renamed: "NIONetworkDevice") -extension NIONetworkInterface: Equatable { - public static func ==(lhs: NIONetworkInterface, rhs: NIONetworkInterface) -> Bool { - return lhs.name == rhs.name && - lhs.address == rhs.address && - lhs.netmask == rhs.netmask && - lhs.broadcastAddress == rhs.broadcastAddress && - lhs.pointToPointDestinationAddress == rhs.pointToPointDestinationAddress && - lhs.interfaceIndex == rhs.interfaceIndex - } -} diff --git a/Sources/NIO/SelectableChannel.swift b/Sources/NIO/SelectableChannel.swift new file mode 100644 index 00000000..0e8050ba --- /dev/null +++ b/Sources/NIO/SelectableChannel.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import NIOConcurrencyHelpers + +/// A `SelectableChannel` is a `Channel` that can be used with a `Selector` which notifies a user when certain events +/// are possible. On UNIX a `Selector` is usually an abstraction of `select`, `poll`, `epoll` or `kqueue`. +/// +/// - warning: `SelectableChannel` methods and properties are _not_ thread-safe (unless they also belong to `Channel`). +internal protocol SelectableChannel: Channel { + /// The type of the `Selectable`. A `Selectable` is usually wrapping a file descriptor that can be registered in a + /// `Selector`. + associatedtype SelectableType: Selectable + + var isOpen: Bool { get } + + /// The event(s) of interest. + var interestedEvent: SelectorEventSet { get } + + /// Called when the `SelectableChannel` is ready to be written. + func writable() + + /// Called when the `SelectableChannel` is ready to be read. + func readable() + + /// Called when the read side of the `SelectableChannel` hit EOF. + func readEOF() + + /// Called when the write side of the `SelectableChannel` hit EOF. + func writeEOF() + + /// Called when the `SelectableChannel` was reset (ie. is now unusable) + func reset() + + func register(selector: Selector, interested: SelectorEventSet) throws + + func deregister(selector: Selector, mode: CloseMode) throws + + func reregister(selector: Selector, interested: SelectorEventSet) throws +} + +/// Multicast is not supported on this interface. +public struct NIOMulticastNotSupportedError: Error { + public var device: NIONetworkDevice + + public init(device: NIONetworkDevice) { + self.device = device + } +} + +/// Multicast has not been properly implemented on this channel. +public struct NIOMulticastNotImplementedError: Error { + public init() {} +} diff --git a/Sources/NIO/SocketChannel.swift b/Sources/NIO/SocketChannel.swift index a5c4c822..6fdc7c7f 100644 --- a/Sources/NIO/SocketChannel.swift +++ b/Sources/NIO/SocketChannel.swift @@ -296,7 +296,7 @@ final class ServerSocketChannel: BaseSocketChannel { override public func channelRead0(_ data: NIOAny) { self.eventLoop.assertInEventLoop() - let ch = data.forceAsOther() as SocketChannel + let ch = self.unwrapData(data, as: SocketChannel.self) ch.eventLoop.execute { ch.register().flatMapThrowing { guard ch.isOpen else { @@ -669,7 +669,7 @@ final class DatagramChannel: BaseSocketChannel { } /// Buffer a write in preparation for a flush. override func bufferPendingWrite(data: NIOAny, promise: EventLoopPromise?) { - let data = data.forceAsByteEnvelope() + let data = self.unwrapData(data, as: AddressedEnvelope.self) if !self.pendingWrites.add(envelope: data, promise: promise) { assert(self.isActive) diff --git a/Sources/NIO/Utilities.swift b/Sources/NIO/Utilities.swift index fb2a9f0f..d5d360f1 100644 --- a/Sources/NIO/Utilities.swift +++ b/Sources/NIO/Utilities.swift @@ -123,7 +123,7 @@ public enum System { } while let concreteInterface = interface { - if let nioInterface = NIONetworkInterface(concreteInterface.pointee) { + if let nioInterface = NIONetworkInterface._construct(from: concreteInterface.pointee) { interfaces.append(nioInterface) } interface = concreteInterface.pointee.ifa_next diff --git a/Sources/NIO/AddressedEnvelope.swift b/Sources/NIOCore/AddressedEnvelope.swift similarity index 100% rename from Sources/NIO/AddressedEnvelope.swift rename to Sources/NIOCore/AddressedEnvelope.swift diff --git a/Sources/NIO/Channel.swift b/Sources/NIOCore/Channel.swift similarity index 89% rename from Sources/NIO/Channel.swift rename to Sources/NIOCore/Channel.swift index ece3f12c..833c7d87 100644 --- a/Sources/NIO/Channel.swift +++ b/Sources/NIOCore/Channel.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -166,42 +166,6 @@ public protocol NIOSynchronousChannelOptions { func getOption(_ option: Option) throws -> Option.Value } -/// A `SelectableChannel` is a `Channel` that can be used with a `Selector` which notifies a user when certain events -/// are possible. On UNIX a `Selector` is usually an abstraction of `select`, `poll`, `epoll` or `kqueue`. -/// -/// - warning: `SelectableChannel` methods and properties are _not_ thread-safe (unless they also belong to `Channel`). -internal protocol SelectableChannel: Channel { - /// The type of the `Selectable`. A `Selectable` is usually wrapping a file descriptor that can be registered in a - /// `Selector`. - associatedtype SelectableType: Selectable - - var isOpen: Bool { get } - - /// The event(s) of interest. - var interestedEvent: SelectorEventSet { get } - - /// Called when the `SelectableChannel` is ready to be written. - func writable() - - /// Called when the `SelectableChannel` is ready to be read. - func readable() - - /// Called when the read side of the `SelectableChannel` hit EOF. - func readEOF() - - /// Called when the write side of the `SelectableChannel` hit EOF. - func writeEOF() - - /// Called when the `SelectableChannel` was reset (ie. is now unusable) - func reset() - - func register(selector: Selector, interested: SelectorEventSet) throws - - func deregister(selector: Selector, mode: CloseMode) throws - - func reregister(selector: Selector, interested: SelectorEventSet) throws -} - /// Default implementations which will start on the head of the `ChannelPipeline`. extension Channel { @@ -414,20 +378,6 @@ extension ChannelError: Equatable { } /// The removal of a `ChannelHandler` using `ChannelPipeline.removeHandler` has been attempted more than once. public struct NIOAttemptedToRemoveHandlerMultipleTimesError: Error {} -/// Multicast is not supported on this interface. -public struct NIOMulticastNotSupportedError: Error { - public var device: NIONetworkDevice - - public init(device: NIONetworkDevice) { - self.device = device - } -} - -/// Multicast has not been properly implemented on this channel. -public struct NIOMulticastNotImplementedError: Error { - public init() {} -} - /// An `Channel` related event that is passed through the `ChannelPipeline` to notify the user. public enum ChannelEvent: Equatable { /// `ChannelOptions.allowRemoteHalfClosure` is `true` and input portion of the `Channel` was closed. diff --git a/Sources/NIO/ChannelHandler.swift b/Sources/NIOCore/ChannelHandler.swift similarity index 100% rename from Sources/NIO/ChannelHandler.swift rename to Sources/NIOCore/ChannelHandler.swift diff --git a/Sources/NIO/ChannelInvoker.swift b/Sources/NIOCore/ChannelInvoker.swift similarity index 100% rename from Sources/NIO/ChannelInvoker.swift rename to Sources/NIOCore/ChannelInvoker.swift diff --git a/Sources/NIO/ChannelOption.swift b/Sources/NIOCore/ChannelOption.swift similarity index 100% rename from Sources/NIO/ChannelOption.swift rename to Sources/NIOCore/ChannelOption.swift diff --git a/Sources/NIO/ChannelPipeline.swift b/Sources/NIOCore/ChannelPipeline.swift similarity index 100% rename from Sources/NIO/ChannelPipeline.swift rename to Sources/NIOCore/ChannelPipeline.swift diff --git a/Sources/NIO/DeadChannel.swift b/Sources/NIOCore/DeadChannel.swift similarity index 100% rename from Sources/NIO/DeadChannel.swift rename to Sources/NIOCore/DeadChannel.swift diff --git a/Sources/NIOCore/FileDescriptor.swift b/Sources/NIOCore/FileDescriptor.swift new file mode 100644 index 00000000..b97d874b --- /dev/null +++ b/Sources/NIOCore/FileDescriptor.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public protocol FileDescriptor { + + /// Will be called with the file descriptor if still open, if not it will + /// throw an `IOError`. + /// + /// The ownership of the file descriptor must not escape the `body` as it's completely managed by the + /// implementation of the `FileDescriptor` protocol. + /// + /// - parameters: + /// - body: The closure to execute if the `FileDescriptor` is still open. + /// - throws: If either the `FileDescriptor` was closed before or the closure throws by itself. + func withUnsafeFileDescriptor(_ body: (CInt) throws -> T) throws -> T + + /// `true` if this `FileDescriptor` is open (which means it was not closed yet). + var isOpen: Bool { get } + + /// Close this `FileDescriptor`. + func close() throws +} + diff --git a/Sources/NIO/FileHandle.swift b/Sources/NIOCore/FileHandle.swift similarity index 94% rename from Sources/NIO/FileHandle.swift rename to Sources/NIOCore/FileHandle.swift index 22b9c43f..afdbce6d 100644 --- a/Sources/NIO/FileHandle.swift +++ b/Sources/NIOCore/FileHandle.swift @@ -11,6 +11,13 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +#if os(Windows) +import ucrt +#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +import Darwin +#elseif os(Linux) || os(Android) +import Glibc +#endif /// A `NIOFileHandle` is a handle to an open file. /// @@ -45,7 +52,7 @@ public final class NIOFileHandle: FileDescriptor { /// - returns: A new `NIOFileHandle` with a fresh underlying file descriptor but shared seek pointer. public func duplicate() throws -> NIOFileHandle { return try withUnsafeFileDescriptor { fd in - NIOFileHandle(descriptor: try Posix.dup(descriptor: fd)) + NIOFileHandle(descriptor: try SystemCalls.dup(descriptor: fd)) } } @@ -66,7 +73,7 @@ public final class NIOFileHandle: FileDescriptor { public func close() throws { try withUnsafeFileDescriptor { fd in - try Posix.close(descriptor: fd) + try SystemCalls.close(descriptor: fd) } self.isOpen = false @@ -141,7 +148,7 @@ extension NIOFileHandle { /// - mode: Access mode. Default mode is `.read`. /// - flags: Additional POSIX flags. public convenience init(path: String, mode: Mode = .read, flags: Flags = .default) throws { - let fd = try Posix.open(file: path, oFlag: mode.posixFlags | O_CLOEXEC | flags.posixFlags, mode: flags.posixMode) + let fd = try SystemCalls.open(file: path, oFlag: mode.posixFlags | O_CLOEXEC | flags.posixFlags, mode: flags.posixMode) self.init(descriptor: fd) } diff --git a/Sources/NIO/FileRegion.swift b/Sources/NIOCore/FileRegion.swift similarity index 92% rename from Sources/NIO/FileRegion.swift rename to Sources/NIOCore/FileRegion.swift index b945a03c..c787ba72 100644 --- a/Sources/NIO/FileRegion.swift +++ b/Sources/NIOCore/FileRegion.swift @@ -11,6 +11,14 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +#if os(Windows) +import ucrt +#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +import Darwin +#elseif os(Linux) || os(Android) +import Glibc +#endif + /// A `FileRegion` represent a readable portion usually created to be sent over the network. /// @@ -80,8 +88,8 @@ extension FileRegion { /// - fileHandle: An open `NIOFileHandle` to the file. public init(fileHandle: NIOFileHandle) throws { let eof = try fileHandle.withUnsafeFileDescriptor { (fd: CInt) throws -> off_t in - let eof = try Posix.lseek(descriptor: fd, offset: 0, whence: SEEK_END) - try Posix.lseek(descriptor: fd, offset: 0, whence: SEEK_SET) + let eof = try SystemCalls.lseek(descriptor: fd, offset: 0, whence: SEEK_END) + try SystemCalls.lseek(descriptor: fd, offset: 0, whence: SEEK_SET) return eof } self.init(fileHandle: fileHandle, readerIndex: 0, endIndex: Int(eof)) diff --git a/Sources/NIOCore/IO.swift b/Sources/NIOCore/IO.swift index 71a81daf..da62e5ca 100644 --- a/Sources/NIOCore/IO.swift +++ b/Sources/NIOCore/IO.swift @@ -115,3 +115,26 @@ extension IOError: CustomStringConvertible { return reasonForError(errnoCode: self.errnoCode, reason: self.failureDescription) } } + +// FIXME: Duplicated with NIO. +/// An result for an IO operation that was done on a non-blocking resource. +enum CoreIOResult: Equatable { + + /// Signals that the IO operation could not be completed as otherwise we would need to block. + case wouldBlock(T) + + /// Signals that the IO operation was completed. + case processed(T) +} + +internal extension CoreIOResult where T: FixedWidthInteger { + var result: T { + switch self { + case .processed(let value): + return value + case .wouldBlock(_): + fatalError("cannot unwrap CoreIOResult") + } + } +} + diff --git a/Sources/NIO/IOData.swift b/Sources/NIOCore/IOData.swift similarity index 100% rename from Sources/NIO/IOData.swift rename to Sources/NIOCore/IOData.swift diff --git a/Sources/NIOCore/Interfaces.swift b/Sources/NIOCore/Interfaces.swift new file mode 100644 index 00000000..60be6aee --- /dev/null +++ b/Sources/NIOCore/Interfaces.swift @@ -0,0 +1,175 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +#if os(Linux) || os(FreeBSD) || os(Android) +import Glibc +import CNIOLinux +#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +import Darwin +#elseif os(Windows) +import let WinSDK.AF_INET +import let WinSDK.AF_INET6 + +import let WinSDK.INET_ADDRSTRLEN +import let WinSDK.INET6_ADDRSTRLEN + +import struct WinSDK.ADDRESS_FAMILY +import struct WinSDK.IP_ADAPTER_ADDRESSES +import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS + +import typealias WinSDK.UINT8 +#endif + +#if !os(Windows) +private extension ifaddrs { + var dstaddr: UnsafeMutablePointer? { + #if os(Linux) || os(Android) + return self.ifa_ifu.ifu_dstaddr + #elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return self.ifa_dstaddr + #endif + } + + var broadaddr: UnsafeMutablePointer? { + #if os(Linux) || os(Android) + return self.ifa_ifu.ifu_broadaddr + #elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return self.ifa_dstaddr + #endif + } +} +#endif + +/// A representation of a single network interface on a system. +@available(*, deprecated, renamed: "NIONetworkDevice") +public final class NIONetworkInterface { + // This is a class because in almost all cases this will carry + // four structs that are backed by classes, and so will incur 4 + // refcount operations each time it is copied. + + /// The name of the network interface. + public let name: String + + /// The address associated with the given network interface. + public let address: SocketAddress + + /// The netmask associated with this address, if any. + public let netmask: SocketAddress? + + /// The broadcast address associated with this socket interface, if it has one. Some + /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. + public let broadcastAddress: SocketAddress? + + /// The address of the peer on a point-to-point interface, if this is one. Some + /// interfaces do not have such an address: most of those have a `broadcastAddress` + /// instead. + public let pointToPointDestinationAddress: SocketAddress? + + /// If the Interface supports Multicast + public let multicastSupported: Bool + + /// The index of the interface, as provided by `if_nametoindex`. + public let interfaceIndex: Int + + fileprivate init?(_ caddr: ifaddrs) { + self.name = String(cString: caddr.ifa_name) + + guard caddr.ifa_addr != nil else { + return nil + } + + guard let address = caddr.ifa_addr!.convert() else { + return nil + } + self.address = address + + if let netmask = caddr.ifa_netmask { + self.netmask = netmask.convert() + } else { + self.netmask = nil + } + + if (caddr.ifa_flags & UInt32(IFF_BROADCAST)) != 0, let addr = caddr.broadaddr { + self.broadcastAddress = addr.convert() + self.pointToPointDestinationAddress = nil + } else if (caddr.ifa_flags & UInt32(IFF_POINTOPOINT)) != 0, let addr = caddr.dstaddr { + self.broadcastAddress = nil + self.pointToPointDestinationAddress = addr.convert() + } else { + self.broadcastAddress = nil + self.pointToPointDestinationAddress = nil + } + + if (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0 { + self.multicastSupported = true + } else { + self.multicastSupported = false + } + + do { + self.interfaceIndex = Int(try SystemCalls.if_nametoindex(caddr.ifa_name)) + } catch { + return nil + } + } + + // This is public just so we can avoid needing to pull over any of the System helpers: they can + // construct this type directly. Ideally, we'd have avoided even needing this in NIOCore. + public static func _construct(from caddr: ifaddrs) -> NIONetworkInterface? { + return NIONetworkInterface(caddr) + } +} + +@available(*, deprecated, renamed: "NIONetworkDevice") +extension NIONetworkInterface: CustomDebugStringConvertible { + public var debugDescription: String { + let baseString = "Interface \(self.name): address \(self.address)" + let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : "" + return baseString + maskString + } +} + +@available(*, deprecated, renamed: "NIONetworkDevice") +extension NIONetworkInterface: Equatable { + public static func ==(lhs: NIONetworkInterface, rhs: NIONetworkInterface) -> Bool { + return lhs.name == rhs.name && + lhs.address == rhs.address && + lhs.netmask == rhs.netmask && + lhs.broadcastAddress == rhs.broadcastAddress && + lhs.pointToPointDestinationAddress == rhs.pointToPointDestinationAddress && + lhs.interfaceIndex == rhs.interfaceIndex + } +} + +/// A helper extension for working with sockaddr pointers. +extension UnsafeMutablePointer where Pointee == sockaddr { + /// Converts the `sockaddr` to a `SocketAddress`. + fileprivate func convert() -> SocketAddress? { + switch NIOBSDSocket.AddressFamily(rawValue: CInt(pointee.sa_family)) { + case .inet: + return self.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { + SocketAddress($0.pointee) + } + case .inet6: + return self.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { + SocketAddress($0.pointee) + } + case .unix: + return self.withMemoryRebound(to: sockaddr_un.self, capacity: 1) { + SocketAddress($0.pointee) + } + default: + return nil + } + } +} diff --git a/Sources/NIO/NIOAny.swift b/Sources/NIOCore/NIOAny.swift similarity index 100% rename from Sources/NIO/NIOAny.swift rename to Sources/NIOCore/NIOAny.swift diff --git a/Sources/NIOCore/SystemCallHelpers.swift b/Sources/NIOCore/SystemCallHelpers.swift new file mode 100644 index 00000000..35a6425c --- /dev/null +++ b/Sources/NIOCore/SystemCallHelpers.swift @@ -0,0 +1,130 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// This file contains code that ensures errno is captured correctly when doing syscalls and no ARC traffic can happen inbetween that *could* change the errno +// value before we were able to read it. +// It's important that all static methods are declared with `@inline(never)` so it's not possible any ARC traffic happens while we need to read errno. +// +// Created by Norman Maurer on 11/10/17. +// +// This file arguably shouldn't be here in NIOCore, but due to early design decisions we accidentally exposed a few types that +// know about system calls into the core API (looking at you, FileHandle). As a result we need support for a small number of system calls. +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +import Darwin.C +#elseif os(Linux) || os(FreeBSD) || os(Android) +import Glibc +#elseif os(Windows) +import CNIOWindows +#else +#error("bad os") +#endif + +private let sysDup: @convention(c) (CInt) -> CInt = dup +private let sysClose: @convention(c) (CInt) -> CInt = close +private let sysOpenWithMode: @convention(c) (UnsafePointer, CInt, mode_t) -> CInt = open +private let sysLseek: @convention(c) (CInt, off_t, CInt) -> off_t = lseek +private let sysIfNameToIndex: @convention(c) (UnsafePointer?) -> CUnsignedInt = if_nametoindex + +private func isUnacceptableErrno(_ code: Int32) -> Bool { + switch code { + case EFAULT, EBADF: + return true + default: + return false + } +} + +private func preconditionIsNotUnacceptableErrno(err: CInt, where function: String) -> Void { + // strerror is documented to return "Unknown error: ..." for illegal value so it won't ever fail + precondition(!isUnacceptableErrno(err), "unacceptable errno \(err) \(String(cString: strerror(err)!)) in \(function))") +} + +/* + * Sorry, we really try hard to not use underscored attributes. In this case + * however we seem to break the inlining threshold which makes a system call + * take twice the time, ie. we need this exception. + */ +@inline(__always) +@discardableResult +internal func syscall(blocking: Bool, + where function: String = #function, + _ body: () throws -> T) + throws -> CoreIOResult { + while true { + let res = try body() + if res == -1 { + let err = errno + switch (err, blocking) { + case (EINTR, _): + continue + case (EWOULDBLOCK, true): + return .wouldBlock(0) + default: + preconditionIsNotUnacceptableErrno(err: err, where: function) + throw IOError(errnoCode: err, reason: function) + } + } + return .processed(res) + } +} + +enum SystemCalls { + @discardableResult + @inline(never) + internal static func dup(descriptor: CInt) throws -> CInt { + return try syscall(blocking: false) { + sysDup(descriptor) + }.result + } + + @inline(never) + internal static func close(descriptor: CInt) throws { + let res = sysClose(descriptor) + if res == -1 { + let err = errno + + // There is really nothing "sane" we can do when EINTR was reported on close. + // So just ignore it and "assume" everything is fine == we closed the file descriptor. + // + // For more details see: + // - https://bugs.chromium.org/p/chromium/issues/detail?id=269623 + // - https://lwn.net/Articles/576478/ + if err != EINTR { + preconditionIsNotUnacceptableErrno(err: err, where: #function) + throw IOError(errnoCode: err, reason: "close") + } + } + } + + @inline(never) + internal static func open(file: UnsafePointer, oFlag: CInt, mode: mode_t) throws -> CInt { + return try syscall(blocking: false) { + sysOpenWithMode(file, oFlag, mode) + }.result + } + + @discardableResult + @inline(never) + internal static func lseek(descriptor: CInt, offset: off_t, whence: CInt) throws -> off_t { + return try syscall(blocking: false) { + sysLseek(descriptor, offset, whence) + }.result + } + + @inline(never) + internal static func if_nametoindex(_ name: UnsafePointer?) throws -> CUnsignedInt { + return try syscall(blocking: false) { + sysIfNameToIndex(name) + }.result + } +} diff --git a/Sources/NIO/TypeAssistedChannelHandler.swift b/Sources/NIOCore/TypeAssistedChannelHandler.swift similarity index 100% rename from Sources/NIO/TypeAssistedChannelHandler.swift rename to Sources/NIOCore/TypeAssistedChannelHandler.swift diff --git a/Tests/NIOTestUtilsTests/ByteToMessageDecoderVerifierTest.swift b/Tests/NIOTestUtilsTests/ByteToMessageDecoderVerifierTest.swift index 18f15253..b5f0ae32 100644 --- a/Tests/NIOTestUtilsTests/ByteToMessageDecoderVerifierTest.swift +++ b/Tests/NIOTestUtilsTests/ByteToMessageDecoderVerifierTest.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2019-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,7 +12,8 @@ // //===----------------------------------------------------------------------===// -@testable import NIO +@testable import NIOCore +import NIO import NIOTestUtils import XCTest diff --git a/Tests/NIOTests/BaseObjectsTest.swift b/Tests/NIOTests/BaseObjectsTest.swift index 0a53f99d..d30cc40d 100644 --- a/Tests/NIOTests/BaseObjectsTest.swift +++ b/Tests/NIOTests/BaseObjectsTest.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import XCTest -@testable import NIO +@testable import NIOCore class BaseObjectTest: XCTestCase { func testNIOByteBufferConversion() { diff --git a/Tests/NIOTests/ChannelPipelineTest.swift b/Tests/NIOTests/ChannelPipelineTest.swift index 6b461e41..dd4c40b4 100644 --- a/Tests/NIOTests/ChannelPipelineTest.swift +++ b/Tests/NIOTests/ChannelPipelineTest.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -14,7 +14,8 @@ import XCTest import NIOConcurrencyHelpers -@testable import NIO +@testable import NIOCore +import NIO import NIOTestUtils private final class IndexWritingHandler: ChannelDuplexHandler { diff --git a/Tests/NIOTests/ChannelTests.swift b/Tests/NIOTests/ChannelTests.swift index 3470f703..cda177ca 100644 --- a/Tests/NIOTests/ChannelTests.swift +++ b/Tests/NIOTests/ChannelTests.swift @@ -254,7 +254,7 @@ public final class ChannelTests: XCTestCase { expectedSingleWritabilities: [Int]?, expectedVectorWritabilities: [[Int]]?, expectedFileWritabilities: [(Int, Int)]?, - returns: [IOResult], + returns: [NIO.IOResult], promiseStates: [[Bool]], file: StaticString = #file, line: UInt = #line) throws -> OverallWriteResult { @@ -2028,7 +2028,7 @@ public final class ChannelTests: XCTestCase { init(protocolFamily: NIOBSDSocket.ProtocolFamily) throws { try super.init(protocolFamily: protocolFamily, type: .stream, setNonBlocking: true) } - override func read(pointer: UnsafeMutableRawBufferPointer) throws -> IOResult { + override func read(pointer: UnsafeMutableRawBufferPointer) throws -> NIO.IOResult { defer { self.firstReadHappened = true } @@ -2443,11 +2443,11 @@ public final class ChannelTests: XCTestCase { try super.init(protocolFamily: .inet, type: .stream, setNonBlocking: true) } - override func write(pointer: UnsafeRawBufferPointer) throws -> IOResult { + override func write(pointer: UnsafeRawBufferPointer) throws -> NIO.IOResult { throw IOError(errnoCode: ETXTBSY, reason: "WriteAlwaysFailingSocket.write fake error") } - override func writev(iovecs: UnsafeBufferPointer) throws -> IOResult { + override func writev(iovecs: UnsafeBufferPointer) throws -> NIO.IOResult { throw IOError(errnoCode: ETXTBSY, reason: "WriteAlwaysFailingSocket.writev fake error") } } diff --git a/Tests/NIOTests/PendingDatagramWritesManagerTests.swift b/Tests/NIOTests/PendingDatagramWritesManagerTests.swift index a247fa66..2df84ea3 100644 --- a/Tests/NIOTests/PendingDatagramWritesManagerTests.swift +++ b/Tests/NIOTests/PendingDatagramWritesManagerTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -109,7 +109,7 @@ class PendingDatagramWritesManagerTests: XCTestCase { promises: [EventLoopPromise], expectedSingleWritabilities: [(Int, SocketAddress)]?, expectedVectorWritabilities: [[(Int, SocketAddress)]]?, - returns: [Result, Error>], + returns: [Result, Error>], promiseStates: [[Bool]], file: StaticString = #file, line: UInt = #line) throws -> OverallWriteResult {