mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-05-14 08:52:42 +08:00
Adds a LengthFieldPrepender class to prepend the length onto a message.
This class is a type of byte to message encoder. Motivation: To encode a prepended length field on data so that messages of arbitrary size can be sent. Can work as a pair with the ‘LengthFieldBasedFrameDecoder’. Modifications: Added ‘LengthFieldPrepender’ Added unit tests for ‘LengthFieldPrepender’ in ‘LengthFieldPrependerTest’ Updated the linux text files by running the script. Result: The length can now be easily prepended to any message.
This commit is contained in:
parent
a9aafde504
commit
c6eae19fc7
97
Sources/NIOExtras/LengthFieldPrepender.swift
Normal file
97
Sources/NIOExtras/LengthFieldPrepender.swift
Normal file
@ -0,0 +1,97 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 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 NIO
|
||||
|
||||
///
|
||||
/// 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 a 3 byte length:
|
||||
/// +---+-----+
|
||||
/// | A | BCD | ('A' contains 0x0003)
|
||||
/// +---+-----+
|
||||
///
|
||||
/// Given that the specified header length is 1 byte, there would be a byte appended which contains the number 3
|
||||
/// This initial appended byte is called the 'length field'.
|
||||
///
|
||||
public final class LengthFieldPrepender: MessageToByteEncoder {
|
||||
|
||||
///
|
||||
/// An enumeration to describe the length of a piece of data in bytes.
|
||||
/// It is contained to lengths that can be converted to integer types.
|
||||
///
|
||||
public enum ByteLength {
|
||||
case one
|
||||
case two
|
||||
case four
|
||||
case eight
|
||||
|
||||
fileprivate var length: Int {
|
||||
|
||||
switch self {
|
||||
case .one:
|
||||
return 1
|
||||
case .two:
|
||||
return 2
|
||||
case .four:
|
||||
return 4
|
||||
case .eight:
|
||||
return 8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias OutboundIn = ByteBuffer
|
||||
public typealias OutbondOut = ByteBuffer
|
||||
|
||||
private let lengthFieldLength: ByteLength
|
||||
private let lengthFieldEndianness: Endianness
|
||||
|
||||
/// 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 init(lengthFieldLength: ByteLength, 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(lengthFieldLength.length <= Int.bitWidth/8)
|
||||
|
||||
self.lengthFieldLength = lengthFieldLength
|
||||
self.lengthFieldEndianness = lengthFieldEndianness
|
||||
}
|
||||
|
||||
public func allocateOutBuffer(ctx: ChannelHandlerContext, data: OutboundIn) throws -> ByteBuffer {
|
||||
return ctx.channel.allocator.buffer(capacity: self.lengthFieldLength.length + data.readableBytes)
|
||||
}
|
||||
|
||||
public func encode(ctx: ChannelHandlerContext, data: OutboundIn, out: inout ByteBuffer) throws {
|
||||
|
||||
switch self.lengthFieldLength {
|
||||
case .one:
|
||||
out.write(integer: UInt8(data.readableBytes), endianness: self.lengthFieldEndianness)
|
||||
case .two:
|
||||
out.write(integer: UInt16(data.readableBytes), endianness: self.lengthFieldEndianness)
|
||||
case .four:
|
||||
out.write(integer: UInt32(data.readableBytes), endianness: self.lengthFieldEndianness)
|
||||
case .eight:
|
||||
out.write(integer: UInt64(data.readableBytes), endianness: self.lengthFieldEndianness)
|
||||
}
|
||||
|
||||
out.write(bytes: data.readableBytesView)
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import XCTest
|
||||
XCTMain([
|
||||
testCase(FixedLengthFrameDecoderTest.allTests),
|
||||
testCase(LengthFieldBasedFrameDecoderTest.allTests),
|
||||
testCase(LengthFieldPrependerTest.allTests),
|
||||
testCase(LineBasedFrameDecoderTest.allTests),
|
||||
testCase(QuiescingHelperTest.allTests),
|
||||
])
|
||||
|
41
Tests/NIOExtrasTests/LengthFieldPrependerTest+XCTest.swift
Normal file
41
Tests/NIOExtrasTests/LengthFieldPrependerTest+XCTest.swift
Normal file
@ -0,0 +1,41 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// LengthFieldPrependerTest+XCTest.swift
|
||||
//
|
||||
import XCTest
|
||||
|
||||
///
|
||||
/// NOTE: This file was generated by generate_linux_tests.rb
|
||||
///
|
||||
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
||||
///
|
||||
|
||||
extension LengthFieldPrependerTest {
|
||||
|
||||
static var allTests : [(String, (LengthFieldPrependerTest) -> () throws -> Void)] {
|
||||
return [
|
||||
("testEncodeWithUInt8HeaderWithData", testEncodeWithUInt8HeaderWithData),
|
||||
("testEncodeWithUInt16HeaderWithString", testEncodeWithUInt16HeaderWithString),
|
||||
("testEncodeWithUInt32HeaderWithString", testEncodeWithUInt32HeaderWithString),
|
||||
("testEncodeWithUInt64HeaderWithString", testEncodeWithUInt64HeaderWithString),
|
||||
("testEncodeWithInt64HeaderWithString", testEncodeWithInt64HeaderWithString),
|
||||
("testEncodeWithUInt64HeaderStringBigEndian", testEncodeWithUInt64HeaderStringBigEndian),
|
||||
("testEncodeWithInt64HeaderStringDefaultingToBigEndian", testEncodeWithInt64HeaderStringDefaultingToBigEndian),
|
||||
("testEmptyBuffer", testEmptyBuffer),
|
||||
("testTooLargeFor256DefaultBuffer", testTooLargeFor256DefaultBuffer),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
312
Tests/NIOExtrasTests/LengthFieldPrependerTest.swift
Normal file
312
Tests/NIOExtrasTests/LengthFieldPrependerTest.swift
Normal file
@ -0,0 +1,312 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 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 XCTest
|
||||
import NIO
|
||||
import NIOExtras
|
||||
|
||||
private let standardDataString = "abcde"
|
||||
|
||||
class LengthFieldPrependerTest: XCTestCase {
|
||||
|
||||
private var channel: EmbeddedChannel!
|
||||
private var encoderUnderTest: LengthFieldPrepender!
|
||||
|
||||
override func setUp() {
|
||||
self.channel = EmbeddedChannel()
|
||||
}
|
||||
|
||||
func testEncodeWithUInt8HeaderWithData() throws {
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .one,
|
||||
lengthFieldEndianness: .little)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
let dataBytes: [UInt8] = [10, 20, 30, 40]
|
||||
let extepectedData: [UInt8] = [UInt8(dataBytes.count)] + dataBytes
|
||||
|
||||
var buffer = self.channel.allocator.buffer(capacity: dataBytes.count)
|
||||
buffer.write(bytes: dataBytes)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let outputData = outputBuffer.readBytes(length: outputBuffer.readableBytes)
|
||||
XCTAssertEqual(extepectedData, outputData)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
|
||||
func testEncodeWithUInt16HeaderWithString() throws {
|
||||
|
||||
let endianness: Endianness = .little
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .two,
|
||||
lengthFieldEndianness: endianness)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
var buffer = self.channel.allocator.buffer(capacity: standardDataString.count)
|
||||
buffer.write(string: standardDataString)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let sizeInHeader = outputBuffer.readInteger(endianness: endianness, as: UInt16.self).map({ Int($0) })
|
||||
XCTAssertEqual(standardDataString.count, sizeInHeader)
|
||||
|
||||
let bodyString = outputBuffer.readString(length: standardDataString.count)
|
||||
XCTAssertEqual(standardDataString, bodyString)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
|
||||
func testEncodeWithUInt32HeaderWithString() throws {
|
||||
|
||||
let endianness: Endianness = .little
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .four,
|
||||
lengthFieldEndianness: endianness)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
var buffer = self.channel.allocator.buffer(capacity: standardDataString.count)
|
||||
buffer.write(string: standardDataString)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let sizeInHeader = outputBuffer.readInteger(endianness: endianness, as: UInt32.self).map({ Int($0) })
|
||||
XCTAssertEqual(standardDataString.count, sizeInHeader)
|
||||
|
||||
let bodyString = outputBuffer.readString(length: standardDataString.count)
|
||||
XCTAssertEqual(standardDataString, bodyString)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
|
||||
func testEncodeWithUInt64HeaderWithString() throws {
|
||||
|
||||
let endianness: Endianness = .little
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .eight,
|
||||
lengthFieldEndianness: endianness)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
var buffer = self.channel.allocator.buffer(capacity: standardDataString.count)
|
||||
buffer.write(string: standardDataString)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let sizeInHeader = outputBuffer.readInteger(endianness: endianness, as: UInt64.self).map({ Int($0) })
|
||||
XCTAssertEqual(standardDataString.count, sizeInHeader)
|
||||
|
||||
let bodyString = outputBuffer.readString(length: standardDataString.count)
|
||||
XCTAssertEqual(standardDataString, bodyString)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
|
||||
func testEncodeWithInt64HeaderWithString() throws {
|
||||
|
||||
let endianness: Endianness = .little
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .eight,
|
||||
lengthFieldEndianness: endianness)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
var buffer = self.channel.allocator.buffer(capacity: standardDataString.count)
|
||||
buffer.write(string: standardDataString)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let sizeInHeader = outputBuffer.readInteger(endianness: endianness, as: Int64.self).map({ Int($0) })
|
||||
XCTAssertEqual(standardDataString.count, sizeInHeader)
|
||||
|
||||
let bodyString = outputBuffer.readString(length: standardDataString.count)
|
||||
XCTAssertEqual(standardDataString, bodyString)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
|
||||
func testEncodeWithUInt64HeaderStringBigEndian() throws {
|
||||
|
||||
let endianness: Endianness = .big
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .eight,
|
||||
lengthFieldEndianness: endianness)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
var buffer = self.channel.allocator.buffer(capacity: standardDataString.count)
|
||||
buffer.write(string: standardDataString)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let sizeInHeader = outputBuffer.readInteger(endianness: endianness, as: UInt64.self).map({ Int($0) })
|
||||
XCTAssertEqual(standardDataString.count, sizeInHeader)
|
||||
|
||||
let bodyString = outputBuffer.readString(length: standardDataString.count)
|
||||
XCTAssertEqual(standardDataString, bodyString)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
|
||||
func testEncodeWithInt64HeaderStringDefaultingToBigEndian() throws {
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .eight)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
var buffer = self.channel.allocator.buffer(capacity: standardDataString.count)
|
||||
buffer.write(string: standardDataString)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let sizeInHeader = outputBuffer.readInteger(endianness: .big, as: UInt64.self).map({ Int($0) })
|
||||
XCTAssertEqual(standardDataString.count, sizeInHeader)
|
||||
|
||||
let bodyString = outputBuffer.readString(length: standardDataString.count)
|
||||
XCTAssertEqual(standardDataString, bodyString)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
|
||||
func testEmptyBuffer() throws {
|
||||
|
||||
let endianness: Endianness = .little
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .eight,
|
||||
lengthFieldEndianness: endianness)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
let buffer = self.channel.allocator.buffer(capacity: 0)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let sizeInHeader = outputBuffer.readInteger(endianness: endianness, as: UInt64.self).map({ Int($0) })
|
||||
XCTAssertEqual(0, sizeInHeader)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
|
||||
func testTooLargeFor256DefaultBuffer() throws {
|
||||
|
||||
// Default implementation of 'MessageToByteEncoder' allocates a `ByteBuffer` with capacity of `256`
|
||||
|
||||
let endianness: Endianness = .little
|
||||
|
||||
self.encoderUnderTest = LengthFieldPrepender(lengthFieldLength: .eight,
|
||||
lengthFieldEndianness: endianness)
|
||||
|
||||
try? self.channel.pipeline.add(handler: self.encoderUnderTest).wait()
|
||||
|
||||
let contents = Array<UInt8>(repeating: 200, count: 256)
|
||||
|
||||
var buffer = self.channel.allocator.buffer(capacity: contents.count)
|
||||
buffer.write(bytes: contents)
|
||||
|
||||
try self.channel.writeAndFlush(buffer).wait()
|
||||
|
||||
if case .some(.byteBuffer(var outputBuffer)) = self.channel.readOutbound() {
|
||||
|
||||
let sizeInHeader = outputBuffer.readInteger(endianness: endianness, as: UInt64.self).map({ Int($0) })
|
||||
XCTAssertEqual(contents.count, sizeInHeader)
|
||||
|
||||
let bodyData = outputBuffer.readBytes(length: contents.count)
|
||||
XCTAssertEqual(contents, bodyData)
|
||||
|
||||
let additionalData = outputBuffer.readBytes(length: 1)
|
||||
XCTAssertNil(additionalData)
|
||||
|
||||
} else {
|
||||
XCTFail("couldn't read ByteBuffer from channel")
|
||||
}
|
||||
|
||||
XCTAssertFalse(try self.channel.finish())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user