From bbd5e63cf949b7db0c9edaf7a21e141c52afe214 Mon Sep 17 00:00:00 2001 From: Clinton Nkwocha <32041805+clintonpi@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:14:45 +0100 Subject: [PATCH] Provide configurability for receiving connection data (#212) Motivation: Users are stuck with the hardcoded parameters for receiving data from a connection. Modifications: - Add new options to `NIOTSChannelOptions` for configuring how to receive data from a connection. Result: Users can configure how they receive data from a connection. --- .../Datagram/NIOTSDatagramChannel.swift | 14 +++++++ .../NIOTSChannelOptions.swift | 26 ++++++++++++ .../NIOTSConnectionChannel.swift | 14 +++++++ .../StateManagedNWConnectionChannel.swift | 20 +++++++++- .../NIOTSChannelOptionsTests.swift | 40 +++++++++++++++++++ 5 files changed, 112 insertions(+), 2 deletions(-) diff --git a/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift b/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift index e9b18a2..cfc98ad 100644 --- a/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift +++ b/Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift @@ -68,6 +68,12 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { /// after the initial connection attempt has been made. internal var connection: NWConnection? + /// The minimum length of data to receive from this connection, until the content is complete. + internal var minimumIncompleteReceiveLength: Int + + /// The maximum length of data to receive from this connection in a single completion. + internal var maximumReceiveLength: Int + /// The `DispatchQueue` that socket events for this connection will be dispatched onto. internal let connectionQueue: DispatchQueue @@ -169,11 +175,15 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { internal init(eventLoop: NIOTSEventLoop, parent: Channel? = nil, qos: DispatchQoS? = nil, + minimumIncompleteReceiveLength: Int = 1, + maximumReceiveLength: Int = 8192, udpOptions: NWProtocolUDP.Options, tlsOptions: NWProtocolTLS.Options?) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() self.parent = parent + self.minimumIncompleteReceiveLength = minimumIncompleteReceiveLength + self.maximumReceiveLength = maximumReceiveLength self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos) self.udpOptions = udpOptions self.tlsOptions = tlsOptions @@ -187,11 +197,15 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel { on eventLoop: NIOTSEventLoop, parent: Channel, qos: DispatchQoS? = nil, + minimumIncompleteReceiveLength: Int = 1, + maximumReceiveLength: Int = 8192, udpOptions: NWProtocolUDP.Options, tlsOptions: NWProtocolTLS.Options?) { self.init(eventLoop: eventLoop, parent: parent, qos: qos, + minimumIncompleteReceiveLength: minimumIncompleteReceiveLength, + maximumReceiveLength: maximumReceiveLength, udpOptions: udpOptions, tlsOptions: tlsOptions) self.connection = connection diff --git a/Sources/NIOTransportServices/NIOTSChannelOptions.swift b/Sources/NIOTransportServices/NIOTSChannelOptions.swift index 4164af1..9d73c2f 100644 --- a/Sources/NIOTransportServices/NIOTSChannelOptions.swift +++ b/Sources/NIOTransportServices/NIOTSChannelOptions.swift @@ -53,6 +53,12 @@ public struct NIOTSChannelOptions { /// See: ``Types/NIOTSListenerOption``. @available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) public static let listener = NIOTSChannelOptions.Types.NIOTSListenerOption() + + /// See: ``Types/NIOTSMinimumIncompleteReceiveLengthOption``. + public static let minimumIncompleteReceiveLength = NIOTSChannelOptions.Types.NIOTSMinimumIncompleteReceiveLengthOption() + + /// See: ``Types/NIOTSMaximumReceiveLengthOption``. + public static let maximumReceiveLength = NIOTSChannelOptions.Types.NIOTSMaximumReceiveLengthOption() } @@ -179,6 +185,26 @@ extension NIOTSChannelOptions { public init() {} } + + /// ``NIOTSMinimumIncompleteReceiveLengthOption`` controls the minimum length to receive from a given + /// `NWConnection`, until the content is complete. + /// + /// This option is only valid with a `Channel` backed by an `NWConnection`. + public struct NIOTSMinimumIncompleteReceiveLengthOption: ChannelOption, Equatable { + public typealias Value = Int + + public init() {} + } + + /// ``NIOTSMaximumReceiveLengthOption`` controls the maximum length to receive from a given + /// `NWConnection` in a single completion. + /// + /// This option is only valid with a `Channel` backed by an `NWConnection`. + public struct NIOTSMaximumReceiveLengthOption: ChannelOption, Equatable { + public typealias Value = Int + + public init() {} + } } } diff --git a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift index 9927181..a858480 100644 --- a/Sources/NIOTransportServices/NIOTSConnectionChannel.swift +++ b/Sources/NIOTransportServices/NIOTSConnectionChannel.swift @@ -150,6 +150,12 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { /// after the initial connection attempt has been made. internal var connection: NWConnection? + /// The minimum length of data to receive from this connection, until the content is complete. + internal var minimumIncompleteReceiveLength: Int + + /// The maximum length of data to receive from this connection in a single completion. + internal var maximumReceiveLength: Int + /// The `DispatchQueue` that socket events for this connection will be dispatched onto. internal let connectionQueue: DispatchQueue @@ -230,11 +236,15 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { internal init(eventLoop: NIOTSEventLoop, parent: Channel? = nil, qos: DispatchQoS? = nil, + minimumIncompleteReceiveLength: Int = 1, + maximumReceiveLength: Int = 8192, tcpOptions: NWProtocolTCP.Options, tlsOptions: NWProtocolTLS.Options?) { self.tsEventLoop = eventLoop self.closePromise = eventLoop.makePromise() self.parent = parent + self.minimumIncompleteReceiveLength = minimumIncompleteReceiveLength + self.maximumReceiveLength = maximumReceiveLength self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos) self.tcpOptions = tcpOptions self.tlsOptions = tlsOptions @@ -248,11 +258,15 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel { on eventLoop: NIOTSEventLoop, parent: Channel? = nil, qos: DispatchQoS? = nil, + minimumIncompleteReceiveLength: Int = 1, + maximumReceiveLength: Int = 8192, tcpOptions: NWProtocolTCP.Options, tlsOptions: NWProtocolTLS.Options?) { self.init(eventLoop: eventLoop, parent: parent, qos: qos, + minimumIncompleteReceiveLength: minimumIncompleteReceiveLength, + maximumReceiveLength: maximumReceiveLength, tcpOptions: tcpOptions, tlsOptions: tlsOptions) self.connection = connection diff --git a/Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift b/Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift index 9fa68c7..40068a3 100644 --- a/Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift +++ b/Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift @@ -48,6 +48,10 @@ internal protocol StateManagedNWConnectionChannel: StateManagedChannel where Act var connection: NWConnection? { get set } + var minimumIncompleteReceiveLength: Int { get set } + + var maximumReceiveLength: Int { get set } + var connectionQueue: DispatchQueue { get } var connectPromise: EventLoopPromise? { get set } @@ -249,9 +253,13 @@ extension StateManagedNWConnectionChannel { preconditionFailure("Connection should not be nil") } - // TODO: Can we do something sensible with these numbers? self.outstandingRead = true - conn.receive(minimumIncompleteLength: 1, maximumLength: 8192, completion: self.dataReceivedHandler(content:context:isComplete:error:)) + + conn.receive( + minimumIncompleteLength: self.minimumIncompleteReceiveLength, + maximumLength: self.maximumReceiveLength, + completion: self.dataReceivedHandler(content:context:isComplete:error:) + ) } public func doClose0(error: Error) { @@ -554,6 +562,10 @@ extension StateManagedNWConnectionChannel { self.options.supportRemoteHalfClosure = value as! Bool case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse: self.allowLocalEndpointReuse = value as! NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse.Value + case is NIOTSChannelOptions.Types.NIOTSMinimumIncompleteReceiveLengthOption: + self.minimumIncompleteReceiveLength = value as! NIOTSChannelOptions.Types.NIOTSMinimumIncompleteReceiveLengthOption.Value + case is NIOTSChannelOptions.Types.NIOTSMaximumReceiveLengthOption: + self.maximumReceiveLength = value as! NIOTSChannelOptions.Types.NIOTSMaximumReceiveLengthOption.Value default: try self.setChannelSpecificOption0(option: option, value: value) } @@ -610,6 +622,10 @@ extension StateManagedNWConnectionChannel { throw NIOTSErrors.NoCurrentConnection() } return connection.metadata(definition: optionValue.definition) as! Option.Value + case is NIOTSChannelOptions.Types.NIOTSMinimumIncompleteReceiveLengthOption: + return self.minimumIncompleteReceiveLength as! Option.Value + case is NIOTSChannelOptions.Types.NIOTSMaximumReceiveLengthOption: + return self.maximumReceiveLength as! Option.Value default: // watchOS 6.0 availability is covered by the @available on this extension. if #available(OSX 10.15, iOS 13.0, tvOS 13.0, *) { diff --git a/Tests/NIOTransportServicesTests/NIOTSChannelOptionsTests.swift b/Tests/NIOTransportServicesTests/NIOTSChannelOptionsTests.swift index adeba7b..878199d 100644 --- a/Tests/NIOTransportServicesTests/NIOTSChannelOptionsTests.swift +++ b/Tests/NIOTransportServicesTests/NIOTSChannelOptionsTests.swift @@ -139,5 +139,45 @@ class NIOTSChannelOptionsTests: XCTestCase { XCTAssertEqual(listenerValue, .handover) XCTAssertEqual(connectionValue, .interactive) } + + func testMinimumIncompleteReceiveLength() 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) + .channelOption(NIOTSChannelOptions.minimumIncompleteReceiveLength, value: 1) + .connect(to: listener.localAddress!) + .wait() + defer { + XCTAssertNoThrow(try connection.close().wait()) + } + + let connectionValue = try assertNoThrowWithValue(connection.getOption(NIOTSChannelOptions.minimumIncompleteReceiveLength).wait()) + + XCTAssertEqual(connectionValue, 1) + } + + func testMaximumReceiveLength() 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) + .channelOption(NIOTSChannelOptions.maximumReceiveLength, value: 8192) + .connect(to: listener.localAddress!) + .wait() + defer { + XCTAssertNoThrow(try connection.close().wait()) + } + + let connectionValue = try assertNoThrowWithValue(connection.getOption(NIOTSChannelOptions.maximumReceiveLength).wait()) + + XCTAssertEqual(connectionValue, 8192) + } } #endif