diff --git a/Package.swift b/Package.swift index 3c06a50..b46487a 100644 --- a/Package.swift +++ b/Package.swift @@ -27,7 +27,7 @@ let package = Package( .library(name: "NIOSSH", targets: ["NIOSSH"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", from: "2.21.0"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.27.0"), .package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0"), ], targets: [ diff --git a/Sources/NIOSSH/Child Channels/SSHChildChannel.swift b/Sources/NIOSSH/Child Channels/SSHChildChannel.swift index d4988be..2f7cb0b 100644 --- a/Sources/NIOSSH/Child Channels/SSHChildChannel.swift +++ b/Sources/NIOSSH/Child Channels/SSHChildChannel.swift @@ -198,7 +198,7 @@ extension SSHChildChannel: Channel, ChannelCore { } private func setOption0(_ option: Option, value: Option.Value) throws { - assert(self.eventLoop.inEventLoop) + self.eventLoop.preconditionInEventLoop() switch option { case _ as ChannelOptions.Types.AutoReadOption: @@ -211,7 +211,7 @@ extension SSHChildChannel: Channel, ChannelCore { } private func getOption0(_ option: Option) throws -> Option.Value { - assert(self.eventLoop.inEventLoop) + self.eventLoop.preconditionInEventLoop() switch option { case _ as SSHChildChannelOptions.Types.LocalChannelIdentifierOption: @@ -1133,6 +1133,35 @@ extension SSHChildChannel { } } +extension SSHChildChannel { + internal struct SynchronousOptions: NIOSynchronousChannelOptions { + private let channel: SSHChildChannel + + fileprivate init(channel: SSHChildChannel) { + self.channel = channel + } + + /// Set `option` to `value` on this `Channel`. + /// + /// - Important: Must be called on the `EventLoop` the `Channel` is running on. + internal func setOption(_ option: Option, value: Option.Value) throws { + try self.channel.setOption0(option, value: value) + } + + /// Get the value of `option` for this `Channel`. + /// + /// - Important: Must be called on the `EventLoop` the `Channel` is running on. + internal func getOption(_ option: Option) throws -> Option.Value { + try self.channel.getOption0(option) + } + } + + /// Returns a view of the `Channel` exposing synchronous versions of `setOption` and `getOption`. + public var syncOptions: NIOSynchronousChannelOptions? { + SynchronousOptions(channel: self) + } +} + private extension IOData { mutating func slicePrefix(_ length: Int) -> IOData { assert(length < self.readableBytes) diff --git a/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift b/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift index 065ee65..4389b10 100644 --- a/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift +++ b/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift @@ -1657,4 +1657,40 @@ final class ChildChannelMultiplexerTests: XCTestCase { XCTAssertEqual(readRecorder.reads.count, 5) XCTAssertTrue(eofHandler.seenEOF) } + + func testChildChannelSupportsSyncOptions() throws { + var createdChannels = 0 + let harness = self.harness { channel, _ in + createdChannels += 1 + + guard let sync = channel.syncOptions else { + XCTFail("\(channel) does not support syncOptions but should") + return channel.eventLoop.makeSucceededFuture(()) + } + + do { + let autoRead = try sync.getOption(ChannelOptions.autoRead) + try sync.setOption(ChannelOptions.autoRead, value: !autoRead) + XCTAssertNotEqual(try sync.getOption(ChannelOptions.autoRead), autoRead) + } catch { + XCTFail("Unable to get/set autoRead using synchronous options") + } + + return channel.eventLoop.makeSucceededFuture(()) + } + defer { + harness.finish() + } + + XCTAssertEqual(createdChannels, 0) + XCTAssertEqual(harness.flushedMessages.count, 0) + + let openRequest = self.openRequest(channelID: 1) + XCTAssertNoThrow(try harness.multiplexer.receiveMessage(openRequest)) + XCTAssertEqual(createdChannels, 1) + XCTAssertEqual(harness.flushedMessages.count, 1) + XCTAssertNil(harness.delegate.writes.first?.1) + + self.assertChannelOpenConfirmation(harness.flushedMessages.first, recipientChannel: 1) + } }