Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ChannelOptions to extract base types. #203

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,16 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
}

func getChannelSpecificOption0<Option>(option: Option) throws -> Option.Value where Option : ChannelOption {
if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
switch option {
case is NIOTSChannelOptions.Types.NIOTSConnectionOption:
return self.connection as! Option.Value
default:
// Check the non-constrained options.
()
}
}

fatalError("option \(type(of: option)).\(option) not supported")
}

Expand Down
36 changes: 36 additions & 0 deletions Sources/NIOTransportServices/NIOTSChannelOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ public struct NIOTSChannelOptions {

/// See: ``Types/NIOTSMultipathOption``
public static let multipathServiceType = NIOTSChannelOptions.Types.NIOTSMultipathOption()

/// See: ``Types/NIOTSConnectionOption``.
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
public static let connection = NIOTSChannelOptions.Types.NIOTSConnectionOption()

/// See: ``Types/NIOTSListenerOption``.
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
public static let listener = NIOTSChannelOptions.Types.NIOTSListenerOption()
}


Expand Down Expand Up @@ -143,6 +151,34 @@ extension NIOTSChannelOptions {

public init() {}
}

/// ``NIOTSConnectionOption`` accesses the `NWConnection` of the underlying connection.
///
/// > Warning: Callers must be extremely careful with this option, as it is easy to break an existing
/// > connection that uses it. NIOTS doesn't support arbitrary modifications of the `NWConnection`
/// > underlying a `Channel`.
///
/// This option is only valid with a `Channel` backed by an `NWConnection`.
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
public struct NIOTSConnectionOption: ChannelOption, Equatable {
public typealias Value = NWConnection?

public init() {}
}

/// ``NIOTSListenerOption`` accesses the `NWListener` of the underlying connection.
///
/// > Warning: Callers must be extremely careful with this option, as it is easy to break an existing
/// > connection that uses it. NIOTS doesn't support arbitrary modifications of the `NWListener`
/// > underlying a `Channel`.
///
/// This option is only valid with a `Channel` backed by an `NWListener`.
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
public struct NIOTSListenerOption: ChannelOption, Equatable {
public typealias Value = NWListener?

public init() {}
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions Sources/NIOTransportServices/NIOTSConnectionChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel {
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSConnectionChannel: Channel {
func getChannelSpecificOption0<Option>(option: Option) throws -> Option.Value where Option : ChannelOption {
if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
switch option {
case is NIOTSChannelOptions.Types.NIOTSConnectionOption:
return self.connection as! Option.Value
default:
// Fallthrough to non-restricted options.
()
}
}

switch option {
case is NIOTSChannelOptions.Types.NIOTSMultipathOption:
return self.multipathServiceType as! Option.Value
Expand Down
10 changes: 10 additions & 0 deletions Sources/NIOTransportServices/StateManagedListenerChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,16 @@ extension StateManagedListenerChannel {
throw ChannelError.ioOnClosedChannel
}

if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
switch option {
case is NIOTSChannelOptions.Types.NIOTSListenerOption:
return self.nwListener as! Option.Value
default:
// Fallthrough to non-restricted options
()
}
}

switch option {
case is ChannelOptions.Types.AutoReadOption:
return autoRead as! Option.Value
Expand Down
29 changes: 29 additions & 0 deletions Tests/NIOTransportServicesTests/NIOTSConnectionChannelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -918,5 +918,34 @@ class NIOTSConnectionChannelTests: XCTestCase {
XCTAssertNoThrow(try connection.close().wait())
XCTAssertNoThrow(try testCompletePromise.futureResult.wait())
}

func testCanExtractTheConnection() throws {
guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else {
throw XCTSkip("Option not available")
}

let listener = try NIOTSListenerBootstrap(group: self.group)
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}

_ = try NIOTSConnectionBootstrap(group: self.group)
.channelInitializer { channel in
let conn = try! channel.syncOptions!.getOption(NIOTSChannelOptions.connection)
XCTAssertNil(conn)
return channel.eventLoop.makeSucceededVoidFuture()
}.connect(to: listener.localAddress!).flatMap {
$0.getOption(NIOTSChannelOptions.connection)
}.always { result in
switch result {
case .success(let connection):
// Make sure we unwrap the connection.
XCTAssertNotNil(connection)
case .failure(let error):
XCTFail("Unexpected error: \(error)")
}
}.wait()
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,58 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase {
_ = try serverHandle.waitForDatagrams(count: 1)
XCTAssertNoThrow(try connection.close().wait())
}

func testCanExtractTheConnection() throws {
guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else {
throw XCTSkip("Option not available")
}

let listener = try NIOTSDatagramListenerBootstrap(group: self.group)
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}

_ = try NIOTSDatagramBootstrap(group: self.group)
.channelInitializer { channel in
let conn = try! channel.syncOptions!.getOption(NIOTSChannelOptions.connection)
XCTAssertNil(conn)
return channel.eventLoop.makeSucceededVoidFuture()
}.connect(to: listener.localAddress!).flatMap {
$0.getOption(NIOTSChannelOptions.connection)
}.always { result in
switch result {
case .success(let connection):
// Make sure we unwrap the connection.
XCTAssertNotNil(connection)
case .failure(let error):
XCTFail("Unexpected error: \(error)")
}
}.wait()
}


func testCanExtractTheListener() throws {
guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else {
throw XCTSkip("Option not available")
}

let listener = try NIOTSDatagramListenerBootstrap(group: self.group)
.serverChannelInitializer { channel in
let underlyingListener = try! channel.syncOptions!.getOption(NIOTSChannelOptions.listener)
XCTAssertNil(underlyingListener)
return channel.eventLoop.makeSucceededVoidFuture()
}
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}

let listenerFuture: EventLoopFuture<NWListener?> = listener.getOption(NIOTSChannelOptions.listener)

try listenerFuture.map { listener in
XCTAssertNotNil(listener)
}.wait()
}
}
#endif
23 changes: 23 additions & 0 deletions Tests/NIOTransportServicesTests/NIOTSListenerChannelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -332,5 +332,28 @@ class NIOTSListenerChannelTests: XCTestCase {

XCTAssertNoThrow(try listener.close().wait())
}

func testCanExtractTheListener() throws {
guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else {
throw XCTSkip("Listener option not available")
}

let listener = try NIOTSListenerBootstrap(group: self.group)
.serverChannelInitializer { channel in
let underlyingListener = try! channel.syncOptions!.getOption(NIOTSChannelOptions.listener)
XCTAssertNil(underlyingListener)
return channel.eventLoop.makeSucceededVoidFuture()
}
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}

let listenerFuture: EventLoopFuture<NWListener?> = listener.getOption(NIOTSChannelOptions.listener)

try listenerFuture.map { listener in
XCTAssertNotNil(listener)
}.wait()
}
}
#endif