Repository: apple/swift-nio-extras
Pull request #20 (2018-12-07 01:35:09)

LengthFieldBasedFrameDecoder: work around brittle B2MD
Motivation:

ByteToMessageDecoder is extremely brittle, for example a reentrant call
into decodeLast will present the user with bytes that were previously
seen...

Modification:

Discard bytes in decodeLast

Result:

LengthFieldBasedFrameDecoder will work if close called from channelRead.


weissi: thanks done @Lukasa 
weissi: ping @Lukasa 

Patch:
diff --git a/Sources/NIOExtras/LengthFieldBasedFrameDecoder.swift b/Sources/NIOExtras/LengthFieldBasedFrameDecoder.swift
index 40949e8..05ef52a 100644
--- a/Sources/NIOExtras/LengthFieldBasedFrameDecoder.swift
+++ b/Sources/NIOExtras/LengthFieldBasedFrameDecoder.swift
@@ -114,6 +114,11 @@ public final class LengthFieldBasedFrameDecoder: ByteToMessageDecoder {
         return .continue
     }
     
+    public func decodeLast(ctx: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
+        // work around the extreme brittleness of ByteToMessageDecoder
+        return .needMoreData
+    }
+
     ///
     /// Attempts to read the header data. Updates the status is successful.
     ///


Test patch:
diff --git a/Tests/NIOExtrasTests/FixedLengthFrameDecoderTest+XCTest.swift b/Tests/NIOExtrasTests/FixedLengthFrameDecoderTest+XCTest.swift
index 0017367..71bf53d 100644
--- a/Tests/NIOExtrasTests/FixedLengthFrameDecoderTest+XCTest.swift
+++ b/Tests/NIOExtrasTests/FixedLengthFrameDecoderTest+XCTest.swift
@@ -30,6 +30,7 @@ extension FixedLengthFrameDecoderTest {
                 ("testDecodeIfMoreBytesAreSent", testDecodeIfMoreBytesAreSent),
                 ("testRemoveHandlerWhenBufferIsNotEmpty", testRemoveHandlerWhenBufferIsNotEmpty),
                 ("testRemoveHandlerWhenBufferIsEmpty", testRemoveHandlerWhenBufferIsEmpty),
+                ("testCloseInChannelRead", testCloseInChannelRead),
            ]
    }
 }
diff --git a/Tests/NIOExtrasTests/FixedLengthFrameDecoderTest.swift b/Tests/NIOExtrasTests/FixedLengthFrameDecoderTest.swift
index 18ff154..e9aed1b 100644
--- a/Tests/NIOExtrasTests/FixedLengthFrameDecoderTest.swift
+++ b/Tests/NIOExtrasTests/FixedLengthFrameDecoderTest.swift
@@ -101,4 +101,31 @@ class FixedLengthFrameDecoderTest: XCTestCase {
         XCTAssertNoThrow(try channel.throwIfErrorCaught())
         XCTAssertFalse(try channel.finish())
     }
+
+    func testCloseInChannelRead() {
+        let channel = EmbeddedChannel(handler: LengthFieldBasedFrameDecoder(lengthFieldLength: .four))
+        class CloseInReadHandler: ChannelInboundHandler {
+            typealias InboundIn = ByteBuffer
+
+            private var numberOfReads = 0
+
+            func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
+                self.numberOfReads += 1
+                XCTAssertEqual(1, self.numberOfReads)
+                XCTAssertEqual([UInt8(100)], Array(self.unwrapInboundIn(data).readableBytesView))
+                ctx.close().whenFailure { error in
+                    XCTFail("unexpected error: \(error)")
+                }
+                ctx.fireChannelRead(data)
+            }
+        }
+        XCTAssertNoThrow(try channel.pipeline.add(handler: CloseInReadHandler()).wait())
+
+        var buf = channel.allocator.buffer(capacity: 1024)
+        buf.write(bytes: [UInt8(0), 0, 0, 1, 100])
+        XCTAssertNoThrow(try channel.writeInbound(buf))
+        XCTAssertEqual([100], Array((channel.readInbound() as ByteBuffer?)!.readableBytesView))
+        XCTAssertNil(channel.readInbound())
+
+    }
 }