swift-nio-extras/Sources/NIOExtras/LengthFieldBasedFrameDecoder.swift
Cory Benfield 9cdb93e321
Drop Swift 5.5 (#197)
Motivation

Per SwiftNIO's formal version policy, we are ready to drop support for
Swift 5.5.

Modifications

This patch removes the support for 5.5 and all supporting
infrastructure. This includes the test generation functionality, which
is no longer required, as well as the files generated by that
functionality. It updates the dockerfile for 5.8, and it removes all
conditional compilation checks that are now definitionally true.

Result

A nice, clean, 5.6+ codebase
2023-04-13 16:47:28 +01:00

252 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
}
}
@available(*, unavailable)
extension LengthFieldBasedFrameDecoder: Sendable {}