Files
swift-mirror/stdlib/public/Darwin/Network/NWConnection.swift
Luciano Almeida 392baefc47 [stdlib][Qol] SR-11295 Removing stdlib unnecessary coercions (#27165)
* Removing unnecessary casts from stdlib

* Minor adjustments

* [stdlib][qol] Clean up error variable assign NSError

* Removing unnecessary coercions after removing DeclRefExpr restriction.
2019-12-11 07:30:25 -08:00

574 lines
26 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Dispatch
import Foundation
import _SwiftNetworkOverlayShims
/// An NWConnection is an object that represents a bi-directional data pipe between
/// a local endpoint and a remote endpoint.
///
/// A connection handles establishment of any transport, security, or application-level protocols
/// required to transmit and receive user data. Multiple protocol instances may be attempted during
/// the establishment phase of the connection.
// NOTE: older overlays had Network.NWConnection as the ObjC name.
// The two must coexist, so it was renamed. The old name must not be
// used in the new runtime. _TtC7Network13_NWConnection is the
// mangled name for Network._NWConnection.
@_objcRuntimeName(_TtC7Network13_NWConnection)
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
public final class NWConnection : CustomDebugStringConvertible {
public var debugDescription: String {
return String("\(self.nw)")
}
/// States a connection may be in
public enum State : Equatable {
/// The initial state prior to start
case setup
/// Waiting connections have not yet been started, or do not have a viable network
case waiting(NWError)
/// Preparing connections are actively establishing the connection
case preparing
/// Ready connections can send and receive data
case ready
/// Failed connections are disconnected and can no longer send or receive data
case failed(NWError)
/// Cancelled connections have been invalidated by the client and will send no more events
case cancelled
internal init(_ nw: nw_connection_state_t, _ err: nw_error_t?) {
switch nw {
case Network.nw_connection_state_invalid:
self = .setup
case Network.nw_connection_state_waiting:
if let error = NWError(err) {
self = .waiting(error)
} else {
self = .waiting(NWError.posix(.ENETDOWN))
}
case Network.nw_connection_state_preparing:
self = .preparing
case Network.nw_connection_state_ready:
self = .ready
case Network.nw_connection_state_failed:
if let error = NWError(err) {
self = .failed(error)
} else {
self = .failed(NWError.posix(.EINVAL))
}
case Network.nw_connection_state_cancelled:
self = .cancelled
default:
self = .cancelled
}
}
}
internal let nw : nw_connection_t
private var _state = NWConnection.State.setup
/// Access the current state of the connection
public var state: NWConnection.State {
return _state
}
private var _stateUpdateHandler: ((_ state: NWConnection.State) -> Void)?
/// Set a block to be called when the connection's state changes, which may be called
/// multiple times until the connection is cancelled.
public var stateUpdateHandler: ((_ state: NWConnection.State) -> Void)? {
set {
self._stateUpdateHandler = newValue
if let newValue = newValue {
nw_connection_set_state_changed_handler(self.nw) { (state, error) in
self._state = NWConnection.State(state, error)
newValue(self._state)
}
} else {
nw_connection_set_state_changed_handler(self.nw) { (state, error) in
self._state = NWConnection.State(state, error)
}
}
}
get {
return self._stateUpdateHandler
}
}
/// Retrieve the maximum datagram size that can be sent
/// on the connection. Any datagrams sent should be less
/// than or equal to this size.
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
public var maximumDatagramSize: Int {
get {
return Int(nw_connection_get_maximum_datagram_size(self.nw))
}
}
private var _currentPath: NWPath? = nil
/// Current path for the connection, which can be used to extract interface and effective endpoint information
public var currentPath: NWPath? {
get {
if let path = nw_connection_copy_current_path(self.nw) {
return NWPath(path)
}
return nil
}
}
private var _pathUpdateHandler: ((_ newPath: NWPath) -> Void)?
/// Set a block to be called when the connection's path has changed, which may be called
/// multiple times until the connection is cancelled.
public var pathUpdateHandler: ((_ newPath: NWPath) -> Void)? {
set {
self._pathUpdateHandler = newValue
if let newValue = newValue {
nw_connection_set_path_changed_handler(self.nw) { (nwPath) in
let newPath = NWPath(nwPath)
self._currentPath = newPath
newValue(newPath)
}
} else {
nw_connection_set_path_changed_handler(self.nw, nil)
}
}
get {
return self._pathUpdateHandler
}
}
private var _viabilityUpdateHandler: ((_ isViable: Bool) -> Void)?
/// Set a block to be called when the connection's viability changes, which may be called
/// multiple times until the connection is cancelled.
///
/// Connections that are not currently viable do not have a route, and packets will not be
/// sent or received. There is a possibility that the connection will become viable
/// again when network connectivity changes.
public var viabilityUpdateHandler: ((_ isViable: Bool) -> Void)? {
set {
self._viabilityUpdateHandler = newValue
if let newValue = newValue {
nw_connection_set_viability_changed_handler(self.nw) { (isViable) in
newValue(isViable)
}
} else {
nw_connection_set_viability_changed_handler(self.nw, nil)
}
}
get {
return self._viabilityUpdateHandler
}
}
private var _betterPathUpdateHandler: ((_ betterPathAvailable: Bool) -> Void)?
/// A better path being available indicates that the system thinks there is a preferred path or
/// interface to use, compared to the one this connection is actively using. As an example, the
/// connection is established over an expensive cellular interface and an unmetered Wi-Fi interface
/// is now available.
///
/// Set a block to be called when a better path becomes available or unavailable, which may be called
/// multiple times until the connection is cancelled.
///
/// When a better path is available, if it is possible to migrate work from this connection to a new connection,
/// create a new connection to the endpoint. Continue doing work on this connection until the new connection is
/// ready. Once ready, transition work to the new connection and cancel this one.
public var betterPathUpdateHandler: ((_ betterPathAvailable: Bool) -> Void)? {
set {
self._betterPathUpdateHandler = newValue
if let newValue = newValue {
nw_connection_set_better_path_available_handler(self.nw) { (betterPathAvailable) in
newValue(betterPathAvailable)
}
} else {
nw_connection_set_better_path_available_handler(self.nw, nil)
}
}
get {
return self._betterPathUpdateHandler
}
}
/// The remote endpoint of the connection
public let endpoint: NWEndpoint
/// The set of parameters with which the connection was created
public let parameters: NWParameters
private init(to: NWEndpoint, using: NWParameters, connection: nw_connection_t) {
self.endpoint = to
self.parameters = using
self.nw = connection
nw_connection_set_state_changed_handler(self.nw) { (state, error) in
self._state = NWConnection.State(state, error)
}
}
convenience internal init?(using: NWParameters, inbound: nw_connection_t) {
guard let peer = NWEndpoint(nw_connection_copy_endpoint(inbound)) else {
return nil
}
self.init(to: peer, using: using, connection: inbound)
}
/// Create a new outbound connection to an endpoint, with parameters.
/// The parameters determine the protocols to be used for the connection, and their options.
///
/// - Parameter to: The remote endpoint to which to connect.
/// - Parameter using: The parameters define which protocols and path to use.
public init(to: NWEndpoint, using: NWParameters) {
self.endpoint = to
self.parameters = using
self.nw = nw_connection_create(to.nw!, using.nw)
nw_connection_set_state_changed_handler(self.nw) { (state, error) in
self._state = NWConnection.State(state, error)
}
}
/// Create a new outbound connection to a hostname and port, with parameters.
/// The parameters determine the protocols to be used for the connection, and their options.
///
/// - Parameter host: The remote hostname to which to connect.
/// - Parameter port: The remote port to which to connect.
/// - Parameter using: The parameters define which protocols and path to use.
public convenience init(host: NWEndpoint.Host, port: NWEndpoint.Port, using: NWParameters) {
self.init(to: .hostPort(host: host, port: port), using: using)
}
/// Start the connection and provide a dispatch queue for callback blocks.
///
/// Starts the connection, which will cause the connection to evaluate its path, do resolution and try to become
/// ready (connected). NWConnection establishment is asynchronous. A stateUpdateHandler may be used to determine
/// when the state changes. If the connection can not be established, the state will transition to waiting with
/// an associated error describing the reason. If an unrecoverable error is encountered, the state will
/// transition to failed with an associated NWError value. If the connection is established, the state will
/// transition to ready.
///
/// Start should only be called once on a connection, and multiple calls to start will
/// be ignored.
public func start(queue: DispatchQueue) {
self._queue = queue
nw_connection_set_queue(self.nw, queue)
nw_connection_start(self.nw)
}
private var _queue: DispatchQueue?
/// Get queue used for delivering block handlers, such as stateUpdateHandler, pathUpdateHandler,
/// viabilityUpdateHandler, betterPathUpdateHandler, and completions for send and receive.
/// If the connection has not yet been started, the queue will be nil. Once the connection has been
/// started, the queue will be valid.
public var queue: DispatchQueue? {
get {
return self._queue
}
}
/// Cancel the connection, and cause all update handlers to be cancelled.
///
/// Cancel is asynchronous. The last callback will be to the stateUpdateHandler with the cancelled state. After
/// the stateUpdateHandlers are called with the cancelled state, all blocks are released to break retain cycles.
///
/// Calls to cancel after the first one are ignored.
public func cancel() {
nw_connection_cancel(self.nw)
}
/// A variant of cancel that performs non-graceful closure of the transport.
public func forceCancel() {
nw_connection_force_cancel(self.nw)
}
/// Cancel the currently connected endpoint, causing the connection to fall through to the next endpoint if
/// available, or to go to the waiting state if no more endpoints are available.
public func cancelCurrentEndpoint() {
nw_connection_cancel_current_endpoint(self.nw)
}
/// NWConnections will normally re-attempt on network changes. This function causes a connection that is in
/// the waiting state to re-attempt even without a network change.
public func restart() {
nw_connection_restart(self.nw)
}
/// Content Context controls options for how data is sent, and access for metadata to send or receive.
/// All properties of Content Context are valid for sending. For received Content Context, only the protocolMetadata
/// values will be set.
// Set the ObjC name of this class to be nested in the customized ObjC name of NWConnection.
@_objcRuntimeName(_TtCC7Network13_NWConnection14ContentContext)
public class ContentContext {
internal let nw: nw_content_context_t
/// A string description of the content, used for logging and debugging.
public let identifier: String
/// An expiration in milliseconds after scheduling a send, after which the content may be dropped.
/// Defaults to 0, which implies no expiration. Used only when sending.
public let expirationMilliseconds: UInt64
/// A numeric value between 0.0 and 1.0 to specify priority of this data/message. Defaults to 0.5.
/// Used only when sending.
public let relativePriority: Double
/// Any content marked as an antecedent must be sent prior to this content being sent. Defaults to nil.
/// Used only when sending.
public let antecedent: NWConnection.ContentContext?
/// A boolean indicating if this context is the final context in a given direction on this connection. Defaults to false.
public let isFinal: Bool
/// An array of protocol metadata to send (to inform the protocols of per-data options) or receive (to receive per-data options or statistics).
public var protocolMetadata: [NWProtocolMetadata] {
get {
var metadataArray: [NWProtocolMetadata] = []
nw_content_context_foreach_protocol_metadata(self.nw) { (definition, metadata) in
if nw_protocol_metadata_is_tcp(metadata) {
metadataArray.append(NWProtocolTCP.Metadata(metadata))
} else if nw_protocol_metadata_is_udp(metadata) {
metadataArray.append(NWProtocolUDP.Metadata(metadata))
} else if nw_protocol_metadata_is_ip(metadata) {
metadataArray.append(NWProtocolIP.Metadata(metadata))
} else if nw_protocol_metadata_is_tls(metadata) {
metadataArray.append(NWProtocolTLS.Metadata(metadata))
}
}
return metadataArray
}
}
/// Access the metadata for a specific protocol from a context. The metadata may be nil.
public func protocolMetadata(definition: NWProtocolDefinition) -> NWProtocolMetadata? {
if let metadata = nw_content_context_copy_protocol_metadata(self.nw, definition.nw) {
if nw_protocol_metadata_is_tcp(metadata) {
return NWProtocolTCP.Metadata(metadata)
} else if nw_protocol_metadata_is_udp(metadata) {
return NWProtocolUDP.Metadata(metadata)
} else if nw_protocol_metadata_is_ip(metadata) {
return NWProtocolIP.Metadata(metadata)
} else if nw_protocol_metadata_is_tls(metadata) {
return NWProtocolTLS.Metadata(metadata)
}
}
return nil
}
/// Create a context for sending, that optionally can set expiration (default 0),
/// priority (default 0.5), antecedent (default nil), and protocol metadata (default []]).
public init(identifier: String, expiration: UInt64 = 0, priority: Double = 0.5, isFinal: Bool = false, antecedent: NWConnection.ContentContext? = nil, metadata: [NWProtocolMetadata]? = []) {
self.nw = nw_content_context_create(identifier)
self.identifier = identifier
self.expirationMilliseconds = expiration
nw_content_context_set_expiration_milliseconds(self.nw, expiration)
self.relativePriority = priority
nw_content_context_set_relative_priority(self.nw, priority)
self.isFinal = isFinal
nw_content_context_set_is_final(self.nw, isFinal)
self.antecedent = antecedent
if let otherContext = antecedent {
nw_content_context_set_antecedent(self.nw, otherContext.nw)
}
if let metadataArray = metadata {
for singleMetadata in metadataArray {
nw_content_context_set_metadata_for_protocol(self.nw, singleMetadata.nw)
}
}
}
/// Use the default message context to send content with all default properties:
/// default priority, no expiration, and not the final message. Marking this context
/// as complete with a send indicates that the message content is now complete and any
/// other messages that were blocked may be scheduled, but will not close the underlying
/// connection. Use this context for any lightweight sends of datagrams or messages on
/// top of a stream that do not require special properties.
/// This context does not support overriding any properties.
public static let defaultMessage : NWConnection.ContentContext = NWConnection.ContentContext(_swift_nw_content_context_default_message())!
/// Use the final message context to indicate that no more sends are expected
/// once this context is complete. Like .defaultMessage, all properties are default.
/// Marking a send as complete when using this context will close the sending side of the
/// underlying connection. This is the equivalent of sending a FIN on a TCP stream.
/// This context does not support overriding any properties.
public static let finalMessage : NWConnection.ContentContext = NWConnection.ContentContext(_swift_nw_content_context_final_message())!
/// Use the default stream context to indicate that this sending context is
/// the one that represents the entire connection. All context properties are default.
/// This context behaves in the same way as .finalMessage, such that marking the
/// context complete by sending isComplete will close the sending side of the
/// underlying connection (a FIN for a TCP stream).
/// Note that this context is a convenience for sending a single, final context.
/// If the protocol used by the connection is a stream (such as TCP), the caller
/// may still use .defaultMessage, .finalMessage, or a custom context with priorities
/// and metadata to set properties of a particular chunk of stream data relative
/// to other data on the stream.
/// This context does not support overriding any properties.
public static let defaultStream : NWConnection.ContentContext = NWConnection.ContentContext(_swift_nw_content_context_default_stream())!
internal init?(_ nw: nw_content_context_t?) {
guard let nw = nw else {
return nil
}
self.nw = nw
// Received content context doesn't have expiration, priority, or antecedents
self.expirationMilliseconds = 0
self.relativePriority = 0
self.antecedent = nil
self.isFinal = nw_content_context_get_is_final(nw)
self.identifier = String(cString: nw_content_context_get_identifier(nw))
}
}
/// Receive data from a connection. This may be called before the connection
/// is ready, in which case the receive request will be queued until the
/// connection is ready. The completion handler will be invoked exactly
/// once for each call, so the client must call this function multiple
/// times to receive multiple chunks of data. For protocols that
/// support flow control, such as TCP, calling receive opens the receive
/// window. If the client stops calling receive, the receive window will
/// fill up and the remote peer will stop sending.
/// - Parameter minimumIncompleteLength: The minimum length to receive from the connection,
/// until the content is complete.
/// - Parameter maximumLength: The maximum length to receive from the connection in a single completion.
/// - Parameter completion: A receive completion is invoked exactly once for a call to receive(...).
/// The completion indicates that the requested content has been received (in which case
/// the content is delivered), or else an error has occurred. Parameters to the completion are:
///
/// - content: The received content, as constrained by the minimum and maximum length. This may
/// be nil if the message or stream is complete (without any more data to deliver), or if
/// an error was encountered.
///
/// - contentContext: Content context describing the received content. This includes protocol metadata
/// that lets the caller introspect information about the received content (such as flags on a packet).
///
/// - isComplete: An indication that this context (a message or stream, for example) is now complete. For
/// protocols such as TCP, this will be marked when the entire stream has be closed in the
/// reading direction. For protocols such as UDP, this will be marked when the end of a
/// datagram has been reached.
///
/// - error: An error will be sent if the receive was terminated before completing. There may still
/// be content delivered along with the error, but this content may be shorter than the requested
/// ranges. An error will be sent for any outstanding receives when the connection is cancelled.
public func receive(minimumIncompleteLength: Int, maximumLength: Int,
completion: @escaping (_ content: Data?,
_ contentContext: NWConnection.ContentContext?,
_ isComplete: Bool, _ error: NWError?) -> Void) {
nw_connection_receive(self.nw, UInt32(minimumIncompleteLength), UInt32(maximumLength)) {
(content, context, complete, nwError) in
completion(NWCreateNSDataFromDispatchData(content), ContentContext(context), complete, NWError(nwError));
}
}
/// Receive complete message content from the connection, waiting for the content to be marked complete
/// (or encounter an error) before delivering the callback. This is useful for datagram or message-based
/// protocols like UDP. See receive(minimumIncompleteLength:, maximumLength:, completion:) for a description
/// of the completion handler.
public func receiveMessage(completion: @escaping (_ completeContent: Data?,
_ contentContext: NWConnection.ContentContext?,
_ isComplete: Bool, _ error: NWError?) -> Void) {
nw_connection_receive_message(self.nw) { (content, context, complete, nwError) in
completion(NWCreateNSDataFromDispatchData(content), ContentContext(context), complete, NWError(nwError))
}
}
/// A type representing a wrapped completion handler invoked when send content has been consumed by the protocol stack, or the lack of a completion handler because the content is idempotent.
public enum SendCompletion {
/// Completion handler to be invoked when send content has been successfully processed, or failed to send due to an error.
/// Note that this does not guarantee that the data was sent out over the network, or acknowledge, but only that
/// it has been consumed by the protocol stack.
case contentProcessed((_ error: NWError?) -> Void)
/// Idempotent content may be sent multiple times when opening up a 0-RTT connection, so there is no completion block
case idempotent
}
/// Send data on a connection. This may be called before the connection is ready,
/// in which case the send will be enqueued until the connection is ready to send.
/// This is an asynchronous send and the completion block can be used to
/// determine when the send is complete. There is nothing preventing a client
/// from issuing an excessive number of outstanding sends. To minimize memory
/// footprint and excessive latency as a consequence of buffer bloat, it is
/// advisable to keep a low number of outstanding sends. The completion block
/// can be used to pace subsequent sends.
/// - Parameter content: The data to send on the connection. May be nil if this send marks its context as complete, such
/// as by sending .finalMessage as the context and marking isComplete to send a write-close.
/// - Parameter contentContext: The context associated with the content, which represents a logical message
/// to be sent on the connection. All content sent within a single context will
/// be sent as an in-order unit, up until the point that the context is marked
/// complete (see isComplete). Once a context is marked complete, it may be re-used
/// as a new logical message. Protocols like TCP that cannot send multiple
/// independent messages at once (serial streams) will only start processing a new
/// context once the prior context has been marked complete. Defaults to .defaultMessage.
/// - Parameter isComplete: A flag indicating if the caller's sending context (logical message) is now complete.
/// Until a context is marked complete, content sent for other contexts may not
/// be sent immediately (if the protocol requires sending bytes serially, like TCP).
/// For datagram protocols, like UDP, isComplete indicates that the content represents
/// a complete datagram.
/// When sending using streaming protocols like TCP, isComplete can be used to mark the end
/// of a single message on the stream, of which there may be many. However, it can also
/// indicate that the connection should send a "write close" (a TCP FIN) if the sending
/// context is the final context on the connection. Specifically, to send a "write close",
/// pass .finalMessage or .defaultStream for the context (or create a custom context and
/// set .isFinal), and pass true for isComplete.
/// - Parameter completion: A completion handler (.contentProcessed) to notify the caller when content has been processed by
/// the connection, or a marker that this data is idempotent (.idempotent) and may be sent multiple times as fast open data.
public func send(content: Data?, contentContext: NWConnection.ContentContext = .defaultMessage, isComplete: Bool = true, completion: SendCompletion) {
switch completion {
case .idempotent:
_swift_nw_connection_send_idempotent(self.nw, NWCreateDispatchDataFromNSData(content), contentContext.nw, isComplete)
case .contentProcessed(let handler):
_swift_nw_connection_send(self.nw, NWCreateDispatchDataFromNSData(content), contentContext.nw, isComplete) { (error) in
handler(NWError(error))
}
}
}
/// Batching allows multiple send or receive calls provides a hint to the connection that the operations
/// should be coalesced to improve efficiency. Calls other than send and receive will not be affected.
public func batch(_ block: () -> Void) {
nw_connection_batch(self.nw, block)
}
/// Access connection-wide protocol metadata on the connection. This allows access to state for protocols
/// like TCP and TLS that have long-term state.
public func metadata(definition: NWProtocolDefinition) -> NWProtocolMetadata? {
if let metadata = nw_connection_copy_protocol_metadata(self.nw, definition.nw) {
if nw_protocol_metadata_is_tcp(metadata) {
return NWProtocolTCP.Metadata(metadata)
} else if nw_protocol_metadata_is_udp(metadata) {
return NWProtocolUDP.Metadata(metadata)
} else if nw_protocol_metadata_is_ip(metadata) {
return NWProtocolIP.Metadata(metadata)
} else if nw_protocol_metadata_is_tls(metadata) {
return NWProtocolTLS.Metadata(metadata)
}
return NWProtocolMetadata(metadata)
} else {
return nil
}
}
}