//===----------------------------------------------------------------------===// // // 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?) { 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 {}