mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-05-15 01:18:58 +08:00
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
150 lines
5.9 KiB
Swift
150 lines
5.9 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 {
|
|
@discardableResult
|
|
@inlinable
|
|
mutating func write24UInt(
|
|
_ integer: UInt32,
|
|
endianness: Endianness = .big
|
|
) -> Int {
|
|
precondition(integer & 0xFF_FF_FF == integer, "integer value does not fit into 24 bit integer")
|
|
switch endianness {
|
|
case .little:
|
|
return writeInteger(UInt8(integer & 0xFF), endianness: .little) +
|
|
writeInteger(UInt16((integer >> 8) & 0xFF_FF), endianness: .little)
|
|
case .big:
|
|
return writeInteger(UInt16((integer >> 8) & 0xFF_FF), endianness: .big) +
|
|
writeInteger(UInt8(integer & 0xFF), endianness: .big)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// 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 {
|
|
switch self {
|
|
case .one: return .oneByte
|
|
case .two: return .twoBytes
|
|
case .four: return .fourBytes
|
|
case .eight: return .eightBytes
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `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
|
|
private let lengthFieldEndianness: Endianness
|
|
|
|
private var lengthBuffer: ByteBuffer?
|
|
|
|
/// 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.
|
|
precondition(lengthFieldBitLength.length <= Int.bitWidth/8)
|
|
|
|
self.lengthFieldLength = lengthFieldBitLength
|
|
self.lengthFieldEndianness = lengthFieldEndianness
|
|
}
|
|
|
|
public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
|
|
|
|
let dataBuffer = self.unwrapOutboundIn(data)
|
|
let dataLength = dataBuffer.readableBytes
|
|
|
|
guard dataLength <= self.lengthFieldLength.max else {
|
|
promise?.fail(LengthFieldPrependerError.messageDataTooLongForLengthField)
|
|
return
|
|
}
|
|
|
|
var dataLengthBuffer: ByteBuffer
|
|
|
|
if let existingBuffer = self.lengthBuffer {
|
|
dataLengthBuffer = existingBuffer
|
|
dataLengthBuffer.clear()
|
|
} else {
|
|
dataLengthBuffer = context.channel.allocator.buffer(capacity: self.lengthFieldLength.length)
|
|
self.lengthBuffer = dataLengthBuffer
|
|
}
|
|
|
|
switch self.lengthFieldLength.bitLength {
|
|
case .bits8:
|
|
dataLengthBuffer.writeInteger(UInt8(dataLength), endianness: self.lengthFieldEndianness)
|
|
case .bits16:
|
|
dataLengthBuffer.writeInteger(UInt16(dataLength), endianness: self.lengthFieldEndianness)
|
|
case .bits24:
|
|
dataLengthBuffer.write24UInt(UInt32(dataLength), endianness: self.lengthFieldEndianness)
|
|
case .bits32:
|
|
dataLengthBuffer.writeInteger(UInt32(dataLength), endianness: self.lengthFieldEndianness)
|
|
case .bits64:
|
|
dataLengthBuffer.writeInteger(UInt64(dataLength), endianness: self.lengthFieldEndianness)
|
|
}
|
|
|
|
context.write(self.wrapOutboundOut(dataLengthBuffer), promise: nil)
|
|
context.write(data, promise: promise)
|
|
}
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
extension LengthFieldPrepender: Sendable {}
|