New channel options exposing properties of underlying NWConnection (#90)

* Initial draft implementation of new channel options for NWConnection.currentPath, metadata for a given NWProtocol, establishment report and data transfer report. Add a new error for not existing connection. Add tests for all of these. The data transfer report option has to be implemented in another way, as the collection of the final data transfer report does not seem very straightforward.

* Fix incorrect available attributes. Make the Value of NIOTSEstablishmentReportOption an EventLoopFuture returning an optional report. Move handling of NIOTSEstablishmentReportOption and NIOTSDataTransferReportOption to getOption0. Remove nwConnection0().

* Use full type on currentPath too.

* Add a DispatchQueue to do collect of the pending data transfer report, use a DispatchGroup to wait for completion of report collection. Remove redundant attribute. Fix nits related to spacing and file header.

* Fix available attributes for tests too.
This commit is contained in:
Pasi Salenius 2020-06-01 13:52:56 +03:00 committed by GitHub
parent 998422e195
commit 7e330732f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 214 additions and 0 deletions

View File

@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
#if canImport(Network)
import NIO
import Network
/// Options that can be set explicitly and only on bootstraps provided by `NIOTransportServices`.
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
@ -21,6 +22,18 @@ public struct NIOTSChannelOptions {
public static let waitForActivity = NIOTSChannelOptions.Types.NIOTSWaitForActivityOption()
public static let enablePeerToPeer = NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption()
public static let currentPath = NIOTSChannelOptions.Types.NIOTSCurrentPathOption()
public static let metadata = { (definition: NWProtocolDefinition) -> NIOTSChannelOptions.Types.NIOTSMetadataOption in
.init(definition: definition)
}
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public static let establishmentReport = NIOTSChannelOptions.Types.NIOTSEstablishmentReportOption()
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public static let dataTransferReport = NIOTSChannelOptions.Types.NIOTSDataTransferReportOption()
}
@ -54,6 +67,50 @@ extension NIOTSChannelOptions {
public init() {}
}
/// `NIOTSCurrentPathOption` accesses the `NWConnection.currentPath` of the underlying connection.
///
/// This option is only valid with `NIOTSConnectionBootstrap`.
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
public struct NIOTSCurrentPathOption: ChannelOption, Equatable {
public typealias Value = NWPath
public init() {}
}
/// `NIOTSMetadataOption` accesses the metadata for a given `NWProtocol`.
///
/// This option is only valid with `NIOTSConnectionBootstrap`.
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)
public struct NIOTSMetadataOption: ChannelOption, Equatable {
public typealias Value = NWProtocolMetadata
let definition: NWProtocolDefinition
public init(definition: NWProtocolDefinition) {
self.definition = definition
}
}
/// `NIOTSEstablishmentReportOption` accesses the `NWConnection.EstablishmentReport` of the underlying connection.
///
/// This option is only valid with `NIOTSConnectionBootstrap`.
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct NIOTSEstablishmentReportOption: ChannelOption, Equatable {
public typealias Value = EventLoopFuture<NWConnection.EstablishmentReport?>
public init() {}
}
/// `NIOTSDataTransferReportOption` accesses the `NWConnection.DataTransferReport` of the underlying connection.
///
/// This option is only valid with `NIOTSConnectionBootstrap`.
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct NIOTSDataTransferReportOption: ChannelOption, Equatable {
public typealias Value = NWConnection.PendingDataTransferReport
public init() {}
}
}
}

View File

