swift-nio/Tests/NIOPosixTests/CodecTest.swift

1748 lines
75 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 XCTest
@testable import NIOCore
import NIOEmbedded
@testable import NIOPosix
private var testDecoderIsNotQuadratic_mallocs = 0
private var testDecoderIsNotQuadratic_reallocs = 0
private func testDecoderIsNotQuadratic_freeHook(_ ptr: UnsafeMutableRawPointer) -> Void {
free(ptr)
}
private func testDecoderIsNotQuadratic_mallocHook(_ size: Int) -> UnsafeMutableRawPointer? {
testDecoderIsNotQuadratic_mallocs += 1
return malloc(size)
}
private func testDecoderIsNotQuadratic_reallocHook(_ ptr: UnsafeMutableRawPointer?, _ count: Int) -> UnsafeMutableRawPointer? {
testDecoderIsNotQuadratic_reallocs += 1
return realloc(ptr, count)
}
private func testDecoderIsNotQuadratic_memcpyHook(_ dst: UnsafeMutableRawPointer, _ src: UnsafeRawPointer, _ count: Int) -> Void {
_ = memcpy(dst, src, count)
}
private final class ChannelInactivePromiser: ChannelInboundHandler {
typealias InboundIn = Any
let channelInactivePromise: EventLoopPromise<Void>
init(channel: Channel) {
channelInactivePromise = channel.eventLoop.makePromise()
}
func channelInactive(context: ChannelHandlerContext) {
channelInactivePromise.succeed(())
}
}
public final class ByteToMessageDecoderTest: XCTestCase {
private final class ByteToInt32Decoder : ByteToMessageDecoder {
typealias InboundIn = ByteBuffer
typealias InboundOut = Int32
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) -> DecodingState {
guard buffer.readableBytes >= MemoryLayout<Int32>.size else {
return .needMoreData
}
context.fireChannelRead(self.wrapInboundOut(buffer.readInteger()!))
return .continue
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
return self.decode(context: context, buffer: &buffer)
}
}
private final class ForeverDecoder: ByteToMessageDecoder {
typealias InboundIn = ByteBuffer
typealias InboundOut = Never
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) -> DecodingState {
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
return self.decode(context: context, buffer: &buffer)
}
}
private final class LargeChunkDecoder: ByteToMessageDecoder {
typealias InboundIn = ByteBuffer
typealias InboundOut = ByteBuffer
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) -> DecodingState {
guard case .some(let buffer) = buffer.readSlice(length: 512) else {
return .needMoreData
}
context.fireChannelRead(self.wrapInboundOut(buffer))
return .continue
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
return self.decode(context: context, buffer: &buffer)
}
}
// A special case decoder that decodes only once there is 5,120 bytes in the buffer,
// at which point it decodes exactly 2kB of memory.
private final class OnceDecoder: ByteToMessageDecoder {
typealias InboundIn = ByteBuffer
typealias InboundOut = ByteBuffer
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) -> DecodingState {
guard buffer.readableBytes >= 5120 else {
return .needMoreData
}
context.fireChannelRead(self.wrapInboundOut(buffer.readSlice(length: 2048)!))
return .continue
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
return self.decode(context: context, buffer: &buffer)
}
}
func testDecoder() throws {
let channel = EmbeddedChannel()
_ = try channel.pipeline.addHandler(ByteToMessageHandler(ByteToInt32Decoder())).wait()
var buffer = channel.allocator.buffer(capacity: 32)
buffer.writeInteger(Int32(1))
let writerIndex = buffer.writerIndex
buffer.moveWriterIndex(to: writerIndex - 1)
channel.pipeline.fireChannelRead(NIOAny(buffer))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
buffer.moveWriterIndex(to: writerIndex)
channel.pipeline.fireChannelRead(NIOAny(buffer.getSlice(at: writerIndex - 1, length: 1)!))
var buffer2 = channel.allocator.buffer(capacity: 32)
buffer2.writeInteger(Int32(2))
buffer2.writeInteger(Int32(3))
channel.pipeline.fireChannelRead(NIOAny(buffer2))
XCTAssertNoThrow(try channel.finish())
XCTAssertNoThrow(XCTAssertEqual(Int32(1), try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual(Int32(2), try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual(Int32(3), try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
}
func testDecoderPropagatesChannelInactive() throws {
let channel = EmbeddedChannel()
defer {
XCTAssertNoThrow(try channel.finish())
}
let inactivePromiser = ChannelInactivePromiser(channel: channel)
_ = try channel.pipeline.addHandler(ByteToMessageHandler(ByteToInt32Decoder())).wait()
_ = try channel.pipeline.addHandler(inactivePromiser).wait()
var buffer = channel.allocator.buffer(capacity: 32)
buffer.writeInteger(Int32(1))
channel.pipeline.fireChannelRead(NIOAny(buffer))
XCTAssertNoThrow(XCTAssertEqual(Int32(1), try channel.readInbound()))
XCTAssertFalse(inactivePromiser.channelInactivePromise.futureResult.isFulfilled)
channel.pipeline.fireChannelInactive()
XCTAssertTrue(inactivePromiser.channelInactivePromise.futureResult.isFulfilled)
}
func testDecoderIsNotQuadratic() throws {
let channel = EmbeddedChannel()
defer {
XCTAssertNoThrow(try channel.finish())
}
XCTAssertEqual(testDecoderIsNotQuadratic_mallocs, 0)
XCTAssertEqual(testDecoderIsNotQuadratic_reallocs, 0)
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(ForeverDecoder())).wait())
let dummyAllocator = ByteBufferAllocator(hookedMalloc: testDecoderIsNotQuadratic_mallocHook,
hookedRealloc: testDecoderIsNotQuadratic_reallocHook,
hookedFree: testDecoderIsNotQuadratic_freeHook,
hookedMemcpy: testDecoderIsNotQuadratic_memcpyHook)
channel.allocator = dummyAllocator
var inputBuffer = dummyAllocator.buffer(capacity: 8)
inputBuffer.writeStaticString("whatwhat")
for _ in 0..<10 {
channel.pipeline.fireChannelRead(NIOAny(inputBuffer))
}
// We get one extra malloc the first time around the loop, when we have aliased the buffer. From then on it's
// all reallocs of the underlying buffer.
XCTAssertEqual(testDecoderIsNotQuadratic_mallocs, 2)
XCTAssertEqual(testDecoderIsNotQuadratic_reallocs, 3)
}
func testMemoryIsReclaimedIfMostIsConsumed() {
let channel = EmbeddedChannel()
defer {
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
let decoder = ByteToMessageHandler(LargeChunkDecoder())
XCTAssertNoThrow(try channel.pipeline.addHandler(decoder).wait())
// We're going to send in 513 bytes. This will cause a chunk to be passed on, and will leave
// a 512-byte empty region in a byte buffer with a capacity of 1024 bytes. Since 512 empty
// bytes are exactly 50% of the buffers capacity and not one tiny bit more, the empty space
// will not be reclaimed.
var buffer = channel.allocator.buffer(capacity: 513)
buffer.writeBytes(Array(repeating: 0x04, count: 513))
XCTAssertNoThrow(XCTAssertTrue(try channel.writeInbound(buffer).isFull))
XCTAssertNoThrow(XCTAssertEqual(ByteBuffer(repeating: 0x04, count: 512), try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertEqual(decoder.cumulationBuffer!.capacity, 1024)
XCTAssertEqual(decoder.cumulationBuffer!.readableBytes, 1)
XCTAssertEqual(decoder.cumulationBuffer!.readerIndex, 512)
// Next we're going to send in another 513 bytes. This will cause another chunk to be passed
// into our decoder buffer, which has a capacity of 1024 bytes, before we pass in another
// 513 bytes. Since we already have written to 513 bytes, there isn't enough space in the
// buffer, which will cause a resize to a new underlying storage with 2048 bytes. Since the
// `LargeChunkDecoder` has consumed another 512 bytes, there are now two bytes left to read
// (513 + 513) - (512 + 512). The reader index is at 1024. The empty space has not been
// reclaimed: While the capacity is more than 1024 bytes (2048 bytes), the reader index is
// now at 1024. This means the buffer is exactly 50% consumed and not a tiny bit more, which
// means no space will be reclaimed.
XCTAssertNoThrow(XCTAssertTrue(try channel.writeInbound(buffer).isFull))
XCTAssertNoThrow(XCTAssertEqual(ByteBuffer(repeating: 0x04, count: 512), try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertEqual(decoder.cumulationBuffer!.capacity, 2048)
XCTAssertEqual(decoder.cumulationBuffer!.readableBytes, 2)
XCTAssertEqual(decoder.cumulationBuffer!.readerIndex, 1024)
// Finally we're going to send in another 513 bytes. This will cause another chunk to be
// passed into our decoder buffer, which has a capacity of 2048 bytes. Since the buffer has
// enough available space (1022 bytes) there will be no buffer resize before the decoding.
// After the decoding of another 512 bytes, the buffer will have 1536 empty bytes
// (3 * 512 bytes). This means that 75% of the buffer's capacity can now be reclaimed, which
// will lead to a reclaim. The resulting buffer will have a capacity of 2048 bytes (based
// on its previous growth), with 3 readable bytes remaining.
XCTAssertNoThrow(XCTAssertTrue(try channel.writeInbound(buffer).isFull))
XCTAssertNoThrow(XCTAssertEqual(ByteBuffer(repeating: 0x04, count: 512), try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertEqual(decoder.cumulationBuffer!.capacity, 2048)
XCTAssertEqual(decoder.cumulationBuffer!.readableBytes, 3)
XCTAssertEqual(decoder.cumulationBuffer!.readerIndex, 0)
}
func testMemoryIsReclaimedIfLotsIsAvailable() {
let channel = EmbeddedChannel()
defer {
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
let decoder = ByteToMessageHandler(OnceDecoder())
XCTAssertNoThrow(try channel.pipeline.addHandler(decoder).wait())
// We're going to send in 5119 bytes. This will be held.
var buffer = channel.allocator.buffer(capacity: 5119)
buffer.writeBytes(Array(repeating: 0x04, count: 5119))
XCTAssertNoThrow(XCTAssertTrue(try channel.writeInbound(buffer).isEmpty))
XCTAssertEqual(decoder.cumulationBuffer!.readableBytes, 5119)
XCTAssertEqual(decoder.cumulationBuffer!.readerIndex, 0)
// Now we're going to send in one more byte. This will cause a chunk to be passed on,
// shrinking the held memory to 3072 bytes. However, memory will be reclaimed.
XCTAssertNoThrow(XCTAssertTrue(try channel.writeInbound(buffer.getSlice(at: 0, length: 1)).isFull))
XCTAssertNoThrow(XCTAssertEqual(ByteBuffer(repeating: 0x04, count: 2048), try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertEqual(decoder.cumulationBuffer!.readableBytes, 3072)
XCTAssertEqual(decoder.cumulationBuffer!.readerIndex, 0)
}
func testWeDoNotCallShouldReclaimMemoryAsLongAsWeContinue() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
var numberOfDecodeCalls = 0
var numberOfShouldReclaimCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.numberOfDecodeCalls += 1
guard buffer.readSlice(length: [2048, 1024, 512, 256,
128, 64, .max][self.numberOfDecodeCalls - 1]) != nil else {
return .needMoreData
}
XCTAssertEqual(0, self.numberOfShouldReclaimCalls)
return .continue
}
func shouldReclaimBytes(buffer: ByteBuffer) -> Bool {
XCTAssertEqual(7, self.numberOfDecodeCalls)
XCTAssertEqual(64, buffer.readableBytes)
self.numberOfShouldReclaimCalls += 1
return false
}
func decodeLast(context: ChannelHandlerContext,
buffer: inout ByteBuffer,
seenEOF: Bool) throws -> DecodingState {
XCTAssertEqual(64, buffer.readableBytes)
XCTAssertTrue(seenEOF)
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
defer {
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
let buffer = ByteBuffer(repeating: 0, count: 4096)
XCTAssertEqual(4096, buffer.storageCapacity)
// We're sending 4096 bytes. The decoder will do:
// 1. read 2048 -> .continue
// 2. read 1024 -> .continue
// 3. read 512 -> .continue
// 4. read 256 -> .continue
// 5. read 128 -> .continue
// 6. read 64 -> .continue
// 7. read Int.max -> .needMoreData
//
// So we're expecting 7 decode calls but only 1 call to shouldReclaimBytes (at the end).
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertEqual(7, decoder.numberOfDecodeCalls)
XCTAssertEqual(1, decoder.numberOfShouldReclaimCalls)
}
func testDecoderReentranceChannelRead() throws {
let channel = EmbeddedChannel()
defer {
XCTAssertNoThrow(try channel.finish())
}
class TestDecoder: ByteToMessageDecoder {
typealias InboundIn = ByteBuffer
typealias InboundOut = ByteBuffer
var numberOfDecodeCalls = 0
var hasReentranced = false
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.numberOfDecodeCalls += 1
var reentrantWriteBuffer = context.channel.allocator.buffer(capacity: 1)
if self.numberOfDecodeCalls == 2 {
// this is the first time, let's fireChannelRead
self.hasReentranced = true
reentrantWriteBuffer.clear()
reentrantWriteBuffer.writeStaticString("3")
context.channel.pipeline.fireChannelRead(self.wrapInboundOut(reentrantWriteBuffer))
}
context.fireChannelRead(self.wrapInboundOut(buffer.readSlice(length: 1)!))
if self.numberOfDecodeCalls == 2 {
reentrantWriteBuffer.clear()
reentrantWriteBuffer.writeStaticString("4")
context.channel.pipeline.fireChannelRead(self.wrapInboundOut(reentrantWriteBuffer))
}
return .continue
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
return .needMoreData
}
}
let testDecoder = TestDecoder()
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(testDecoder)).wait())
var inputBuffer = channel.allocator.buffer(capacity: 4)
/* 1 */
inputBuffer.writeStaticString("1")
XCTAssertTrue(try channel.writeInbound(inputBuffer).isFull)
inputBuffer.clear()
/* 2 */
inputBuffer.writeStaticString("2")
XCTAssertTrue(try channel.writeInbound(inputBuffer).isFull)
inputBuffer.clear()
/* 3 */
inputBuffer.writeStaticString("5")
XCTAssertTrue(try channel.writeInbound(inputBuffer).isFull)
inputBuffer.clear()
func readOneInboundString() -> String {
do {
switch try channel.readInbound(as: ByteBuffer.self) {
case .some(let buffer):
return String(decoding: buffer.readableBytesView, as: Unicode.UTF8.self)
case .none:
XCTFail("expected ByteBuffer found nothing")
return "no, error from \(#line)"
}
} catch {
XCTFail("unexpected error: \(error)")
return "no, error from \(#line)"
}
}
channel.embeddedEventLoop.run()
XCTAssertEqual("1", readOneInboundString())
XCTAssertEqual("2", readOneInboundString())
XCTAssertEqual("3", readOneInboundString())
XCTAssertEqual("4", readOneInboundString())
XCTAssertEqual("5", readOneInboundString())
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound(as: IOData.self)))
XCTAssertTrue(testDecoder.hasReentranced)
}
func testTrivialDecoderDoesSensibleStuffWhenCloseInRead() {
class HandItThroughDecoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
var decodeLastCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
let originalBuffer = buffer
context.fireChannelRead(self.wrapInboundOut(buffer.readSlice(length: buffer.readableBytes)!))
if originalBuffer.readableBytesView.last == "0".utf8.last {
context.close().whenFailure { error in
XCTFail("unexpected error: \(error)")
}
}
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
self.decodeLastCalls += 1
XCTAssertEqual(1, self.decodeLastCalls)
return .needMoreData
}
}
let decoder = HandItThroughDecoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
XCTAssertTrue(channel.isActive)
var buffer = channel.allocator.buffer(capacity: 16)
buffer.clear()
buffer.writeStaticString("1")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("23")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("4567890")
XCTAssertNoThrow(try channel.writeInbound(buffer))
channel.embeddedEventLoop.run()
XCTAssertFalse(channel.isActive)
XCTAssertNoThrow(XCTAssertEqual("1", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("23", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("4567890", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertEqual(1, decoder.decodeLastCalls)
}
func testLeftOversMakeDecodeLastCalled() {
let lastPromise = EmbeddedEventLoop().makePromise(of: ByteBuffer.self)
let channel = EmbeddedChannel(handler: ByteToMessageHandler(PairOfBytesDecoder(lastPromise: lastPromise)))
var buffer = channel.allocator.buffer(capacity: 16)
buffer.clear()
buffer.writeStaticString("1")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("23")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("4567890x")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(try channel.close().wait())
XCTAssertFalse(channel.isActive)
XCTAssertNoThrow(XCTAssertEqual("12", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("34", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("56", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("78", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("90", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("x", String(decoding: try lastPromise.futureResult.wait().readableBytesView,
as: Unicode.UTF8.self)))
}
func testRemovingHandlerMakesLeftoversAppearInDecodeLast() {
let lastPromise = EmbeddedEventLoop().makePromise(of: ByteBuffer.self)
let decoder = PairOfBytesDecoder(lastPromise: lastPromise)
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
defer {
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
var buffer = channel.allocator.buffer(capacity: 16)
buffer.clear()
buffer.writeStaticString("1")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("23")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("4567890x")
XCTAssertNoThrow(try channel.writeInbound(buffer))
channel.pipeline.context(handlerType: ByteToMessageHandler<PairOfBytesDecoder>.self).flatMap { context in
return channel.pipeline.removeHandler(context: context)
}.whenFailure { error in
XCTFail("unexpected error: \(error)")
}
XCTAssertNoThrow(XCTAssertEqual("12", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("34", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("56", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("78", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertEqual("90", try channel.readInbound(as: ByteBuffer.self).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
channel.embeddedEventLoop.run()
XCTAssertNoThrow(XCTAssertEqual("x", String(decoding: try lastPromise.futureResult.wait().readableBytesView,
as: Unicode.UTF8.self)))
XCTAssertEqual(1, decoder.decodeLastCalls)
}
func testStructsWorkAsByteToMessageDecoders() {
struct WantsOneThenTwoBytesDecoder: ByteToMessageDecoder {
typealias InboundOut = Int
var state: Int = 1
mutating func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
if buffer.readSlice(length: self.state) != nil {
defer {
self.state += 1
}
context.fireChannelRead(self.wrapInboundOut(self.state))
return .continue
} else {
return .needMoreData
}
}
mutating func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
context.fireChannelRead(self.wrapInboundOut(buffer.readableBytes * -1))
return .needMoreData
}
}
let channel = EmbeddedChannel(handler: ByteToMessageHandler(WantsOneThenTwoBytesDecoder()))
var buffer = channel.allocator.buffer(capacity: 16)
buffer.clear()
buffer.writeStaticString("1")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("23")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("4567890qwer")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertEqual(1, try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual(2, try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual(3, try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual(4, try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertNoThrow(try channel.close().wait())
XCTAssertFalse(channel.isActive)
XCTAssertNoThrow(XCTAssertEqual(-4, try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
}
func testReentrantChannelReadWhileWholeBufferIsBeingProcessed() {
struct ProcessAndReentrantylyProcessExponentiallyLessStuffDecoder: ByteToMessageDecoder {
typealias InboundOut = String
var state = 16
mutating func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTAssertGreaterThan(self.state, 0)
if let slice = buffer.readSlice(length: self.state) {
self.state >>= 1
for i in 0..<self.state {
XCTAssertNoThrow(try (context.channel as! EmbeddedChannel).writeInbound(slice.getSlice(at: i, length: 1)))
}
context.fireChannelRead(self.wrapInboundOut(String(decoding: slice.readableBytesView, as: Unicode.UTF8.self)))
return .continue
} else {
return .needMoreData
}
}
mutating func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
return try self.decode(context: context, buffer: &buffer)
}
}
let channel = EmbeddedChannel(handler: ByteToMessageHandler(ProcessAndReentrantylyProcessExponentiallyLessStuffDecoder()))
var buffer = channel.allocator.buffer(capacity: 16)
buffer.writeStaticString("0123456789abcdef")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertEqual("0123456789abcdef", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("01234567", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("0123", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("01", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("0", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
}
func testReentrantChannelCloseInChannelRead() {
struct Take16BytesThenCloseAndPassOnDecoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
mutating func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
if let slice = buffer.readSlice(length: 16) {
context.fireChannelRead(self.wrapInboundOut(slice))
context.channel.close().whenFailure { error in
XCTFail("unexpected error: \(error)")
}
return .continue
} else {
return .needMoreData
}
}
mutating func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
context.fireChannelRead(self.wrapInboundOut(buffer))
return .needMoreData
}
}
let channel = EmbeddedChannel(handler: ByteToMessageHandler(Take16BytesThenCloseAndPassOnDecoder()))
var buffer = channel.allocator.buffer(capacity: 16)
buffer.writeStaticString("0123456789abcdefQWER")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertEqual("0123456789abcdef", try channel.readInbound(as: ByteBuffer.self).map { String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)}))
XCTAssertNoThrow(XCTAssertEqual("QWER", try channel.readInbound(as: ByteBuffer.self).map { String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)}))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
}
func testHandlerRemoveInChannelRead() {
struct Take16BytesThenCloseAndPassOnDecoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
mutating func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
if let slice = buffer.readSlice(length: 16) {
context.fireChannelRead(self.wrapInboundOut(slice))
context.pipeline.removeHandler(context: context).whenFailure { error in
XCTFail("unexpected error: \(error)")
}
return .continue
} else {
return .needMoreData
}
}
mutating func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertFalse(seenEOF)
context.fireChannelRead(self.wrapInboundOut(buffer))
return .needMoreData
}
}
let channel = EmbeddedChannel(handler: ByteToMessageHandler(Take16BytesThenCloseAndPassOnDecoder()))
var buffer = channel.allocator.buffer(capacity: 16)
buffer.writeStaticString("0123456789abcdefQWER")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertEqual("0123456789abcdef", (try channel.readInbound() as ByteBuffer?).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
})
channel.embeddedEventLoop.run()
XCTAssertEqual("QWER", (try channel.readInbound() as ByteBuffer?).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
})
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
}
func testChannelCloseInChannelRead() {
struct Take16BytesThenCloseAndPassOnDecoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
mutating func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
if let slice = buffer.readSlice(length: 16) {
context.fireChannelRead(self.wrapInboundOut(slice))
context.close().whenFailure { error in
XCTFail("unexpected error: \(error)")
}
return .continue
} else {
return .needMoreData
}
}
mutating func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
return .needMoreData
}
}
class DoNotForwardChannelInactiveHandler: ChannelInboundHandler {
typealias InboundIn = Never
func channelInactive(context: ChannelHandlerContext) {
// just eat this event
}
}
let channel = EmbeddedChannel(handler: ByteToMessageHandler(Take16BytesThenCloseAndPassOnDecoder()))
XCTAssertNoThrow(try channel.pipeline.addHandler(DoNotForwardChannelInactiveHandler(), position: .first).wait())
var buffer = channel.allocator.buffer(capacity: 16)
buffer.writeStaticString("0123456789abcdefQWER")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertEqual("0123456789abcdef", (try channel.readInbound() as ByteBuffer?).map {
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
}))
channel.embeddedEventLoop.run()
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound())) // no leftovers are forwarded
}
func testDecodeLoopGetsInterruptedWhenRemovalIsTriggered() {
struct Decoder: ByteToMessageDecoder {
typealias InboundOut = String
var callsToDecode = 0
var callsToDecodeLast = 0
mutating func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTAssertEqual(9, buffer.readableBytes)
self.callsToDecode += 1
XCTAssertEqual(1, self.callsToDecode)
context.fireChannelRead(self.wrapInboundOut(String(decoding: buffer.readBytes(length: 1)!,
as: Unicode.UTF8.self)))
context.pipeline.removeHandler(context: context).whenFailure { error in
XCTFail("unexpected error: \(error)")
}
return .continue
}
mutating func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertFalse(seenEOF)
self.callsToDecodeLast += 1
XCTAssertLessThanOrEqual(self.callsToDecodeLast, 2)
context.fireChannelRead(self.wrapInboundOut(String(decoding: buffer.readBytes(length: 4) ??
[ /* "no bytes" */
0x6e, 0x6f, 0x20,
0x62, 0x79, 0x74, 0x65, 0x73],
as: Unicode.UTF8.self) + "#\(self.callsToDecodeLast)"))
return .continue
}
}
let handler = ByteToMessageHandler(Decoder())
let channel = EmbeddedChannel(handler: handler)
defer {
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
var buffer = channel.allocator.buffer(capacity: 9)
buffer.writeStaticString("012345678")
XCTAssertNoThrow(try channel.writeInbound(buffer))
channel.embeddedEventLoop.run()
XCTAssertEqual(1, handler.decoder?.callsToDecode)
XCTAssertEqual(2, handler.decoder?.callsToDecodeLast)
["0", "1234#1", "5678#2"].forEach { expected in
func workaroundSR9815() {
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readInbound()))
}
workaroundSR9815()
}
}
func testDecodeLastIsInvokedOnceEvenIfNothingEverArrivedOnChannelClosed() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = ()
var decodeLastCalls = 0
public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTFail("did not expect to see decode called")
return .needMoreData
}
public func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
self.decodeLastCalls += 1
XCTAssertEqual(1, self.decodeLastCalls)
XCTAssertEqual(0, buffer.readableBytes)
context.fireChannelRead(self.wrapInboundOut(()))
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertNoThrow(try channel.close().wait())
XCTAssertNoThrow(XCTAssertNotNil(try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertEqual(1, decoder.decodeLastCalls)
}
func testDecodeLastIsInvokedOnceEvenIfNothingEverArrivedOnChannelHalfClosure() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = ()
var decodeLastCalls = 0
public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTFail("did not expect to see decode called")
return .needMoreData
}
public func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertTrue(seenEOF)
self.decodeLastCalls += 1
XCTAssertEqual(1, self.decodeLastCalls)
XCTAssertEqual(0, buffer.readableBytes)
context.fireChannelRead(self.wrapInboundOut(()))
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
XCTAssertNoThrow(XCTAssertNotNil(try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertEqual(1, decoder.decodeLastCalls)
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertEqual(1, decoder.decodeLastCalls)
}
func testDecodeLastHasSeenEOFFalseOnHandlerRemoved() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = ()
var decodeLastCalls = 0
public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTAssertEqual(1, buffer.readableBytes)
return .needMoreData
}
public func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
XCTAssertEqual(1, buffer.readableBytes)
XCTAssertEqual(1, self.decodeLastCalls)
XCTAssertFalse(seenEOF)
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
var buffer = channel.allocator.buffer(capacity: 1)
buffer.writeString("x")
XCTAssertNoThrow(try channel.writeInbound(buffer))
let removalFuture = channel.pipeline.context(handlerType: ByteToMessageHandler<Decoder>.self).flatMap {
channel.pipeline.removeHandler(context: $0)
}
channel.embeddedEventLoop.run()
XCTAssertNoThrow(try removalFuture.wait())
XCTAssertEqual(1, decoder.decodeLastCalls)
}
func testDecodeLastHasSeenEOFFalseOnHandlerRemovedEvenIfNoData() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = ()
var decodeLastCalls = 0
public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTFail("shouldn't have been called")
return .needMoreData
}
public func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
XCTAssertEqual(0, buffer.readableBytes)
XCTAssertEqual(1, self.decodeLastCalls)
XCTAssertFalse(seenEOF)
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
let removalFuture = channel.pipeline.context(handlerType: ByteToMessageHandler<Decoder>.self).flatMap {
channel.pipeline.removeHandler(context: $0)
}
channel.embeddedEventLoop.run()
XCTAssertNoThrow(try removalFuture.wait())
XCTAssertEqual(1, decoder.decodeLastCalls)
}
func testDecodeLastHasSeenEOFTrueOnChannelInactive() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = ()
var decodeLastCalls = 0
public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTAssertEqual(1, buffer.readableBytes)
return .needMoreData
}
public func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
XCTAssertEqual(1, buffer.readableBytes)
XCTAssertEqual(1, self.decodeLastCalls)
XCTAssertTrue(seenEOF)
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
var buffer = channel.allocator.buffer(capacity: 1)
buffer.writeString("x")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
XCTAssertEqual(1, decoder.decodeLastCalls)
}
func testWriteObservingByteToMessageDecoderBasic() {
class Decoder: WriteObservingByteToMessageDecoder {
typealias OutboundIn = Int
typealias InboundOut = String
var allObservedWrites: [Int] = []
func write(data: Int) {
self.allObservedWrites.append(data)
}
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
if let string = buffer.readString(length: 1) {
context.fireChannelRead(self.wrapInboundOut(string))
return .continue
} else {
return .needMoreData
}
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
while case .continue = try self.decode(context: context, buffer: &buffer) {}
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
var buffer = channel.allocator.buffer(capacity: 3)
buffer.writeStaticString("abc")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(try channel.writeOutbound(1))
XCTAssertNoThrow(try channel.writeOutbound(2))
XCTAssertNoThrow(try channel.writeOutbound(3))
XCTAssertEqual([1, 2, 3], decoder.allObservedWrites)
XCTAssertNoThrow(XCTAssertEqual("a", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("b", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("c", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual(1, try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertEqual(2, try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertEqual(3, try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
func testWriteObservingByteToMessageDecoderWhereWriteIsReentrantlyCalled() {
class Decoder: WriteObservingByteToMessageDecoder {
typealias OutboundIn = String
typealias InboundOut = String
var allObservedWrites: [String] = []
var decodeRun = 0
func write(data: String) {
self.allObservedWrites.append(data)
}
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.decodeRun += 1
if let string = buffer.readString(length: 1) {
context.fireChannelRead(self.wrapInboundOut("I: \(self.decodeRun): \(string)"))
XCTAssertNoThrow(try (context.channel as! EmbeddedChannel).writeOutbound("O: \(self.decodeRun): \(string)"))
if self.decodeRun == 1 {
var buffer = context.channel.allocator.buffer(capacity: 1)
buffer.writeStaticString("X")
XCTAssertNoThrow(try (context.channel as! EmbeddedChannel).writeInbound(buffer))
}
return .continue
} else {
return .needMoreData
}
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
while case .continue = try self.decode(context: context, buffer: &buffer) {}
return .needMoreData
}
}
class CheckStateOfDecoderHandler: ChannelOutboundHandler {
typealias OutboundIn = String
typealias OutboundOut = String
private let decoder: Decoder
init(decoder: Decoder) {
self.decoder = decoder
}
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
let string = self.unwrapOutboundIn(data)
context.write(self.wrapOutboundOut("\(string) @ \(decoder.decodeRun)"), promise: promise)
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.pipeline.addHandler(CheckStateOfDecoderHandler(decoder: decoder), position: .first).wait())
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
var buffer = channel.allocator.buffer(capacity: 3)
XCTAssertNoThrow(try channel.writeOutbound("before"))
buffer.writeStaticString("ab")
XCTAssertNoThrow(try channel.writeInbound(buffer))
buffer.clear()
buffer.writeStaticString("xyz")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(try channel.writeOutbound("after"))
XCTAssertEqual(["before", "O: 1: a", "O: 2: b", "O: 3: X", "O: 4: x", "O: 5: y", "O: 6: z", "after"],
decoder.allObservedWrites)
XCTAssertNoThrow(XCTAssertEqual("I: 1: a", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("I: 2: b", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("I: 3: X", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("I: 4: x", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("I: 5: y", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("I: 6: z", try channel.readInbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertNoThrow(XCTAssertEqual("before @ 0", try channel.readOutbound()))
// in the next line, it's important that it ends in '@ 1' because that means the outbound write was forwarded
// when the Decoder was after decode run 1, ie. before it ever saw the 'b'. It's important we forward writes
// as soon as possible for correctness but also to keep as few queued writes as possible.
XCTAssertNoThrow(XCTAssertEqual("O: 1: a @ 1", try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertEqual("O: 2: b @ 2", try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertEqual("O: 3: X @ 3", try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertEqual("O: 4: x @ 4", try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertEqual("O: 5: y @ 5", try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertEqual("O: 6: z @ 6", try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertEqual("after @ 6", try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertNil(try channel.readOutbound()))
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
func testDecodeMethodsNoLongerCalledIfErrorInDecode() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
struct DecodeError: Error {}
private var errorThrownAlready = false
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTAssertFalse(self.errorThrownAlready)
self.errorThrownAlready = true
throw DecodeError()
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTFail("decodeLast should never be called")
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
var buffer = channel.allocator.buffer(capacity: 1)
buffer.writeString("x")
XCTAssertThrowsError(try channel.writeInbound(buffer)) { error in
XCTAssert(error is Decoder.DecodeError)
}
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertThrowsError(try channel.writeInbound(buffer)) { error in
if case .some(ByteToMessageDecoderError.dataReceivedInErrorState(let error, let receivedBuffer)) =
error as? ByteToMessageDecoderError {
XCTAssert(error is Decoder.DecodeError)
XCTAssertEqual(buffer, receivedBuffer)
} else {
XCTFail("wrong error: \(error)")
}
}
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
func testDecodeMethodsNoLongerCalledIfErrorInDecodeLast() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
struct DecodeError: Error {}
private var errorThrownAlready = false
private var decodeCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.decodeCalls += 1
XCTAssertEqual(1, self.decodeCalls)
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertFalse(self.errorThrownAlready)
self.errorThrownAlready = true
throw DecodeError()
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
var buffer = channel.allocator.buffer(capacity: 1)
buffer.writeString("x")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertThrowsError(try channel.finish()) { error in
XCTAssert(error is Decoder.DecodeError)
}
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
XCTAssertNoThrow(try channel.writeInbound(buffer)) // this will go through because the decoder is already 'done'
}
func testBasicLifecycle() {
class Decoder: ByteToMessageDecoder {
enum State {
case constructed
case added
case decode
case decodeLast
case removed
}
var state = State.constructed
typealias InboundOut = ()
func decoderAdded(context: ChannelHandlerContext) {
XCTAssertEqual(.constructed, self.state)
self.state = .added
}
func decoderRemoved(context: ChannelHandlerContext) {
XCTAssertEqual(.decodeLast, self.state)
self.state = .removed
}
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTAssertEqual(.added, self.state)
XCTAssertEqual(1, buffer.readableBytes)
self.state = .decode
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertEqual(.decode, self.state)
XCTAssertEqual(1, buffer.readableBytes)
self.state = .decodeLast
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
var buffer = channel.allocator.buffer(capacity: 1)
buffer.writeString("x")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(try channel.finish())
XCTAssertEqual(.removed, decoder.state)
}
func testDecodeLoopStopsOnChannelInactive() {
class CloseAfterThreeMessagesDecoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
var decodeCalls = 0
var decodeLastCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.decodeCalls += 1
XCTAssert(buffer.readableBytes > 0)
context.fireChannelRead(self.wrapInboundOut(buffer.readSlice(length: 1)!))
if self.decodeCalls == 3 {
context.close(promise: nil)
}
return .continue
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
if buffer.readableBytes > 0 {
context.fireErrorCaught(ByteToMessageDecoderError.leftoverDataWhenDone(buffer))
}
return .needMoreData
}
}
let decoder = CloseAfterThreeMessagesDecoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
defer {
XCTAssertFalse(channel.isActive)
}
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
var buffer = channel.allocator.buffer(capacity: 16)
buffer.writeStaticString("0123456")
XCTAssertThrowsError(try channel.writeInbound(buffer)) { error in
if case .some(.leftoverDataWhenDone(let buffer)) = error as? ByteToMessageDecoderError {
XCTAssertEqual("3456", buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes))
} else {
XCTFail("unexpected error: \(error)")
}
}
for i in 0..<3 {
buffer.clear()
buffer.writeString("\(i)")
XCTAssertNoThrow(XCTAssertEqual(buffer, try channel.readInbound(as: ByteBuffer.self)))
}
XCTAssertEqual(3, decoder.decodeCalls)
XCTAssertEqual(1, decoder.decodeLastCalls)
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
}
func testDecodeLoopStopsOnInboundHalfClosure() {
class CloseAfterThreeMessagesDecoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
var decodeCalls = 0
var decodeLastCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.decodeCalls += 1
XCTAssert(buffer.readableBytes > 0)
context.fireChannelRead(self.wrapInboundOut(buffer.readSlice(length: 1)!))
if self.decodeCalls == 3 {
context.channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
}
return .continue
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
if buffer.readableBytes > 0 {
context.fireErrorCaught(ByteToMessageDecoderError.leftoverDataWhenDone(buffer))
}
return .needMoreData
}
}
let decoder = CloseAfterThreeMessagesDecoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
defer {
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
}
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
var buffer = channel.allocator.buffer(capacity: 16)
buffer.writeStaticString("0123456")
XCTAssertThrowsError(try channel.writeInbound(buffer)) { error in
if case .some(.leftoverDataWhenDone(let buffer)) = error as? ByteToMessageDecoderError {
XCTAssertEqual("3456", buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes))
} else {
XCTFail("unexpected error: \(error)")
}
}
for i in 0..<3 {
buffer.clear()
buffer.writeString("\(i)")
XCTAssertNoThrow(XCTAssertEqual(buffer, try channel.readInbound(as: ByteBuffer.self)))
}
XCTAssertEqual(3, decoder.decodeCalls)
XCTAssertEqual(1, decoder.decodeLastCalls)
XCTAssertNoThrow(XCTAssertNil(try channel.readInbound()))
}
func testWeForwardReadEOFAndChannelInactive() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
var decodeLastCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTFail("should not have been called")
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
XCTAssertEqual(self.decodeLastCalls, 1)
XCTAssertTrue(seenEOF)
XCTAssertEqual(0, buffer.readableBytes)
return .needMoreData
}
}
class CheckThingsAreOkayHandler: ChannelInboundHandler {
typealias InboundIn = Never
var readEOFEvents = 0
var channelInactiveEvents = 0
func channelInactive(context: ChannelHandlerContext) {
self.channelInactiveEvents += 1
XCTAssertEqual(1, self.channelInactiveEvents)
}
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
if let event = event as? ChannelEvent, event == .inputClosed {
self.readEOFEvents += 1
XCTAssertEqual(1, self.readEOFEvents)
}
}
}
let decoder = Decoder()
let checker = CheckThingsAreOkayHandler()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
XCTAssertNoThrow(try channel.pipeline.addHandler(checker).wait())
XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "1.2.3.4", port: 5678)).wait())
channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
XCTAssertEqual(1, decoder.decodeLastCalls)
XCTAssertEqual(0, checker.channelInactiveEvents)
XCTAssertEqual(1, checker.readEOFEvents)
XCTAssertNoThrow(try channel.pipeline.close().wait())
XCTAssertEqual(1, decoder.decodeLastCalls)
XCTAssertEqual(1, checker.channelInactiveEvents)
XCTAssertEqual(1, checker.readEOFEvents)
}
func testErrorInDecodeLastWhenCloseIsReceivedReentrantlyInDecode() {
struct DummyError: Error {}
struct Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
// simulate a re-entrant trigger of reading EOF
context.channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertEqual("X", buffer.readString(length: buffer.readableBytes))
throw DummyError()
}
}
let channel = EmbeddedChannel(handler: ByteToMessageHandler(Decoder()))
var buffer = channel.allocator.buffer(capacity: 1)
buffer.writeString("X")
XCTAssertThrowsError(try channel.writeInbound(buffer)) { error in
XCTAssertTrue(error is DummyError)
}
}
func testWeAreOkayWithReceivingDataAfterHalfClosureEOF() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
var decodeCalls = 0
var decodeLastCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.decodeCalls += 1
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
var buffer = channel.allocator.buffer(capacity: 16)
buffer.writeStaticString("abc")
XCTAssertEqual(0, decoder.decodeCalls)
XCTAssertEqual(0, decoder.decodeLastCalls)
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertEqual(1, decoder.decodeCalls)
XCTAssertEqual(0, decoder.decodeLastCalls)
channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
XCTAssertEqual(1, decoder.decodeCalls)
XCTAssertEqual(1, decoder.decodeLastCalls)
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertEqual(1, decoder.decodeCalls)
XCTAssertEqual(1, decoder.decodeLastCalls)
XCTAssertTrue(try channel.finish().isClean)
}
func testWeAreOkayWithReceivingDataAfterFullClose() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
var decodeCalls = 0
var decodeLastCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.decodeCalls += 1
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
return .needMoreData
}
}
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder))
var buffer = channel.allocator.buffer(capacity: 16)
buffer.writeStaticString("abc")
XCTAssertEqual(0, decoder.decodeCalls)
XCTAssertEqual(0, decoder.decodeLastCalls)
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertEqual(1, decoder.decodeCalls)
XCTAssertEqual(0, decoder.decodeLastCalls)
XCTAssertTrue(try channel.finish().isClean)
XCTAssertEqual(1, decoder.decodeCalls)
XCTAssertEqual(1, decoder.decodeLastCalls)
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertEqual(1, decoder.decodeCalls)
XCTAssertEqual(1, decoder.decodeLastCalls)
}
func testPayloadTooLarge() {
struct Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
return .needMoreData
}
}
let max = 100
let channel = EmbeddedChannel(handler: ByteToMessageHandler(Decoder(), maximumBufferSize: max))
var buffer = channel.allocator.buffer(capacity: max + 1)
buffer.writeString(String(repeating: "*", count: max + 1))
XCTAssertThrowsError(try channel.writeInbound(buffer)) { error in
XCTAssertTrue(error is ByteToMessageDecoderError.PayloadTooLargeError)
}
}
func testPayloadTooLargeButHandlerOk() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
var decodeCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
self.decodeCalls += 1
buffer.moveReaderIndex(to: buffer.readableBytes)
return .continue
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeCalls += 1
buffer.moveReaderIndex(to: buffer.readableBytes)
return .continue
}
}
let max = 100
let decoder = Decoder()
let channel = EmbeddedChannel(handler: ByteToMessageHandler(decoder, maximumBufferSize: max))
var buffer = channel.allocator.buffer(capacity: max + 1)
buffer.writeString(String(repeating: "*", count: max + 1))
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
XCTAssertGreaterThan(decoder.decodeCalls, 0)
}
func testRemoveHandlerBecauseOfChannelTearDownWhilstUserTriggeredRemovalIsInProgress() {
class Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
var removedCalls = 0
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTFail("\(#function) should never have been called")
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTAssertEqual(0, buffer.readableBytes)
XCTAssertTrue(seenEOF)
return .needMoreData
}
func decoderRemoved(context: ChannelHandlerContext) {
self.removedCalls += 1
XCTAssertEqual(1, self.removedCalls)
}
}
let decoder = Decoder()
let decoderHandler = ByteToMessageHandler(decoder)
let channel = EmbeddedChannel(handler: decoderHandler)
XCTAssertNoThrow(try channel.connect(to: .init(ipAddress: "1.2.3.4", port: 5)).wait())
// We are now trying to get the channel into the following states (ordered by time):
// 1. user-triggered removal is in progress (started but not completed)
// 2. `removeHandlers()` as part of the Channel teardown is called
// 3. user-triggered removal completes
//
// The way we can get into this situation might be slightly counter-intuitive but currently, the easiest way
// to trigger this is:
// 1. `channel.close()` (because `removeHandlers()` is called inside an `eventLoop.execute` so is delayed
// 2. user-triggered removal start (`channel.pipeline.removeHandler`) which will also use an
// `eventLoop.execute` to ask for the handler to actually be removed.
// 3. run the event loop (this will now first call `removeHandlers()` which completes the channel tear down
// and a little later will complete the user-triggered removal.
let closeFuture = channel.close() // close the channel, `removeHandlers` will be called in next EL tick.
// user-trigger the handler removal (the actual removal will be done on the next EL tick too)
let removalFuture = channel.pipeline.removeHandler(decoderHandler)
// run the event loop, this will make `removeHandlers` run first because it was enqueued before the
// user-triggered handler removal
channel.embeddedEventLoop.run()
// just to make sure everything has completed.
XCTAssertNoThrow(try closeFuture.wait())
XCTAssertNoThrow(try removalFuture.wait())
XCTAssertThrowsError(try channel.finish()) { error in
XCTAssertEqual(ChannelError.alreadyClosed, error as? ChannelError)
}
}
}
public final class MessageToByteEncoderTest: XCTestCase {
private struct Int32ToByteEncoder: MessageToByteEncoder {
typealias OutboundIn = Int32
public func encode(data value: Int32, out: inout ByteBuffer) throws {
out.writeInteger(value)
}
}
private final class Int32ToByteEncoderWithDefaultImpl: MessageToByteEncoder {
typealias OutboundIn = Int32
public func encode(data value: Int32, out: inout ByteBuffer) throws {
XCTAssertEqual(MemoryLayout<Int32>.size, 256)
out.writeInteger(value)
}
}
func testEncoderOverrideAllocateOutBuffer() throws {
try testEncoder(MessageToByteHandler(Int32ToByteEncoder()))
}
func testEncoder() throws {
try testEncoder(MessageToByteHandler(Int32ToByteEncoderWithDefaultImpl()))
}
private func testEncoder(_ handler: ChannelHandler, file: StaticString = #file, line: UInt = #line) throws {
let channel = EmbeddedChannel()
XCTAssertNoThrow(try channel.pipeline.addHandler(MessageToByteHandler(Int32ToByteEncoder())).wait(),
file: (file), line: line)
XCTAssertNoThrow(try channel.writeAndFlush(NIOAny(Int32(5))).wait(), file: (file), line: line)
if var buffer = try channel.readOutbound(as: ByteBuffer.self) {
XCTAssertEqual(Int32(5), buffer.readInteger())
XCTAssertEqual(0, buffer.readableBytes)
} else {
XCTFail("couldn't read ByteBuffer from channel")
}
XCTAssertTrue(try channel.finish().isClean)
}
func testB2MHIsHappyNeverBeingAddedToAPipeline() {
@inline(never)
func createAndReleaseIt() {
struct Decoder: ByteToMessageDecoder {
typealias InboundOut = Never
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
XCTFail()
return .needMoreData
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
XCTFail()
return .needMoreData
}
}
_ = ByteToMessageHandler(Decoder())
}
createAndReleaseIt()
}
func testM2BHIsHappyNeverBeingAddedToAPipeline() {
@inline(never)
func createAndReleaseIt() {
struct Encoder: MessageToByteEncoder {
typealias OutboundIn = Void
func encode(data: Void, out: inout ByteBuffer) throws {
XCTFail()
}
}
_ = MessageToByteHandler(Encoder())
}
createAndReleaseIt()
}
}
private class PairOfBytesDecoder: ByteToMessageDecoder {
typealias InboundOut = ByteBuffer
private let lastPromise: EventLoopPromise<ByteBuffer>
var decodeLastCalls = 0
init(lastPromise: EventLoopPromise<ByteBuffer>) {
self.lastPromise = lastPromise
}
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
if let slice = buffer.readSlice(length: 2) {
context.fireChannelRead(self.wrapInboundOut(slice))
return .continue
} else {
return .needMoreData
}
}
func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState {
self.decodeLastCalls += 1
XCTAssertEqual(1, self.decodeLastCalls)
self.lastPromise.succeed(buffer)
return .needMoreData
}
}
public final class MessageToByteHandlerTest: XCTestCase {
private struct ThrowingMessageToByteEncoder: MessageToByteEncoder {
private struct HandlerError: Error { }
typealias OutboundIn = Int
public func encode(data value: Int, out: inout ByteBuffer) throws {
if value == 0 {
out.writeInteger(value)
} else {
throw HandlerError()
}
}
}
func testThrowingEncoderFailsPromises() {
let channel = EmbeddedChannel()
XCTAssertNoThrow(try channel.pipeline.addHandler(MessageToByteHandler(ThrowingMessageToByteEncoder())).wait())
XCTAssertNoThrow(try channel.writeAndFlush(0).wait())
XCTAssertThrowsError(try channel.writeAndFlush(1).wait())
XCTAssertThrowsError(try channel.writeAndFlush(0).wait())
}
}