mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-05-14 08:52:42 +08:00
* 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>
249 lines
10 KiB
Swift
249 lines
10 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2021 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import NIOCore
|
|
|
|
extension ByteBuffer {
|
|
@inlinable
|
|
mutating func get24UInt(
|
|
at index: Int,
|
|
endianness: Endianness = .big
|
|
) -> UInt32? {
|
|
let mostSignificant: UInt16
|
|
let leastSignificant: UInt8
|
|
switch endianness {
|
|
case .big:
|
|
guard let uint16 = self.getInteger(at: index, endianness: .big, as: UInt16.self),
|
|
let uint8 = self.getInteger(at: index + 2, endianness: .big, as: UInt8.self) else { return nil }
|
|
mostSignificant = uint16
|
|
leastSignificant = uint8
|
|
case .little:
|
|
guard let uint8 = self.getInteger(at: index, endianness: .little, as: UInt8.self),
|
|
let uint16 = self.getInteger(at: index + 1, endianness: .little, as: UInt16.self) else { return nil }
|
|
mostSignificant = uint16
|
|
leastSignificant = uint8
|
|
}
|
|
return (UInt32(mostSignificant) << 8) &+ UInt32(leastSignificant)
|
|
}
|
|
@inlinable
|
|
mutating func read24UInt(
|
|
endianness: Endianness = .big
|
|
) -> UInt32? {
|
|
guard let integer = get24UInt(at: self.readerIndex, endianness: endianness) else { return nil }
|
|
self.moveReaderIndex(forwardBy: 3)
|
|
return integer
|
|
}
|
|
}
|
|
|
|
public enum NIOLengthFieldBasedFrameDecoderError: Error {
|
|
/// 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`
|
|
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:
|
|
///
|
|
/// +-----+------+
|
|
/// | BCD | FGHI |
|
|
/// +-----+------+
|
|
///
|
|
/// '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`
|
|
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 {
|
|
switch self {
|
|
case .one: return .oneByte
|
|
case .two: return .twoBytes
|
|
case .four: return .fourBytes
|
|
case .eight: return .eightBytes
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
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")
|
|
public var cumulationBuffer: ByteBuffer?
|
|
private var readState: DecoderReadState = .waitingForHeader
|
|
|
|
private let lengthFieldLength: NIOLengthFieldBitLength
|
|
private let lengthFieldEndianness: Endianness
|
|
|
|
/// Create `LengthFieldBasedFrameDecoder` with a given frame 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 `LengthFieldBasedFrameDecoder` with a given frame 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) {
|
|
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 {
|
|
try self.readNextLengthFieldToState(buffer: &buffer)
|
|
}
|
|
|
|
guard case .waitingForFrame(let frameLength) = self.readState else {
|
|
return .needMoreData
|
|
}
|
|
|
|
guard let frameBuffer = try self.readNextFrame(buffer: &buffer, frameLength: frameLength) else {
|
|
return .needMoreData
|
|
}
|
|
|
|
context.fireChannelRead(self.wrapInboundOut(frameBuffer))
|
|
|
|
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) {}
|
|
if buffer.readableBytes > 0 {
|
|
context.fireErrorCaught(NIOExtrasErrors.LeftOverBytesError(leftOverBytes: buffer))
|
|
}
|
|
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
|
|
guard let lengthFieldValue = try self.readFrameLength(for: &buffer) else {
|
|
return
|
|
}
|
|
|
|
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 {
|
|
return nil
|
|
}
|
|
|
|
self.readState = .waitingForHeader
|
|
|
|
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 {
|
|
case .bits8:
|
|
frameLength = buffer.readInteger(endianness: self.lengthFieldEndianness, as: UInt8.self).map { Int($0) }
|
|
case .bits16:
|
|
frameLength = buffer.readInteger(endianness: self.lengthFieldEndianness, as: UInt16.self).map { Int($0) }
|
|
case .bits24:
|
|
frameLength = buffer.read24UInt(endianness: self.lengthFieldEndianness).map { Int($0) }
|
|
case .bits32:
|
|
frameLength = try buffer.readInteger(endianness: self.lengthFieldEndianness, as: UInt32.self).map {
|
|
guard let size = Int(exactly: $0) else {
|
|
throw NIOLengthFieldBasedFrameDecoderError.lengthFieldValueTooLarge
|
|
}
|
|
return size
|
|
}
|
|
case .bits64:
|
|
frameLength = try buffer.readInteger(endianness: self.lengthFieldEndianness, as: UInt64.self).map {
|
|
guard let size = Int(exactly: $0) else {
|
|
throw NIOLengthFieldBasedFrameDecoderError.lengthFieldValueTooLarge
|
|
}
|
|
return size
|
|
}
|
|
}
|
|
|
|
if let frameLength = frameLength,
|
|
frameLength > LengthFieldBasedFrameDecoder.maxSupportedLengthFieldSize {
|
|
throw NIOLengthFieldBasedFrameDecoderError.lengthFieldValueLargerThanMaxSupportedSize
|
|
}
|
|
return frameLength
|
|
}
|
|
}
|