mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
* Removing unnecessary casts from stdlib * Minor adjustments * [stdlib][qol] Clean up error variable assign NSError * Removing unnecessary coercions after removing DeclRefExpr restriction.
574 lines
26 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|