FixedLengthFrameDecoder

Provide a decoder for frames with a fixed length.

Motivation:
This was motivated by the issue https://github.com/apple/swift-nio/issues/474 as a good first issue.

Modifications:
Implemented a FixedLengthFrameDecoder as well as tests.

Result:
The event loops are now accessible from the outside.
This commit is contained in:
Franz Busch 2018-07-17 16:36:13 +02:00
parent f5512693e3
commit 5efe6977e4
5 changed files with 227 additions and 0 deletions

View File

@ -0,0 +1,61 @@
//===----------------------------------------------------------------------===//
//
// 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
///
/// A decoder that splits the received `ByteBuffer` by a fixed number
/// of bytes. For example, if you received the following four fragmented packets:
///
/// +---+----+------+----+
/// | A | BC | DEFG | HI |
/// +---+----+------+----+
///
/// A `FixedLengthFrameDecoder` will decode them into the
/// following three packets with the fixed length:
///
/// +-----+-----+-----+
/// | ABC | DEF | GHI |
/// +-----+-----+-----+
///
public final class FixedLengthFrameDecoder: ByteToMessageDecoder {
public typealias InboundIn = ByteBuffer
public typealias InboundOut = ByteBuffer
public var cumulationBuffer: ByteBuffer?
private let frameLength: Int
/// Create `FixedLengthFrameDecoder` with a given frame length.
///
/// - parameters:
/// - frameLength: The length of a frame.
public init(frameLength: Int) {
self.frameLength = frameLength
}
public func decode(ctx: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
guard let slice = buffer.readSlice(length: frameLength) else {
return .needMoreData
}
ctx.fireChannelRead(self.wrapInboundOut(slice))
return .continue
}
public func handlerRemoved(ctx: ChannelHandlerContext) {
if let buffer = cumulationBuffer, buffer.readableBytes > 0 {
ctx.fireErrorCaught(NIOExtrasErrors.LeftOverBytesError(leftOverBytes: buffer))
}
}
}

View File

@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// 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
public protocol NIOExtrasError: Equatable, Error { }
/// Errors that are raised in NIOExtras.
public enum NIOExtrasErrors {
/// Error indicating that after an operation some unused bytes are left.
public struct LeftOverBytesError: NIOExtrasError {
public let leftOverBytes: ByteBuffer
}
}

View File

@ -26,6 +26,7 @@ import XCTest
@testable import NIOExtrasTests
XCTMain([
testCase(FixedLengthFrameDecoderTest.allTests),
testCase(QuiescingHelperTest.allTests),
])
#endif

View File

@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// FixedLengthDecoderTest+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 FixedLengthFrameDecoderTest {
static var allTests : [(String, (FixedLengthFrameDecoderTest) -> () throws -> Void)] {
return [
("testDecodeIfFewerBytesAreSent", testDecodeIfFewerBytesAreSent),
("testDecodeIfMoreBytesAreSent", testDecodeIfMoreBytesAreSent),
("testRemoveHandlerWhenBufferIsNotEmpty", testRemoveHandlerWhenBufferIsNotEmpty),
("testRemoveHandlerWhenBufferIsEmpty", testRemoveHandlerWhenBufferIsEmpty),
]
}
}

View File

@ -0,0 +1,104 @@
//===----------------------------------------------------------------------===//
//
// 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
class FixedLengthFrameDecoderTest: XCTestCase {
public func testDecodeIfFewerBytesAreSent() throws {
let channel = EmbeddedChannel()
let frameLength = 8
try channel.pipeline.add(handler: FixedLengthFrameDecoder(frameLength: frameLength)).wait()
var buffer = channel.allocator.buffer(capacity: frameLength)
buffer.write(string: "xxxx")
XCTAssertFalse(try channel.writeInbound(buffer))
XCTAssertTrue(try channel.writeInbound(buffer))
var outputBuffer: ByteBuffer? = channel.readInbound()
XCTAssertEqual("xxxxxxxx", outputBuffer?.readString(length: frameLength))
XCTAssertFalse(try channel.finish())
}
public func testDecodeIfMoreBytesAreSent() throws {
let channel = EmbeddedChannel()
let frameLength = 8
try channel.pipeline.add(handler: FixedLengthFrameDecoder(frameLength: frameLength)).wait()
var buffer = channel.allocator.buffer(capacity: 19)
buffer.write(string: "xxxxxxxxaaaaaaaabbb")
XCTAssertTrue(try channel.writeInbound(buffer))
var outputBuffer: ByteBuffer? = channel.readInbound()
XCTAssertEqual("xxxxxxxx", outputBuffer?.readString(length: frameLength))
outputBuffer = channel.readInbound()
XCTAssertEqual("aaaaaaaa", outputBuffer?.readString(length: frameLength))
outputBuffer = channel.readInbound()
XCTAssertNil(outputBuffer?.readString(length: frameLength))
XCTAssertFalse(try channel.finish())
}
public func testRemoveHandlerWhenBufferIsNotEmpty() throws {
let channel = EmbeddedChannel()
let frameLength = 8
let handler = FixedLengthFrameDecoder(frameLength: frameLength)
try channel.pipeline.add(handler: handler).wait()
var buffer = channel.allocator.buffer(capacity: 15)
buffer.write(string: "xxxxxxxxxxxxxxx")
XCTAssertTrue(try channel.writeInbound(buffer))
var outputBuffer: ByteBuffer? = channel.readInbound()
XCTAssertEqual("xxxxxxxx", outputBuffer?.readString(length: frameLength))
_ = try channel.pipeline.remove(handler: handler).wait()
XCTAssertThrowsError(try channel.throwIfErrorCaught()) { error in
guard let error = error as? NIOExtrasErrors.LeftOverBytesError else {
XCTFail()
return
}
var expectedBuffer = channel.allocator.buffer(capacity: 7)
expectedBuffer.write(string: "xxxxxxx")
XCTAssertEqual(error.leftOverBytes, expectedBuffer)
}
XCTAssertFalse(try channel.finish())
}
public func testRemoveHandlerWhenBufferIsEmpty() throws {
let channel = EmbeddedChannel()
let frameLength = 8
let handler = FixedLengthFrameDecoder(frameLength: frameLength)
try channel.pipeline.add(handler: handler).wait()
var buffer = channel.allocator.buffer(capacity: 6)
buffer.write(string: "xxxxxxxx")
XCTAssertTrue(try channel.writeInbound(buffer))
var outputBuffer: ByteBuffer? = channel.readInbound()
XCTAssertEqual("xxxxxxxx", outputBuffer?.readString(length: frameLength))
_ = try channel.pipeline.remove(handler: handler).wait()
XCTAssertNoThrow(try channel.throwIfErrorCaught())
XCTAssertFalse(try channel.finish())
}
}