1
0
mirror of https://github.com/apple/swift-nio-extras.git synced 2025-06-01 18:53:35 +08:00

Docnioextras ()

* Improve documentation for NIOExtras

Motivation:

Docs will help users do things correctly.

Modifications:

Add missing comments, improve links.

Result:

Better docc documentation

* Docc in NIOHTTPCompression

* NIOSOCKS docc

* Correct bad symbol

* Minor typo

Co-authored-by: Cory Benfield <lukasa@apple.com>
This commit is contained in:
Peter Adams 2022-08-03 09:34:45 +01:00 committed by GitHub
parent ca22c12528
commit da7c04777b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 393 additions and 116 deletions

@ -19,76 +19,135 @@ import Glibc
import NIOCore
/// ChannelInboundHandler that prints all inbound events that pass through the pipeline by default,
/// overridable by providing your own closure for custom logging. See DebugOutboundEventsHandler for outbound events.
/// `ChannelInboundHandler` that prints all inbound events that pass through the pipeline by default,
/// overridable by providing your own closure for custom logging. See ``DebugOutboundEventsHandler`` for outbound events.
public class DebugInboundEventsHandler: ChannelInboundHandler {
/// The type of the inbound data which is wrapped in `NIOAny`.
public typealias InboundIn = Any
/// The type of the inbound data which will be forwarded to the next `ChannelInboundHandler` in the `ChannelPipeline`.
public typealias InboudOut = Any
/// Enumeration of possible `ChannelHandler` events which can occur.
public enum Event {
/// Channel was registered.
case registered
/// Channel was unregistered.
case unregistered
/// Channel became active.
case active
/// Channel became inactive.
case inactive
/// Data was received.
case read(data: NIOAny)
/// Current read loop finished.
case readComplete
/// Writability state of the channel changed.
case writabilityChanged(isWritable: Bool)
/// A user inbound event was received.
case userInboundEventTriggered(event: Any)
/// An error was caught.
case errorCaught(Error)
}
var logger: (Event, ChannelHandlerContext) -> ()
/// Initialiser.
/// - Parameter logger: Method for logging events which occur.
public init(logger: @escaping (Event, ChannelHandlerContext) -> () = DebugInboundEventsHandler.defaultPrint) {
self.logger = logger
}
/// Logs ``Event.registered`` to ``logger``
/// Called when the `Channel` has successfully registered with its `EventLoop` to handle I/O.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func channelRegistered(context: ChannelHandlerContext) {
logger(.registered, context)
context.fireChannelRegistered()
}
/// Logs ``Event.unregistered`` to ``logger``
/// Called when the `Channel` has unregistered from its `EventLoop`, and so will no longer be receiving I/O events.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func channelUnregistered(context: ChannelHandlerContext) {
logger(.unregistered, context)
context.fireChannelUnregistered()
}
/// Logs ``Event.active`` to ``logger``
/// Called when the `Channel` has become active, and is able to send and receive data.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func channelActive(context: ChannelHandlerContext) {
logger(.active, context)
context.fireChannelActive()
}
/// Logs ``Event.inactive`` to ``logger``
/// Called when the `Channel` has become inactive and is no longer able to send and receive data`.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func channelInactive(context: ChannelHandlerContext) {
logger(.inactive, context)
context.fireChannelInactive()
}
/// Logs ``Event.read`` to ``logger``
/// Called when some data has been read from the remote peer.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - data: The data read from the remote peer, wrapped in a `NIOAny`.
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
logger(.read(data: data), context)
context.fireChannelRead(data)
}
/// Logs ``Event.readComplete`` to ``logger``
/// Called when the `Channel` has completed its current read loop, either because no more data is available
/// to read from the transport at this time, or because the `Channel` needs to yield to the event loop to process
/// other I/O events for other `Channel`s.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func channelReadComplete(context: ChannelHandlerContext) {
logger(.readComplete, context)
context.fireChannelReadComplete()
}
/// Logs ``Event.writabilityChanged`` to ``logger``
/// The writability state of the `Channel` has changed, either because it has buffered more data than the writability
/// high water mark, or because the amount of buffered data has dropped below the writability low water mark.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func channelWritabilityChanged(context: ChannelHandlerContext) {
logger(.writabilityChanged(isWritable: context.channel.isWritable), context)
context.fireChannelWritabilityChanged()
}
/// Logs ``Event.userInboundEventTriggered`` to ``logger``
/// Called when a user inbound event has been triggered.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - event: The event.
public func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
logger(.userInboundEventTriggered(event: event), context)
context.fireUserInboundEventTriggered(event)
}
/// Logs ``Event.errorCaught`` to ``logger``
/// An error was encountered earlier in the inbound `ChannelPipeline`.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - error: The `Error` that was encountered.
public func errorCaught(context: ChannelHandlerContext, error: Error) {
logger(.errorCaught(error), context)
context.fireErrorCaught(error)
}
/// Print and flush a textual description of an ``Event``.
/// - parameters:
/// - event: The ``Event`` to print.
/// - context: The context `event` was received in.
public static func defaultPrint(event: Event, in context: ChannelHandlerContext) {
let message: String
switch event {
@ -114,5 +173,4 @@ public class DebugInboundEventsHandler: ChannelInboundHandler {
print(message + " in \(context.name)")
fflush(stdout)
}
}

@ -21,69 +21,130 @@ import Glibc
import NIOCore
/// ChannelOutboundHandler that prints all outbound events that pass through the pipeline by default,
/// overridable by providing your own closure for custom logging. See DebugInboundEventsHandler for inbound events.
/// overridable by providing your own closure for custom logging. See ``DebugInboundEventsHandler`` for inbound events.
public class DebugOutboundEventsHandler: ChannelOutboundHandler {
/// The type of the outbound data which is wrapped in `NIOAny`.
public typealias OutboundIn = Any
/// The type of the outbound data which will be forwarded to the next `ChannelOutboundHandler` in the `ChannelPipeline`.
public typealias OutboundOut = Any
/// All possible outbound events which could occur.
public enum Event {
/// `Channel` registered for I/O events.
case register
/// Bound to a `SocketAddress`
case bind(address: SocketAddress)
/// Connected to an address.
case connect(address: SocketAddress)
/// Write operation.
case write(data: NIOAny)
/// Pending writes flushed.
case flush
/// Ready to read more data.
case read
/// Close the channel.
case close(mode: CloseMode)
/// User outbound event triggered.
case triggerUserOutboundEvent(event: Any)
}
var logger: (Event, ChannelHandlerContext) -> ()
/// Initialiser.
/// - parameters:
/// - logger: Method for logging events which happen.
public init(logger: @escaping (Event, ChannelHandlerContext) -> () = DebugOutboundEventsHandler.defaultPrint) {
self.logger = logger
}
/// Logs ``Event.register`` to ``logger``
/// Called to request that the `Channel` register itself for I/O events with its `EventLoop`.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
public func register(context: ChannelHandlerContext, promise: EventLoopPromise<Void>?) {
logger(.register, context)
context.register(promise: promise)
}
/// Logs ``Event.bind`` to ``logger``
/// Called to request that the `Channel` bind to a specific `SocketAddress`.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - to: The `SocketAddress` to which this `Channel` should bind.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
public func bind(context: ChannelHandlerContext, to address: SocketAddress, promise: EventLoopPromise<Void>?) {
logger(.bind(address: address), context)
context.bind(to: address, promise: promise)
}
/// Logs ``Event.connect`` to ``logger``
/// Called to request that the `Channel` connect to a given `SocketAddress`.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - to: The `SocketAddress` to which the the `Channel` should connect.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
public func connect(context: ChannelHandlerContext, to address: SocketAddress, promise: EventLoopPromise<Void>?) {
logger(.connect(address: address), context)
context.connect(to: address, promise: promise)
}
/// Logs ``Event.data`` to ``logger``
/// Called to request a write operation.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - data: The data to write through the `Channel`, wrapped in a `NIOAny`.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
logger(.write(data: data), context)
context.write(data, promise: promise)
}
/// Logs ``Event.flush`` to ``logger``
/// Called to request that the `Channel` flush all pending writes. The flush operation will try to flush out all previous written messages
/// that are pending.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func flush(context: ChannelHandlerContext) {
logger(.flush, context)
context.flush()
}
/// Logs ``Event.read`` to ``logger``
/// Called to request that the `Channel` perform a read when data is ready. The read operation will signal that we are ready to read more data.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func read(context: ChannelHandlerContext) {
logger(.read, context)
context.read()
}
/// Logs ``Event.close`` to ``logger``
/// Called to request that the `Channel` close itself down`.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - mode: The `CloseMode` to apply
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
public func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?) {
logger(.close(mode: mode), context)
context.close(mode: mode, promise: promise)
}
/// Logs ``Event.triggerUserOutboundEvent`` to ``logger``
/// Called when an user outbound event is triggered.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - event: The triggered event.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
public func triggerUserOutboundEvent(context: ChannelHandlerContext, event: Any, promise: EventLoopPromise<Void>?) {
logger(.triggerUserOutboundEvent(event: event), context)
context.triggerUserOutboundEvent(event, promise: promise)
}
/// Print textual event description to stdout.
/// - parameters:
/// - event: The ``Event`` to print.
/// - in: The context the event occured in.
public static func defaultPrint(event: Event, in context: ChannelHandlerContext) {
let message: String
switch event {
@ -108,5 +169,4 @@ public class DebugOutboundEventsHandler: ChannelOutboundHandler {
print(message + " in \(context.name)")
fflush(stdout)
}
}

@ -0,0 +1,13 @@
# Article
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
## Overview
<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->
## Topics
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->

@ -21,7 +21,7 @@ import NIOCore
/// | A | BC | DEFG | HI |
/// +---+----+------+----+
///
/// A `FixedLengthFrameDecoder` will decode them into the
/// A ``FixedLengthFrameDecoder`` will decode them into the
/// following three packets with the fixed length:
///
/// +-----+-----+-----+
@ -29,7 +29,9 @@ import NIOCore
/// +-----+-----+-----+
///
public final class FixedLengthFrameDecoder: ByteToMessageDecoder {
/// Data type we receive.
public typealias InboundIn = ByteBuffer
/// Data type we send to the next stage.
public typealias InboundOut = ByteBuffer
@available(*, deprecated, message: "No longer used")
@ -45,6 +47,11 @@ public final class FixedLengthFrameDecoder: ByteToMessageDecoder {
self.frameLength = frameLength
}
/// Get a frame of data and `fireChannelRead` if sufficient data exists in the buffer.
/// - Parameters:
/// - context: Calling context.
/// - buffer: Buffer containing data.
/// - Returns: Status detailing if more data is required or if a successful decode occurred.
public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
guard let slice = buffer.readSlice(length: frameLength) else {
return .needMoreData
@ -54,6 +61,13 @@ public final class FixedLengthFrameDecoder: ByteToMessageDecoder {
return .continue
}
/// Repeatedly decode frames until there is not enough data to decode any more.
/// Reports an error through `fireErrorCaught` if this doesn't empty the buffer exactly.
/// - Parameters:
/// - context: Calling context
/// - buffer: Buffer containing data.
/// - seenEOF: If end of file has been seen.
/// - Returns: needMoreData always as all data is consumed.
public func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
while case .continue = try self.decode(context: context, buffer: &buffer) {}
if buffer.readableBytes > 0 {

@ -15,22 +15,31 @@
import NIOCore
extension NIOJSONRPCFraming {
/// `ContentLengthHeaderFrameEncoder` is responsible for emitting JSON-RPC wire protocol with 'Content-Length'
/// ``ContentLengthHeaderFrameEncoder`` is responsible for emitting JSON-RPC wire protocol with 'Content-Length'
/// HTTP-like headers as used by for example by LSP (Language Server Protocol).
public final class ContentLengthHeaderFrameEncoder: ChannelOutboundHandler {
/// We'll get handed one message through the `Channel` and ...
/// We'll get handed one message through the `Channel` of this type and will encode into `OutboundOut`
public typealias OutboundIn = ByteBuffer
/// ... will encode it into a `ByteBuffer`.
/// Outbound data will be encoded into a `ByteBuffer`.
public typealias OutboundOut = ByteBuffer
private var scratchBuffer: ByteBuffer!
public init() {}
/// Called when this `ChannelHandler` is added to the `ChannelPipeline`.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
public func handlerAdded(context: ChannelHandlerContext) {
self.scratchBuffer = context.channel.allocator.buffer(capacity: 512)
}
/// Called to request a write operation. Writes write protocol header and then the message.
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - data: The data to write through the `Channel`, wrapped in a `NIOAny`.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
let data = self.unwrapOutboundIn(data)
// Step 1, clear the target buffer (note, we are re-using it so if we get lucky we don't need to
@ -52,7 +61,7 @@ extension NIOJSONRPCFraming {
}
}
/// `ContentLengthHeaderFrameDecoder` is responsible for parsing JSON-RPC wire protocol with 'Content-Length'
/// ``ContentLengthHeaderFrameDecoder`` is responsible for parsing JSON-RPC wire protocol with 'Content-Length'
/// HTTP-like headers as used by for example by LSP (Language Server Protocol).
public struct ContentLengthHeaderFrameDecoder: ByteToMessageDecoder {
/// We're emitting one `ByteBuffer` corresponding exactly to one full payload, no headers etc.
@ -60,15 +69,15 @@ extension NIOJSONRPCFraming {
/// `ContentLengthHeaderFrameDecoder` is a simple state machine.
private enum State {
/// either we're waiting for the end of the header block or a new header field, ...
/// Waiting for the end of the header block or a new header field
case waitingForHeaderNameOrHeaderBlockEnd
/// ... or for a header value, or ...
/// Waiting for a header value
case waitingForHeaderValue(name: String)
/// ... or for the payload of a given size.
/// Waiting for the payload of a given size.
case waitingForPayload(length: Int)
}
/// A `DecodingError` is sent through the pipeline if anything went wrong.
/// A ``DecodingError`` is sent through the pipeline if anything went wrong.
public enum DecodingError: Error, Equatable {
/// Missing 'Content-Length' header.
case missingContentLengthHeader
@ -106,7 +115,12 @@ extension NIOJSONRPCFraming {
}
}
// `decode` will be invoked whenever there is more data available (or if we return `.continue`).
/// Decode the data in the supplied `buffer`.
/// `decode` will be invoked whenever there is more data available (or if we return `.continue`).
/// - parameters:
/// - context: Calling context.
/// - buffer: The data to decode.
/// - returns: Status describing need for more data or otherwise.
public mutating func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
switch self.state {
case .waitingForHeaderNameOrHeaderBlockEnd:
@ -166,7 +180,14 @@ extension NIOJSONRPCFraming {
}
}
/// Invoked when the `Channel` is being brough down.
/// Decode all remaining data.
/// Invoked when the `Channel` is being brought down.
/// Reports error through `ByteToMessageDecoderError.leftoverDataWhenDone` if not all data is consumed.
/// - parameters:
/// - context: Calling context.
/// - buffer: Buffer of data to decode.
/// - seenEOF: If the end of file has been seen.
/// - returns: .needMoreData always as all data should be consumed.
public mutating func decodeLast(context: ChannelHandlerContext,
buffer: inout ByteBuffer,
seenEOF: Bool) throws -> DecodingState {

@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//
/// Namespace to contain JSON framing implementation.
public enum NIOJSONRPCFraming {
// just a name-space
}

@ -47,23 +47,25 @@ extension ByteBuffer {
}
public enum NIOLengthFieldBasedFrameDecoderError: Error {
/// This error can be thrown by `LengthFieldBasedFrameDecoder` if the length field value is larger than `Int.max`
/// This error can be thrown by ``LengthFieldBasedFrameDecoder`` if the length field value is larger than `Int.max`
case lengthFieldValueTooLarge
/// This error can be thrown by `LengthFieldBasedFrameDecoder` if the length field value is larger than `LengthFieldBasedFrameDecoder.maxSupportedLengthFieldSize`
/// This error can be thrown by ``LengthFieldBasedFrameDecoder`` if the length field value is larger than `LengthFieldBasedFrameDecoder.maxSupportedLengthFieldSize`
case lengthFieldValueLargerThanMaxSupportedSize
}
///
/// A decoder that splits the received `ByteBuffer` by the number of bytes specified in a fixed length header
/// contained within the buffer.
///
/// For example, if you received the following four fragmented packets:
///
/// +---+----+------+----+
/// | A | BC | DEFG | HI |
/// +---+----+------+----+
///
/// Given that the specified header length is 1 byte,
/// where the first header specifies 3 bytes while the second header specifies 4 bytes,
/// a `LengthFieldBasedFrameDecoder` will decode them into the following packets:
/// a ``LengthFieldBasedFrameDecoder`` will decode them into the following packets:
///
/// +-----+------+
/// | BCD | FGHI |
@ -72,15 +74,18 @@ public enum NIOLengthFieldBasedFrameDecoderError: Error {
/// 'A' and 'E' will be the headers and will not be passed forward.
///
public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
/// Maximum supported length field size in bytes of `LengthFieldBasedFrameDecoder` and is currently `Int32.max`
/// Maximum supported length field size in bytes of ``LengthFieldBasedFrameDecoder`` and is currently `Int32.max`
public static let maxSupportedLengthFieldSize: Int = Int(Int32.max)
///
/// An enumeration to describe the length of a piece of data in bytes.
///
public enum ByteLength {
/// One byte
case one
/// Two bytes
case two
/// Four bytes
case four
/// Eight bytes
case eight
fileprivate var bitLength: NIOLengthFieldBitLength {
@ -92,18 +97,20 @@ public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
}
}
}
///
/// The decoder has two distinct sections of data to read.
/// Each must be fully present before it is considered as read.
/// During the time when it is not present the decoder must wait. `DecoderReadState` details that waiting state.
///
/// During the time when it is not present the decoder must wait. ``DecoderReadState`` details that waiting state.
private enum DecoderReadState {
// Expending a header next.
case waitingForHeader
// Expecting the frame next.
case waitingForFrame(length: Int)
}
/// Incoming data is in `ByteBuffer`
public typealias InboundIn = ByteBuffer
/// `ByteBuffer` is type passed to next stage.
public typealias InboundOut = ByteBuffer
@available(*, deprecated, message: "No longer used")
@ -118,7 +125,6 @@ public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
/// - parameters:
/// - lengthFieldLength: The length of the field specifying the remaining length of the frame.
/// - lengthFieldEndianness: The endianness of the field specifying the remaining length of the frame.
///
public convenience init(lengthFieldLength: ByteLength, lengthFieldEndianness: Endianness = .big) {
self.init(lengthFieldBitLength: lengthFieldLength.bitLength, lengthFieldEndianness: lengthFieldEndianness)
}
@ -128,12 +134,16 @@ public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
/// - parameters:
/// - lengthFieldBitLength: The length of the field specifying the remaining length of the frame.
/// - lengthFieldEndianness: The endianness of the field specifying the remaining length of the frame.
///
public init(lengthFieldBitLength: NIOLengthFieldBitLength, lengthFieldEndianness: Endianness = .big) {
self.lengthFieldLength = lengthFieldBitLength
self.lengthFieldEndianness = lengthFieldEndianness
}
/// Decode supplied data.
/// - Parameters:
/// - context: Calling context.
/// - buffer: data to decode.
/// - Returns: `DecodingState` describing what's needed next.
public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
if case .waitingForHeader = self.readState {
@ -152,7 +162,14 @@ public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
return .continue
}
/// Decode all data supplied. No more is expected after this.
/// If all data is not exactly consumed reports and error through `context.fireErrorCaught`
/// - Parameters:
/// - context: Calling context.
/// - buffer: The data to decode
/// - seenEOF: If End of File has been seen.
/// - Returns: .needMoreData always as all data has been consumed.
public func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
// we'll just try to decode as much as we can as usually
while case .continue = try self.decode(context: context, buffer: &buffer) {}
@ -162,12 +179,10 @@ public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
return .needMoreData
}
///
/// Attempts to read the header data. Updates the status is successful.
///
/// - parameters:
/// - buffer: The buffer containing the integer frame length.
///
private func readNextLengthFieldToState(buffer: inout ByteBuffer) throws {
// Convert the length field to an integer specifying the length
@ -177,14 +192,12 @@ public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
self.readState = .waitingForFrame(length: lengthFieldValue)
}
///
/// Attempts to read the body data for a given length. Updates the status is successful.
///
/// - parameters:
/// - buffer: The buffer containing the frame data.
/// - frameLength: The length of the frame data to be read.
///
private func readNextFrame(buffer: inout ByteBuffer, frameLength: Int) throws -> ByteBuffer? {
guard let contentsFieldSlice = buffer.readSlice(length: frameLength) else {
@ -196,13 +209,11 @@ public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
return contentsFieldSlice
}
///
/// Decodes the specified region of the buffer into an unadjusted frame length. The default implementation is
/// capable of decoding the specified region into an unsigned 8/16/24/32/64 bit integer.
///
/// - parameters:
/// - buffer: The buffer containing the integer frame length.
///
private func readFrameLength(for buffer: inout ByteBuffer) throws -> Int? {
let frameLength: Int?
switch self.lengthFieldLength.bitLength {

@ -34,30 +34,35 @@ extension ByteBuffer {
}
/// Error types from ``LengthFieldPrepender``
public enum LengthFieldPrependerError: Error {
/// More data was given than the maximum encodable length value.
case messageDataTooLongForLengthField
}
///
/// An encoder that takes a `ByteBuffer` message and prepends the number of bytes in the message.
/// The length field is always the same fixed length specified on construction.
/// These bytes contain a binary specification of the message size.
///
/// For example, if you received a packet with the 3 byte length (BCD)...
/// Given that the specified header length is 1 byte, there would be a single byte prepended which contains the number 3
///
/// +---+-----+
/// | A | BCD | ('A' contains 0x03)
/// +---+-----+
///
/// This initial prepended byte is called the 'length field'.
///
public final class LengthFieldPrepender: ChannelOutboundHandler {
///
/// An enumeration to describe the length of a piece of data in bytes.
///
public enum ByteLength {
/// One byte
case one
/// Two bytes
case two
/// Four bytes
case four
/// Eight bytes
case eight
fileprivate var bitLength: NIOLengthFieldBitLength {
@ -70,7 +75,9 @@ public final class LengthFieldPrepender: ChannelOutboundHandler {
}
}
/// `ByteBuffer` is the expected type to be given for encoding.
public typealias OutboundIn = ByteBuffer
/// Encoded output is passed in a `ByteBuffer`
public typealias OutboundOut = ByteBuffer
private let lengthFieldLength: NIOLengthFieldBitLength
@ -78,15 +85,19 @@ public final class LengthFieldPrepender: ChannelOutboundHandler {
private var lengthBuffer: ByteBuffer?
/// Create `LengthFieldPrepender` with a given length field length.
/// Create ``LengthFieldPrepender`` with a given length field length.
///
/// - parameters:
/// - lengthFieldLength: The length of the field specifying the remaining length of the frame.
/// - lengthFieldEndianness: The endianness of the field specifying the remaining length of the frame.
///
public convenience init(lengthFieldLength: ByteLength, lengthFieldEndianness: Endianness = .big) {
self.init(lengthFieldBitLength: lengthFieldLength.bitLength, lengthFieldEndianness: lengthFieldEndianness)
}
/// Create ``LengthFieldPrepender`` with a given length field length.
/// - parameters:
/// - lengthFieldBitLength: The length of the field specifying the remaining length of the frame.
/// - lengthFieldEndianness: The endianness of the field specifying the remaining length of the frame.
public init(lengthFieldBitLength: NIOLengthFieldBitLength, lengthFieldEndianness: Endianness = .big) {
// The value contained in the length field must be able to be represented by an integer type on the platform.
// ie. .eight == 64bit which would not fit into the Int type on a 32bit platform.

@ -23,7 +23,7 @@ import NIOCore
/// | AB | C\nDE | F\r\nGHI\n |
/// +----+-------+------------+
///
/// A instance of `LineBasedFrameDecoder` will split this buffer
/// A instance of ``LineBasedFrameDecoder`` will split this buffer
/// as follows:
///
/// +-----+-----+-----+
@ -31,7 +31,9 @@ import NIOCore
/// +-----+-----+-----+
///
public class LineBasedFrameDecoder: ByteToMessageDecoder {
/// `ByteBuffer` is the expected type passed in.
public typealias InboundIn = ByteBuffer
/// `ByteBuffer`s will be passed to the next stage.
public typealias InboundOut = ByteBuffer
@available(*, deprecated, message: "No longer used")
@ -40,7 +42,12 @@ public class LineBasedFrameDecoder: ByteToMessageDecoder {
private var lastScanOffset = 0
public init() { }
/// Decode data in the supplied buffer.
/// - Parameters:
/// - context: Calling cotext
/// - buffer: Buffer containing data to decode.
/// - Returns: State describing if more data is required.
public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
if let frame = try self.findNextFrame(buffer: &buffer) {
context.fireChannelRead(wrapInboundOut(frame))
@ -49,7 +56,14 @@ public class LineBasedFrameDecoder: ByteToMessageDecoder {
return .needMoreData
}
}
/// Decode all remaining data.
/// If it is not possible to consume all the data then ``NIOExtrasErrors/LeftOverBytesError`` is reported via `context.fireErrorCaught`
/// - Parameters:
/// - context: Calling context.
/// - buffer: Buffer containing the data to decode.
/// - seenEOF: Has end of file been seen.
/// - Returns: Always .needMoreData as all data will be consumed.
public func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
while try self.decode(context: context, buffer: &buffer) == .continue {}
if buffer.readableBytes > 0 {

@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
import NIOCore
/// Base type for errors from NIOExtras
public protocol NIOExtrasError: Equatable, Error { }
/// Errors that are raised in NIOExtras.

@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
/// An struct to describe the length of a piece of data in bits
/// A struct to describe the length of a piece of data in bits
public struct NIOLengthFieldBitLength {
internal enum Backing {
case bits8
@ -23,16 +23,26 @@ public struct NIOLengthFieldBitLength {
}
internal let bitLength: Backing
/// One byte - the same as ``eightBits``
public static let oneByte = NIOLengthFieldBitLength(bitLength: .bits8)
/// Two bytes - the same as ``sixteenBits``
public static let twoBytes = NIOLengthFieldBitLength(bitLength: .bits16)
/// Three bytes - the same as ``twentyFourBits``
public static let threeBytes = NIOLengthFieldBitLength(bitLength: .bits24)
/// Four bytes - the same as ``thirtyTwoBits``
public static let fourBytes = NIOLengthFieldBitLength(bitLength: .bits32)
/// Eight bytes - the same as ``sixtyFourBits``
public static let eightBytes = NIOLengthFieldBitLength(bitLength: .bits64)
/// Eight bits - the same as ``oneByte``
public static let eightBits = NIOLengthFieldBitLength(bitLength: .bits8)
/// Sixteen bits - the same as ``twoBytes``
public static let sixteenBits = NIOLengthFieldBitLength(bitLength: .bits16)
/// Twenty-four bits - the same as ``threeBytes``
public static let twentyFourBits = NIOLengthFieldBitLength(bitLength: .bits24)
/// Thirty-two bits - the same as ``fourBytes``
public static let thirtyTwoBits = NIOLengthFieldBitLength(bitLength: .bits32)
/// Sixty-four bits - the same as ``eightBytes``
public static let sixtyFourBits = NIOLengthFieldBitLength(bitLength: .bits64)
internal var length: Int {

@ -16,7 +16,7 @@ import NIOCore
// MARK: NIOPCAPRingBuffer
/// Storage for the most recent set of packets captured subject to constraints.
/// Use `addFragment` as the sink to a `NIOWritePCAPHandler` and call `emitPCAP`
/// Use ``addFragment(_:)`` as the sink to a ``NIOWritePCAPHandler`` and call ``emitPCAP()``
/// when you wish to get the recorded data.
/// - Warning: This class is not thread safe so should only be called from one thread.
public class NIOPCAPRingBuffer {

@ -191,8 +191,8 @@ private final class CollectAcceptedChannelsHandler: ChannelInboundHandler {
/// Helper that can be used to orchestrate the quiescing of a server `Channel` and all the child `Channel`s that are
/// open at a given point in time.
///
/// `ServerQuiescingHelper` makes it easy to collect all child `Channel`s that a given server `Channel` accepts. When
/// the quiescing period starts (that is when `ServerQuiescingHelper.initiateShutdown` is invoked), it will perform the
/// ``ServerQuiescingHelper`` makes it easy to collect all child `Channel`s that a given server `Channel` accepts. When
/// the quiescing period starts (that is when ``initiateShutdown(promise:)`` is invoked), it will perform the
/// following actions:
///
/// 1. close the server `Channel` so no further connections get accepted
@ -240,8 +240,9 @@ public final class ServerQuiescingHelper {
return CollectAcceptedChannelsHandler(channelCollector: collector)
}
/// Initiate the shutdown. The following actions will be performed:
/// Initiate the shutdown.
///
/// The following actions will be performed:
/// 1. close the server `Channel` so no further connections get accepted
/// 2. send a `ChannelShouldQuiesceEvent` user event to all currently still open child `Channel`s
/// 3. after all previously open child `Channel`s have closed, notify `promise`

@ -14,21 +14,25 @@
import NIOCore
/// `RequestResponseHandler` receives a `Request` alongside an `EventLoopPromise<Response>` from the `Channel`'s
/// ``RequestResponseHandler`` receives a `Request` alongside an `EventLoopPromise<Response>` from the `Channel`'s
/// outbound side. It will fulfill the promise with the `Response` once it's received from the `Channel`'s inbound
/// side.
///
/// `RequestResponseHandler` does support pipelining `Request`s and it will send them pipelined further down the
/// `Channel`. Should `RequestResponseHandler` receive an error from the `Channel`, it will fail all promises meant for
/// ``RequestResponseHandler`` does support pipelining `Request`s and it will send them pipelined further down the
/// `Channel`. Should ``RequestResponseHandler`` receive an error from the `Channel`, it will fail all promises meant for
/// the outstanding `Reponse`s and close the `Channel`. All requests enqueued after an error occured will be immediately
/// failed with the first error the channel received.
///
/// `RequestResponseHandler` requires that the `Response`s arrive on `Channel` in the same order as the `Request`s
/// ``RequestResponseHandler`` requires that the `Response`s arrive on `Channel` in the same order as the `Request`s
/// were submitted.
public final class RequestResponseHandler<Request, Response>: ChannelDuplexHandler {
/// `Response` is the type this class expects to receive inbound.
public typealias InboundIn = Response
/// Don't expect to pass anything on in-bound.
public typealias InboundOut = Never
/// Type this class expect to receive in an outbound direction.
public typealias OutboundIn = (Request, EventLoopPromise<Response>)
/// Type this class passes out.
public typealias OutboundOut = Request
private enum State {
@ -49,10 +53,10 @@ public final class RequestResponseHandler<Request, Response>: ChannelDuplexHandl
private var promiseBuffer: CircularBuffer<EventLoopPromise<Response>>
/// Create a new `RequestResponseHandler`.
/// Create a new ``RequestResponseHandler``.
///
/// - parameters:
/// - initialBufferCapacity: `RequestResponseHandler` saves the promises for all outstanding responses in a
/// - initialBufferCapacity: ``RequestResponseHandler`` saves the promises for all outstanding responses in a
/// buffer. `initialBufferCapacity` is the initial capacity for this buffer. You usually do not need to set
/// this parameter unless you intend to pipeline very deeply and don't want the buffer to resize.
public init(initialBufferCapacity: Int = 4) {

@ -119,7 +119,7 @@ struct PCAPRecordHeader {
/// example when your real network traffic is TLS protected (so `tcpdump`/Wireshark can't read it directly), or if you
/// don't have enough privileges on the running host to dump the network traffic.
///
/// `NIOWritePCAPHandler` will also work with Unix Domain Sockets in which case it will still synthesize a TCP packet
/// ``NIOWritePCAPHandler`` will also work with Unix Domain Sockets in which case it will still synthesize a TCP packet
/// capture with local address `111.111.111.111` (port `1111`) and remote address `222.222.222.222` (port `2222`).
public class NIOWritePCAPHandler: RemovableChannelHandler {
public enum Mode {
@ -127,21 +127,21 @@ public class NIOWritePCAPHandler: RemovableChannelHandler {
case server
}
/// Settings for `NIOWritePCAPHandler`.
/// Settings for ``NIOWritePCAPHandler``.
public struct Settings {
/// When to issue data into the `.pcap` file.
public enum EmitPCAP {
/// Write the data immediately when `NIOWritePCAPHandler` saw the event on the `ChannelPipeline`.
/// Write the data immediately when ``NIOWritePCAPHandler`` saw the event on the `ChannelPipeline`.
///
/// For writes this means when the `write` event is triggered. Please note that this will write potentially
/// unflushed data into the `.pcap` file.
///
/// If in doubt, prefer `.whenCompleted`.
/// If in doubt, prefer ``whenCompleted``.
case whenIssued
/// Write the data when the event completed.
///
/// For writes this means when the `write` promise is succeeded. The `whenCompleted` mode mirrors most
/// For writes this means when the `write` promise is succeeded. The ``whenCompleted`` mode mirrors most
/// closely what's actually sent over the wire.
case whenCompleted
}
@ -149,7 +149,7 @@ public class NIOWritePCAPHandler: RemovableChannelHandler {
/// When to emit the data from the `write` event into the `.pcap` file.
public var emitPCAPWrites: EmitPCAP
/// Default settings for the `NIOWritePCAPHandler`.
/// Default settings for the ``NIOWritePCAPHandler``.
public init() {
self = .init(emitPCAPWrites: .whenCompleted)
}
@ -157,7 +157,7 @@ public class NIOWritePCAPHandler: RemovableChannelHandler {
/// Settings with customization.
///
/// - parameters:
/// - emitPCAPWrites: When to issue the writes into the `.pcap` file, see `EmitPCAP`.
/// - emitPCAPWrites: When to issue the writes into the `.pcap` file, see ``EmitPCAP``.
public init(emitPCAPWrites: EmitPCAP) {
self.emitPCAPWrites = emitPCAPWrites
}
@ -184,20 +184,21 @@ public class NIOWritePCAPHandler: RemovableChannelHandler {
private var localAddress: SocketAddress?
private var remoteAddress: SocketAddress?
/// Reusable header for `. pcap` file.
public static var pcapFileHeader: ByteBuffer {
var buffer = ByteBufferAllocator().buffer(capacity: 24)
buffer.writePCAPHeader()
return buffer
}
/// Initialize a `NIOWritePCAPHandler`.
/// Initialize a ``NIOWritePCAPHandler``.
///
/// - parameters:
/// - fakeLocalAddress: Allows you to optionally override the local address to be different from the real one.
/// - fakeRemoteAddress: Allows you to optionally override the remote address to be different from the real one.
/// - settings: The settings for the `NIOWritePCAPHandler`.
/// - settings: The settings for the ``NIOWritePCAPHandler``.
/// - fileSink: The `fileSink` closure is called every time a new chunk of the `.pcap` file is ready to be
/// written to disk or elsewhere. See `SynchronizedFileSink` for a convenient way to write to
/// written to disk or elsewhere. See ``SynchronizedFileSink`` for a convenient way to write to
/// disk.
public init(mode: Mode,
fakeLocalAddress: SocketAddress? = nil,
@ -215,7 +216,7 @@ public class NIOWritePCAPHandler: RemovableChannelHandler {
}
}
/// Initialize a `NIOWritePCAPHandler` with default settings.
/// Initialize a ``NIOWritePCAPHandler`` with default settings.
///
/// - parameters:
/// - fakeLocalAddress: Allows you to optionally override the local address to be different from the real one.

@ -15,8 +15,10 @@
import CNIOExtrasZlib
import NIOCore
/// Namespace for compression code.
public enum NIOCompression {
/// Which algorithm should be used for compression.
public struct Algorithm: CustomStringConvertible, Equatable {
fileprivate enum AlgorithmEnum: String {
case gzip
@ -26,11 +28,14 @@ public enum NIOCompression {
/// return as String
public var description: String { return algorithm.rawValue }
/// `gzip` method
public static let gzip = Algorithm(algorithm: .gzip)
/// `deflate` method
public static let deflate = Algorithm(algorithm: .deflate)
}
/// Error types for ``NIOCompression``
public struct Error: Swift.Error, CustomStringConvertible, Equatable {
fileprivate enum ErrorEnum: String {
case uncompressedWritesPending
@ -40,11 +45,14 @@ public enum NIOCompression {
/// return as String
public var description: String { return error.rawValue }
/// There were writes pending which were not processed before shutdown.
public static let uncompressedWritesPending = Error(error: .uncompressedWritesPending)
/// Currently never used.
public static let noDataToWrite = Error(error: .noDataToWrite)
}
/// Data compression utility.
struct Compressor {
private var stream = z_stream()
var isActive = false

@ -46,9 +46,13 @@ public enum NIOHTTPDecompression {
}
}
/// Error types for ``NIOHTTPCompression``
public enum DecompressionError: Error, Equatable {
/// The set ``NIOHTTPDecompression/DecompressionLimit`` has been exceeded
case limit
/// An error occured when inflating. Error code is included to aid diagnosis.
case inflationError(Int)
/// Decoder could not be initialised. Error code is included to aid diagnosis.
case initializationError(Int)
}

@ -16,7 +16,7 @@ import CNIOExtrasZlib
import NIOCore
import NIOHTTP1
/// NIOHTTPResponseCompressor is an outbound channel handler that handles automatic streaming compression of
/// ``NIOHTTPRequestCompressor`` is an outbound channel handler that handles automatic streaming compression of
/// HTTP requests.
///
/// This compressor supports gzip and deflate. It works best if many writes are made between flushes.
@ -27,7 +27,9 @@ import NIOHTTP1
/// benefit. This channel handler should be present in the pipeline only for dynamically-generated and
/// highly-compressible content, which will see the biggest benefits from streaming compression.
public final class NIOHTTPRequestCompressor: ChannelOutboundHandler, RemovableChannelHandler {
/// Class takes `HTTPClientRequestPart` as the type to send.
public typealias OutboundIn = HTTPClientRequestPart
/// Passes `HTTPClientRequestPart` to the next stage in the pipeline when sending.
public typealias OutboundOut = HTTPClientRequestPart
/// Handler state
@ -53,18 +55,22 @@ public final class NIOHTTPRequestCompressor: ChannelOutboundHandler, RemovableCh
/// pending write promise
var pendingWritePromise: EventLoopPromise<Void>!
/// Initialize a NIOHTTPRequestCompressor
/// Initialize a ``NIOHTTPRequestCompressor``
/// - Parameter encoding: Compression algorithm to use
public init(encoding: NIOCompression.Algorithm) {
self.encoding = encoding
self.state = .idle
self.compressor = NIOCompression.Compressor()
}
/// Add handler to the pipeline.
/// - Parameter context: Calling context.
public func handlerAdded(context: ChannelHandlerContext) {
pendingWritePromise = context.eventLoop.makePromise()
}
/// Remove handler from pipeline.
/// - Parameter context: Calling context.
public func handlerRemoved(context: ChannelHandlerContext) {
pendingWritePromise.fail(NIOCompression.Error.uncompressedWritesPending)
compressor.shutdownIfActive()

@ -16,10 +16,15 @@ import CNIOExtrasZlib
import NIOHTTP1
import NIOCore
/// Channel hander to decompress incoming HTTP data.
public final class NIOHTTPRequestDecompressor: ChannelDuplexHandler, RemovableChannelHandler {
/// Expect to receive `HTTPServerRequestPart` from the network
public typealias InboundIn = HTTPServerRequestPart
/// Pass `HTTPServerRequestPart` to the next pipeline state in an inbound direction.
public typealias InboundOut = HTTPServerRequestPart
/// Pass through `HTTPServerResponsePart` outbound.
public typealias OutboundIn = HTTPServerResponsePart
/// Pass through `HTTPServerResponsePart` outbound.
public typealias OutboundOut = HTTPServerResponsePart
private struct Compression {
@ -30,6 +35,8 @@ public final class NIOHTTPRequestDecompressor: ChannelDuplexHandler, RemovableCh
private var decompressor: NIOHTTPDecompression.Decompressor
private var compression: Compression?
/// Initialise with limits.
/// - Parameter limit: Limit to how much inflation can occur to protect against bad cases.
public init(limit: NIOHTTPDecompression.DecompressionLimit) {
self.decompressor = NIOHTTPDecompression.Decompressor(limit: limit)
self.compression = nil

@ -46,7 +46,7 @@ private func qValueFromHeader<S: StringProtocol>(_ text: S) -> Float {
return qValue
}
/// A HTTPResponseCompressor is a duplex channel handler that handles automatic streaming compression of
/// A ``HTTPResponseCompressor`` is a duplex channel handler that handles automatic streaming compression of
/// HTTP responses. It respects the client's Accept-Encoding preferences, including q-values if present,
/// and ensures that clients are served the compression algorithm that works best for them.
///
@ -58,14 +58,20 @@ private func qValueFromHeader<S: StringProtocol>(_ text: S) -> Float {
/// benefit. This channel handler should be present in the pipeline only for dynamically-generated and
/// highly-compressible content, which will see the biggest benefits from streaming compression.
public final class HTTPResponseCompressor: ChannelDuplexHandler, RemovableChannelHandler {
/// This class accepts `HTTPServerRequestPart` inbound
public typealias InboundIn = HTTPServerRequestPart
/// This class emits `HTTPServerRequestPart` inbound.
public typealias InboundOut = HTTPServerRequestPart
/// This class accepts `HTTPServerResponsePart` outbound,
public typealias OutboundIn = HTTPServerResponsePart
/// This class emits `HTTPServerResponsePart` outbound.
public typealias OutboundOut = HTTPServerResponsePart
/// Errors which can occur when compressing
public enum CompressionError: Error {
// Writes were still pending when shutdown.
case uncompressedWritesPending
/// Data was somehow lost without being written.
case noDataToWrite
}
@ -79,16 +85,22 @@ public final class HTTPResponseCompressor: ChannelDuplexHandler, RemovableChanne
private let initialByteBufferCapacity: Int
/// Initialise a ``HTTPResponseCompressor``
/// - Parameter initialByteBufferCapacity: Initial size of buffer to allocate when hander is first added.
public init(initialByteBufferCapacity: Int = 1024) {
self.initialByteBufferCapacity = initialByteBufferCapacity
self.compressor = NIOCompression.Compressor()
}
/// Setup and add to the pipeline.
/// - Parameter context: Calling context.
public func handlerAdded(context: ChannelHandlerContext) {
pendingResponse = PartialHTTPResponse(bodyBuffer: context.channel.allocator.buffer(capacity: initialByteBufferCapacity))
pendingWritePromise = context.eventLoop.makePromise()
}
/// Remove channel handler from the pipeline.
/// - Parameter context: Calling context
public func handlerRemoved(context: ChannelHandlerContext) {
pendingWritePromise?.fail(CompressionError.uncompressedWritesPending)
compressor.shutdownIfActive()

@ -15,10 +15,15 @@
import NIOCore
import NIOHTTP1
/// Duplex channel handler which will accept deflate and gzip encoded responses and decompress them.
public final class NIOHTTPResponseDecompressor: ChannelDuplexHandler, RemovableChannelHandler {
/// Expect `HTTPClientResponsePart` inbound.
public typealias InboundIn = HTTPClientResponsePart
/// Sends `HTTPClientResponsePart` to the next pipeline stage inbound.
public typealias InboundOut = HTTPClientResponsePart
/// Expect `HTTPClientRequestPart` outbound.
public typealias OutboundIn = HTTPClientRequestPart
/// Send `HTTPClientRequestPart` to the next stage outbound.
public typealias OutboundOut = HTTPClientRequestPart
/// this struct encapsulates the state of a single http response decompression
@ -34,6 +39,8 @@ public final class NIOHTTPResponseDecompressor: ChannelDuplexHandler, RemovableC
private var compression: Compression? = nil
private var decompressor: NIOHTTPDecompression.Decompressor
/// Initialise
/// - Parameter limit: Limit on the amount of decompression allowed.
public init(limit: NIOHTTPDecompression.DecompressionLimit) {
self.decompressor = NIOHTTPDecompression.Decompressor(limit: limit)
}

@ -19,10 +19,13 @@ import NIOCore
/// channel's pipeline. Note that SOCKS only supports fully-qualified
/// domain names and IPv4 or IPv6 sockets, and not UNIX sockets.
public final class SOCKSClientHandler: ChannelDuplexHandler {
/// Accepts `ByteBuffer` as input where receiving.
public typealias InboundIn = ByteBuffer
/// Sends `ByteBuffer` to the next pipeline stage when receiving.
public typealias InboundOut = ByteBuffer
/// Accepts `ByteBuffer` as the type to send.
public typealias OutboundIn = ByteBuffer
/// Sends `ByteBuffer` to the next outbound stage.
public typealias OutboundOut = ByteBuffer
private let targetAddress: SOCKSAddress
@ -33,7 +36,7 @@ public final class SOCKSClientHandler: ChannelDuplexHandler {
private var bufferedWrites: MarkedCircularBuffer<(NIOAny, EventLoopPromise<Void>?)> = .init(initialCapacity: 8)
/// Creates a new `SOCKSClientHandler` that connects to a server
/// Creates a new ``SOCKSClientHandler`` that connects to a server
/// and instructs the server to connect to `targetAddress`.
/// - parameter targetAddress: The desired end point - note that only IPv4, IPv6, and FQDNs are supported.
public init(targetAddress: SOCKSAddress) {
@ -52,7 +55,9 @@ public final class SOCKSClientHandler: ChannelDuplexHandler {
public func channelActive(context: ChannelHandlerContext) {
self.beginHandshake(context: context)
}
/// Add handler to pipeline and start handshake.
/// - Parameter context: Calling context.
public func handlerAdded(context: ChannelHandlerContext) {
self.beginHandshake(context: context)
}

@ -17,13 +17,16 @@ import NIOCore
/// Add this handshake handler to the front of your channel, closest to the network.
/// The handler will receive bytes from the network and run them through a state machine
/// and parser to enforce SOCKSv5 protocol correctness. Inbound bytes will by parsed into
/// `ClientMessage` for downstream consumption. Send `ServerMessage` to this
/// ``ClientMessage`` for downstream consumption. Send ``ServerMessage`` to this
/// handler.
public final class SOCKSServerHandshakeHandler: ChannelDuplexHandler, RemovableChannelHandler {
/// Accepts `ByteBuffer` when receiving data.
public typealias InboundIn = ByteBuffer
/// Passes `ClientMessage` to the next stage of the pipeline when receiving data.
public typealias InboundOut = ClientMessage
/// Accepts `ServerMessage` when sending data.
public typealias OutboundIn = ServerMessage
/// Passes `ByteBuffer` to the next pipeline stage when sending data.
public typealias OutboundOut = ByteBuffer
var inboundBuffer: ByteBuffer?
@ -52,7 +55,9 @@ public final class SOCKSServerHandshakeHandler: ChannelDuplexHandler, RemovableC
context.fireErrorCaught(error)
}
}
/// Add hander to pipeline and enter state ready for connection establishment.
/// - Parameter context: Calling context
public func handlerAdded(context: ChannelHandlerContext) {
do {
try self.stateMachine.connectionEstablished()
@ -60,7 +65,9 @@ public final class SOCKSServerHandshakeHandler: ChannelDuplexHandler, RemovableC
context.fireErrorCaught(error)
}
}
/// Remove handler from channel pipeline. Causes any inbound buffer to be surfaced.
/// - Parameter context: Calling context.
public func handlerRemoved(context: ChannelHandlerContext) {
guard let buffer = self.inboundBuffer else {
return

@ -26,7 +26,7 @@ public struct ClientGreeting: Hashable {
/// The SOCKS server will select one to use.
public var methods: [AuthenticationMethod]
/// Creates a new `ClientGreeting`
/// Creates a new ``ClientGreeting``
/// - parameter methods: The client-supported authentication methods.
public init(methods: [AuthenticationMethod]) {
self.methods = methods

@ -35,7 +35,7 @@ public struct SOCKSRequest: Hashable {
/// The target host address.
public var addressType: SOCKSAddress
/// Creates a new `SOCKSRequest`.
/// Creates a new ``SOCKSRequest``.
/// - parameter command: How to connect to the host.
/// - parameter addressType: The target host address.
public init(command: SOCKSCommand, addressType: SOCKSAddress) {
@ -87,9 +87,10 @@ public struct SOCKSCommand: Hashable {
/// Used to establish an association within the UDP relay process to
/// handle UDP datagrams.
public static let udpAssociate = SOCKSCommand(value: 0x03)
/// Command value as defined in RFC
public var value: UInt8
public init(value: UInt8) {
self.value = value
}
@ -99,9 +100,9 @@ public struct SOCKSCommand: Hashable {
/// The address used to connect to the target host.
public enum SOCKSAddress: Hashable {
/// Socket Adress
case address(SocketAddress)
/// Host and port
case domain(String, port: Int)
static let ipv4IdentifierByte: UInt8 = 0x01

@ -30,7 +30,7 @@ public struct SOCKSResponse: Hashable {
/// The host address.
public var boundAddress: SOCKSAddress
/// Creates a new `SOCKSResponse`.
/// Creates a new ``SOCKSResponse``.
/// - parameter reply: The status of the connection - used to check if the request
/// succeeded or failed.
/// - parameter boundAddress: The host address.

@ -25,7 +25,7 @@ public struct SelectedAuthenticationMethod: Hashable {
/// The server's selected authentication method.
public var method: AuthenticationMethod
/// Creates a new `MethodSelection` wrapping an `AuthenticationMethod`.
/// Creates a new `MethodSelection` wrapping an ``AuthenticationMethod``.
/// - parameter method: The selected `AuthenticationMethod`.
public init(method: AuthenticationMethod) {
self.method = method