//===----------------------------------------------------------------------===// // // 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 canImport(Network) import Foundation import NIOCore import NIOConcurrencyHelpers import NIOFoundationCompat import NIOTLS import Dispatch import Network import Security import Atomics /// Channel options for the connection channel. @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) struct TransportServicesChannelOptions { /// Whether autoRead is enabled for this channel. internal var autoRead: Bool = true /// Whether we support remote half closure. If not true, remote half closure will /// cause connection drops. internal var supportRemoteHalfClosure: Bool = false /// Whether this channel should wait for the connection to become active. internal var waitForActivity: Bool = true } internal struct AddressCache { // deliberately lets because they must always be updated together (so forcing `init` is useful). let local: SocketAddress? let remote: SocketAddress? init(local: SocketAddress?, remote: SocketAddress?) { self.local = local self.remote = remote } } /// A structure that manages backpressure signaling on this channel. @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) internal struct BackpressureManager { /// Whether the channel is writable, given the current watermark state. /// /// This is an atomic only because the channel writability flag needs to be safe to access from multiple /// threads. All activity in this structure itself is expected to be thread-safe. /// /// All code that operates on this atomic uses load/store, not compareAndSwap. This is because we know /// that this atomic is only ever written from one thread: the event loop thread. All unsynchronized /// access is only reading. As a result, we don't have racing writes, and don't need CAS. This is good, /// because in most cases these loads/stores will be free, as the user will never actually check the /// channel writability from another thread, meaning this cache line is uncontended. CAS is never free: /// it always has some substantial runtime cost over loads/stores. let writable = ManagedAtomic(true) /// The number of bytes outstanding on the network. private var outstandingBytes: Int = 0 /// The watermarks currently configured by the user. private(set) var waterMarks = ChannelOptions.Types.WriteBufferWaterMark(low: 32 * 1024, high: 64 * 1024) /// Adds `newBytes` to the queue of outstanding bytes, and returns whether this /// has caused a writability change. /// /// - parameters: /// - newBytes: the number of bytes queued to send, but not yet sent. /// - returns: Whether the state changed. mutating func writabilityChanges(whenQueueingBytes newBytes: Int) -> Bool { self.outstandingBytes += newBytes if self.outstandingBytes > self.waterMarks.high && self.writable.load(ordering: .relaxed) { self.writable.store(false, ordering: .relaxed) return true } return false } /// Removes `sentBytes` from the queue of outstanding bytes, and returns whether this /// has caused a writability change. /// /// - parameters: /// - newBytes: the number of bytes sent to the network. /// - returns: Whether the state changed. mutating func writabilityChanges(whenBytesSent sentBytes: Int) -> Bool { self.outstandingBytes -= sentBytes if self.outstandingBytes < self.waterMarks.low && !self.writable.load(ordering: .relaxed) { self.writable.store(true, ordering: .relaxed) return true } return false } /// Updates the watermarks to `waterMarks`, and returns whether this change has changed the /// writability state of the channel. /// /// - parameters: /// - waterMarks: The new waterMarks to use. /// - returns: Whether the state changed. mutating func writabilityChanges( whenUpdatingWaterMarks waterMarks: ChannelOptions.Types.WriteBufferWaterMark ) -> Bool { let writable = self.writable.load(ordering: .relaxed) self.waterMarks = waterMarks if writable && self.outstandingBytes > self.waterMarks.high { self.writable.store(false, ordering: .relaxed) return true } else if !writable && self.outstandingBytes < self.waterMarks.low { self.writable.store(true, ordering: .relaxed) return true } return false } } @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { /// The `ByteBufferAllocator` for this `Channel`. public let allocator = ByteBufferAllocator() /// An `EventLoopFuture` that will complete when this channel is finally closed. public var closeFuture: EventLoopFuture { self.closePromise.futureResult } /// The parent `Channel` for this one, if any. public let parent: Channel? /// The `EventLoop` this `Channel` belongs to. internal let tsEventLoop: NIOTSEventLoop // This is really a constant (set in .init) but needs `self` to be constructed and therefore a `var`. // *Do not change* as this needs to accessed from arbitrary threads. private(set) var _pipeline: ChannelPipeline! = nil internal let closePromise: EventLoopPromise /// The underlying `NWConnection` that this `Channel` wraps. This is only non-nil /// after the initial connection attempt has been made. internal var connection: NWConnection? /// The minimum length of data to receive from this connection, until the content is complete. internal var minimumIncompleteReceiveLength: Int /// The maximum length of data to receive from this connection in a single completion. internal var maximumReceiveLength: Int /// The `DispatchQueue` that socket events for this connection will be dispatched onto. internal let connectionQueue: DispatchQueue /// An `EventLoopPromise` that will be succeeded or failed when a connection attempt succeeds or fails. internal var connectPromise: EventLoopPromise? internal var parameters: NWParameters { NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions) } /// The TCP options for this connection. private var tcpOptions: NWProtocolTCP.Options internal var nwOptions: NWProtocolTCP.Options { self.tcpOptions } /// The TLS options for this connection, if any. private var tlsOptions: NWProtocolTLS.Options? /// The state of this connection channel. internal var state: ChannelState = .idle /// The active state, used for safely reporting the channel state across threads. internal let isActive0 = ManagedAtomic(false) /// The kinds of channel activation this channel supports internal let supportedActivationType: ActivationType = .connect /// Whether a call to NWConnection.receive has been made, but the completion /// handler has not yet been invoked. internal var outstandingRead: Bool = false /// The options for this channel. internal var options = TransportServicesChannelOptions() /// Any pending writes that have yet to be delivered to the network stack. internal var pendingWrites = CircularBuffer(initialCapacity: 8) /// An object to keep track of pending writes and manage our backpressure signaling. internal var _backpressureManager = BackpressureManager() /// The value of SO_REUSEADDR. internal var reuseAddress = false /// The value of SO_REUSEPORT. internal var reusePort = false /// The value of the allowLocalEndpointReuse option. internal var allowLocalEndpointReuse = false /// Whether to use peer-to-peer connectivity when connecting to Bonjour services. internal var enablePeerToPeer = false /// The default multipath service type. internal var multipathServiceType = NWParameters.MultipathServiceType.disabled /// The cache of the local and remote socket addresses. Must be accessed using _addressCacheLock. internal var _addressCache = AddressCache(local: nil, remote: nil) internal var addressCache: AddressCache { get { self._addressCacheLock.withLock { self._addressCache } } set { return self._addressCacheLock.withLock { self._addressCache = newValue } } } /// A lock that guards the _addressCache. internal let _addressCacheLock = NIOLock() /// Create a `NIOTSConnectionChannel` on a given `NIOTSEventLoop`. /// /// Note that `NIOTSConnectionChannel` objects cannot be created on arbitrary loops types. internal init( eventLoop: NIOTSEventLoop, parent: Channel? = nil, qos: DispatchQoS? = nil, minimumIncompleteReceiveLength: Int = 1, maximumReceiveLength: Int = 8192, tcpOptions: NWProtocolTCP.Options, tlsOptions: NWProtocolTLS.Options? ) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() self.parent = parent self.minimumIncompleteReceiveLength = minimumIncompleteReceiveLength self.maximumReceiveLength = maximumReceiveLength self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos) self.tcpOptions = tcpOptions self.tlsOptions = tlsOptions // Must come last, as it requires self to be completely initialized. self._pipeline = ChannelPipeline(channel: self) } /// Create a `NIOTSConnectionChannel` with an already-established `NWConnection`. internal convenience init( wrapping connection: NWConnection, on eventLoop: NIOTSEventLoop, parent: Channel? = nil, qos: DispatchQoS? = nil, minimumIncompleteReceiveLength: Int = 1, maximumReceiveLength: Int = 8192, tcpOptions: NWProtocolTCP.Options, tlsOptions: NWProtocolTLS.Options? ) { self.init( eventLoop: eventLoop, parent: parent, qos: qos, minimumIncompleteReceiveLength: minimumIncompleteReceiveLength, maximumReceiveLength: maximumReceiveLength, tcpOptions: tcpOptions, tlsOptions: tlsOptions ) self.connection = connection } } // MARK:- NIOTSConnectionChannel implementation of Channel @available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) extension NIOTSConnectionChannel: Channel { func getChannelSpecificOption0