@ -388,7 +388,39 @@ extension NIOTSConnectionChannel: Channel {
return self.options.waitForActivity as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
return self.enablePeerToPeer as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSCurrentPathOption:
guard let currentPath = self.nwConnection?.currentPath else {
throw NIOTSErrors.NoCurrentPath()
}
return currentPath as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSMetadataOption:
let optionValue = option as! NIOTSChannelOptions.Types.NIOTSMetadataOption
guard let nwConnection = self.nwConnection else {
throw NIOTSErrors.NoCurrentConnection()
}
return nwConnection.metadata(definition: optionValue.definition) as! Option.Value
default:
if #available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
switch option {
case is NIOTSChannelOptions.Types.NIOTSEstablishmentReportOption:
guard let nwConnection = self.nwConnection else {
throw NIOTSErrors.NoCurrentConnection()
}
let promise: EventLoopPromise<NWConnection.EstablishmentReport?> = eventLoop.makePromise()
nwConnection.requestEstablishmentReport(queue: connectionQueue) { report in
promise.succeed(report)
}
return promise.futureResult as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSDataTransferReportOption:
guard let nwConnection = self.nwConnection else {
throw NIOTSErrors.NoCurrentConnection()
}
return nwConnection.startDataTransferReport() as! Option.Value
default:
break
}
}
fatalError("option \(type(of: option)).\(option) not supported")
}
}

View File

@ -45,6 +45,10 @@ public enum NIOTSErrors {
/// that channel has no path available. This can manifest, for example, when asking for remote
/// or local addresses.
public struct NoCurrentPath: NIOTSError { }
/// `NoCurrentConnection` is thrown when an attempt is made to request connection details from a channel and
/// that channel has no connection available.
public struct NoCurrentConnection: NIOTSError { }
/// `InvalidPort` is thrown when the port passed to a method is not valid.
public struct InvalidPort: NIOTSError {

View File

@ -0,0 +1,121 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020 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 XCTest
import NIO
import NIOConcurrencyHelpers
import NIOTransportServices
import Network
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
class NIOTSChannelOptionsTests: XCTestCase {
private var group: NIOTSEventLoopGroup!
override func setUp() {
self.group = NIOTSEventLoopGroup()
}
override func tearDown() {
XCTAssertNoThrow(try self.group.syncShutdownGracefully())
}
func testCurrentPath() throws {
let listener = try NIOTSListenerBootstrap(group: self.group)
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}
let connection = try NIOTSConnectionBootstrap(group: self.group)
.connect(to: listener.localAddress!)
.wait()
defer {
XCTAssertNoThrow(try connection.close().wait())
}
let currentPath = try connection.getOption(NIOTSChannelOptions.currentPath).wait()
XCTAssertEqual(currentPath.status, NWPath.Status.satisfied)
}
func testMetadata() throws {
let listener = try NIOTSListenerBootstrap(group: self.group)
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}
let connection = try NIOTSConnectionBootstrap(group: self.group)
.connect(to: listener.localAddress!)
.wait()
defer {
XCTAssertNoThrow(try connection.close().wait())
}
let metadata = try connection.getOption(NIOTSChannelOptions.metadata(NWProtocolTCP.definition)).wait() as! NWProtocolTCP.Metadata
XCTAssertEqual(metadata.availableReceiveBuffer, 0)
}
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
func testEstablishmentReport() throws {
let listener = try NIOTSListenerBootstrap(group: self.group)
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}
let connection = try NIOTSConnectionBootstrap(group: self.group)
.connect(to: listener.localAddress!)
.wait()
defer {
XCTAssertNoThrow(try connection.close().wait())
}
let reportFuture = try connection.getOption(NIOTSChannelOptions.establishmentReport).wait()
let establishmentReport = try reportFuture.wait()
XCTAssertEqual(establishmentReport!.resolutions.count, 0)
}
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
func testDataTransferReport() throws {
let syncQueue = DispatchQueue(label: "syncQueue")
let collectGroup = DispatchGroup()
let listener = try NIOTSListenerBootstrap(group: self.group)
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}
let connection = try NIOTSConnectionBootstrap(group: self.group)
.connect(to: listener.localAddress!)
.wait()
defer {
XCTAssertNoThrow(try connection.close().wait())
}
let pendingReport = try connection.getOption(NIOTSChannelOptions.dataTransferReport).wait()
collectGroup.enter()
pendingReport.collect(queue: syncQueue) { report in
XCTAssertEqual(report.pathReports.count, 1)
collectGroup.leave()
}
collectGroup.wait()
}
}
#endif