public override void ChannelActive(IChannelHandlerContext ctx) { CompositeBufferPartialWriteDoesNotCorruptDataInitServerConfig(ctx.Channel.Configuration, _soSndBuf); // First single write int offset = _soSndBuf - 100; ctx.WriteAsync(_expectedContent.RetainedSlice(_expectedContent.ReaderIndex, offset)); // Build and write CompositeByteBuf CompositeByteBuffer compositeByteBuf = ctx.Allocator.CompositeBuffer(); compositeByteBuf.AddComponent(true, _expectedContent.RetainedSlice(_expectedContent.ReaderIndex + offset, 50)); offset += 50; compositeByteBuf.AddComponent(true, _expectedContent.RetainedSlice(_expectedContent.ReaderIndex + offset, 200)); offset += 200; ctx.WriteAsync(compositeByteBuf); // Write a single buffer that is smaller than the second component of the CompositeByteBuf // above but small enough to fit in the remaining space allowed by the soSndBuf amount. ctx.WriteAsync(_expectedContent.RetainedSlice(_expectedContent.ReaderIndex + offset, 50)); offset += 50; // Write the remainder of the content ctx.WriteAndFlushAsync(_expectedContent.RetainedSlice(_expectedContent.ReaderIndex + offset, _expectedContent.ReadableBytes - _expectedContent.ReaderIndex - offset)) .CloseOnComplete(ctx.Channel); }
static IByteBuffer NewCompositeBuffer(IByteBufferAllocator alloc) { CompositeByteBuffer compositeByteBuf = alloc.CompositeBuffer(); compositeByteBuf.AddComponent(true, alloc.DirectBuffer(4).WriteInt(100)); compositeByteBuf.AddComponent(true, alloc.DirectBuffer(8).WriteLong(123)); compositeByteBuf.AddComponent(true, alloc.DirectBuffer(8).WriteLong(456)); Assert.Equal(ExpectedBytes, compositeByteBuf.ReadableBytes); return(compositeByteBuf); }
public void RemoveLastComponentWithOthersLeft() { CompositeByteBuffer buf = Unpooled.CompositeBuffer(); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 1, 2 })); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 1, 2 })); Assert.Equal(2, buf.NumComponents); buf.RemoveComponent(1); Assert.Equal(1, buf.NumComponents); buf.Release(); }
public void TestNioBuffersMaxCount() { TestChannel channel = new TestChannel(); ChannelOutboundBuffer buffer = new ChannelOutboundBuffer(channel); CompositeByteBuffer comp = Unpooled.CompositeBuffer(256); IByteBuffer buf = Unpooled.DirectBuffer().WriteBytes(Encoding.ASCII.GetBytes("buf1")); for (int i = 0; i < 65; i++) { comp.AddComponent(true, buf.Copy()); } Assert.Equal(65, comp.IoBufferCount); buffer.AddMessage(comp, comp.ReadableBytes, channel.VoidPromise()); Assert.Equal(0, buffer.NioBufferCount); // Should still be 0 as not flushed yet buffer.AddFlush(); int maxCount = 10; // less than comp.nioBufferCount() var buffers = buffer.GetSharedBufferList(maxCount, int.MaxValue); Assert.True(buffer.NioBufferCount <= maxCount); // Should not be greater than maxCount for (int i = 0; i < buffer.NioBufferCount; i++) { Assert.Equal(buffers[i], buf.GetIoBuffer(buf.ReaderIndex, buf.ReadableBytes)); } Release(buffer); buf.Release(); }
public void FullConsolidation() { CompositeByteBuffer buf = Unpooled.CompositeBuffer(int.MaxValue); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 1 })); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 2, 3 })); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 4, 5, 6 })); buf.Consolidate(); Assert.Equal(1, buf.NumComponents); Assert.True(buf.HasArray); Assert.NotNull(buf.Array); Assert.Equal(0, buf.ArrayOffset); buf.Release(); }
public void TestNioBuffersExpand2() { TestChannel channel = new TestChannel(); ChannelOutboundBuffer buffer = new ChannelOutboundBuffer(channel); CompositeByteBuffer comp = Unpooled.CompositeBuffer(256); IByteBuffer buf = Unpooled.DirectBuffer().WriteBytes(Encoding.ASCII.GetBytes("buf1")); for (int i = 0; i < 65; i++) { comp.AddComponent(true, buf.Copy()); } buffer.AddMessage(comp, comp.ReadableBytes, channel.VoidPromise()); Assert.Equal(0, buffer.NioBufferCount); // Should still be 0 as not flushed yet buffer.AddFlush(); var buffers = buffer.GetSharedBufferList(); Assert.Equal(65, buffer.NioBufferCount); for (int i = 0; i < buffer.NioBufferCount; i++) { if (i < 65) { Assert.Equal(buffers[i], buf.GetIoBuffer(buf.ReaderIndex, buf.ReadableBytes)); } else { Assert.Null(buffers[i].Array); } } Release(buffer); buf.Release(); }
public void RangedConsolidation() { CompositeByteBuffer buf = Unpooled.CompositeBuffer(int.MaxValue); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 1 })); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 2, 3 })); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 4, 5, 6 })); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 7, 8, 9, 10 })); buf.Consolidate(1, 2); Assert.Equal(3, buf.NumComponents); Assert.Equal(Unpooled.WrappedBuffer(new byte[] { 1 }), buf[0]); Assert.Equal(Unpooled.WrappedBuffer(new byte[] { 2, 3, 4, 5, 6 }), buf[1]); Assert.Equal(Unpooled.WrappedBuffer(new byte[] { 7, 8, 9, 10 }), buf[2]); buf.Release(); }
private IByteBuffer CompressContent(IChannelHandlerContext ctx, WebSocketFrame msg) { if (_encoder is null) { _encoder = new EmbeddedChannel( ZlibCodecFactory.NewZlibEncoder( ZlibWrapper.None, _compressionLevel, _windowSize, 8)); } _ = _encoder.WriteOutbound(msg.Content.Retain()); CompositeByteBuffer fullCompressedContent = ctx.Allocator.CompositeBuffer(); while (true) { var partCompressedContent = _encoder.ReadOutbound <IByteBuffer>(); if (partCompressedContent is null) { break; } if (!partCompressedContent.IsReadable()) { _ = partCompressedContent.Release(); continue; } _ = fullCompressedContent.AddComponent(true, partCompressedContent); } if (fullCompressedContent.NumComponents <= 0) { _ = fullCompressedContent.Release(); ThrowHelper.ThrowCodecException_CannotReadCompressedBuf(); } if (msg.IsFinalFragment && _noContext) { Cleanup(); } IByteBuffer compressedContent; if (RemoveFrameTail(msg)) { int realLength = fullCompressedContent.ReadableBytes - FrameTail.ReadableBytes; compressedContent = fullCompressedContent.Slice(0, realLength); } else { compressedContent = fullCompressedContent; } return(compressedContent); }
public void AddEmptyBufferInMiddle() { CompositeByteBuffer cbuf = Unpooled.CompositeBuffer(); IByteBuffer buf1 = Unpooled.Buffer().WriteByte(1); cbuf.AddComponent(true, buf1); cbuf.AddComponent(true, Unpooled.Empty); IByteBuffer buf3 = Unpooled.Buffer().WriteByte(2); cbuf.AddComponent(true, buf3); Assert.Equal(2, cbuf.ReadableBytes); Assert.Equal((byte)1, cbuf.ReadByte()); Assert.Equal((byte)2, cbuf.ReadByte()); Assert.Same(Unpooled.Empty, cbuf.InternalComponent(1)); Assert.NotSame(Unpooled.Empty, cbuf.InternalComponentAtOffset(1)); cbuf.Release(); }
public void ComponentMustBeDuplicate() { CompositeByteBuffer buf = Unpooled.CompositeBuffer(); buf.AddComponent(Unpooled.Buffer(4, 6).SetIndex(1, 3)); Assert.IsAssignableFrom <AbstractDerivedByteBuffer>(buf[0]); Assert.Equal(4, buf[0].Capacity); Assert.Equal(6, buf[0].MaxCapacity); Assert.Equal(2, buf[0].ReadableBytes); buf.Release(); }
public void AddEmptyBufferRelease() { CompositeByteBuffer cbuf = Unpooled.CompositeBuffer(); IByteBuffer buf = Unpooled.Buffer(); Assert.Equal(1, buf.ReferenceCount); cbuf.AddComponent(buf); Assert.Equal(1, buf.ReferenceCount); cbuf.Release(); Assert.Equal(0, buf.ReferenceCount); }
public void CompositeToSingleBuffer() { CompositeByteBuffer buf = Unpooled.CompositeBuffer(3); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 1, 2, 3 })); Assert.Equal(1, buf.NumComponents); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 4 })); Assert.Equal(2, buf.NumComponents); buf.AddComponent(Unpooled.WrappedBuffer(new byte[] { 5, 6 })); Assert.Equal(3, buf.NumComponents); // NOTE: hard-coding 6 here, since it seems like addComponent doesn't bump the writer index. // I'm unsure as to whether or not this is correct behavior ArraySegment <byte> nioBuffer = buf.GetIoBuffer(0, 6); Assert.Equal(6, nioBuffer.Count); Assert.True(nioBuffer.Array.SequenceEqual(new byte[] { 1, 2, 3, 4, 5, 6 })); buf.Release(); }
public void NestedLayout() { CompositeByteBuffer buf = Unpooled.CompositeBuffer(); buf.AddComponent( Unpooled.CompositeBuffer() .AddComponent(Unpooled.WrappedBuffer(new byte[] { 1, 2 })) .AddComponent(Unpooled.WrappedBuffer(new byte[] { 3, 4 })).Slice(1, 2)); ArraySegment <byte>[] nioBuffers = buf.GetIoBuffers(0, 2); Assert.Equal(2, nioBuffers.Length); Assert.Equal((byte)2, nioBuffers[0].Array[nioBuffers[0].Offset]); Assert.Equal((byte)3, nioBuffers[1].Array[nioBuffers[1].Offset]); buf.Release(); }
public static ReadableBuffer Composite(IEnumerable <ReadableBuffer> buffers) { Contract.Requires(buffers != null); CompositeByteBuffer composite = Unpooled.CompositeBuffer(); foreach (ReadableBuffer buf in buffers) { IByteBuffer byteBuffer = buf.buffer; if (byteBuffer.ReadableBytes > 0) { composite.AddComponent(byteBuffer); } } return(new ReadableBuffer(composite)); }
static void AppendPartialContent(CompositeByteBuffer content, IByteBuffer partialContent) { Contract.Requires(content != null); Contract.Requires(partialContent != null); if (!partialContent.IsReadable()) { return; } var buffer = (IByteBuffer)partialContent.Retain(); content.AddComponent(buffer); // Note that WriterIndex must be manually increased content.SetWriterIndex(content.WriterIndex + buffer.ReadableBytes); }
public void ForEachByteUnderLeakDetectionShouldNotThrowException() { CompositeByteBuffer buf = (CompositeByteBuffer)NewBuffer(8); Assert.True(buf is SimpleLeakAwareCompositeByteBuffer); CompositeByteBuffer comp = (CompositeByteBuffer)NewBuffer(8); Assert.True(comp is SimpleLeakAwareCompositeByteBuffer); IByteBuffer inner = comp.Allocator.DirectBuffer(1).WriteByte(0); comp.AddComponent(true, inner); buf.AddComponent(true, comp); Assert.Equal(-1, buf.ForEachByte(new AlwaysByteProcessor())); Assert.True(buf.Release()); }
public void DiscardSomeReadBytes() { CompositeByteBuffer cbuf = Unpooled.CompositeBuffer(); int len = 8 * 4; for (int i = 0; i < len; i += 4) { IByteBuffer buf = Unpooled.Buffer().WriteInt(i); cbuf.AdjustCapacity(cbuf.WriterIndex); cbuf.AddComponent(buf).SetWriterIndex(i + 4); } cbuf.WriteByte(1); var me = new byte[len]; cbuf.ReadBytes(me); cbuf.ReadByte(); cbuf.DiscardSomeReadBytes(); cbuf.Release(); }
void TestHttpResponseAndFrameInSameBuffer(bool codec) { string url = "ws://localhost:9999/ws"; WebSocketClientHandshaker shaker = this.NewHandshaker(new Uri(url)); var handshaker = new Handshaker(shaker); // use randomBytes helper from utils to check that it functions properly byte[] data = WebSocketUtil.RandomBytes(24); // Create a EmbeddedChannel which we will use to encode a BinaryWebsocketFrame to bytes and so use these // to test the actual handshaker. var factory = new WebSocketServerHandshakerFactory(url, null, false); IFullHttpRequest request = shaker.NewHandshakeRequest(); WebSocketServerHandshaker socketServerHandshaker = factory.NewHandshaker(request); request.Release(); var websocketChannel = new EmbeddedChannel(socketServerHandshaker.NewWebSocketEncoder(), socketServerHandshaker.NewWebsocketDecoder()); Assert.True(websocketChannel.WriteOutbound(new BinaryWebSocketFrame(Unpooled.WrappedBuffer(data)))); byte[] bytes = Encoding.ASCII.GetBytes("HTTP/1.1 101 Switching Protocols\r\nContent-Length: 0\r\n\r\n"); CompositeByteBuffer compositeByteBuf = Unpooled.CompositeBuffer(); compositeByteBuf.AddComponent(true, Unpooled.WrappedBuffer(bytes)); for (; ;) { var frameBytes = websocketChannel.ReadOutbound <IByteBuffer>(); if (frameBytes == null) { break; } compositeByteBuf.AddComponent(true, frameBytes); } var ch = new EmbeddedChannel(new HttpObjectAggregator(int.MaxValue), new Handler(handshaker)); if (codec) { ch.Pipeline.AddFirst(new HttpClientCodec()); } else { ch.Pipeline.AddFirst(new HttpRequestEncoder(), new HttpResponseDecoder()); } // We need to first write the request as HttpClientCodec will fail if we receive a response before a request // was written. shaker.HandshakeAsync(ch).Wait(); for (; ;) { // Just consume the bytes, we are not interested in these. var buf = ch.ReadOutbound <IByteBuffer>(); if (buf == null) { break; } buf.Release(); } Assert.True(ch.WriteInbound(compositeByteBuf)); Assert.True(ch.Finish()); var frame = ch.ReadInbound <BinaryWebSocketFrame>(); IByteBuffer expect = Unpooled.WrappedBuffer(data); try { Assert.Equal(expect, frame.Content); Assert.True(frame.IsFinalFragment); Assert.Equal(0, frame.Rsv); } finally { expect.Release(); frame.Release(); } }
protected internal override void Decode(IChannelHandlerContext ctx, WebSocketFrame msg, List <object> output) { if (this.decoder == null) { if (!(msg is TextWebSocketFrame) && !(msg is BinaryWebSocketFrame)) { throw new CodecException($"unexpected initial frame type: {msg.GetType().Name}"); } this.decoder = new EmbeddedChannel(ZlibCodecFactory.NewZlibDecoder(ZlibWrapper.None)); } bool readable = msg.Content.IsReadable(); this.decoder.WriteInbound(msg.Content.Retain()); if (this.AppendFrameTail(msg)) { this.decoder.WriteInbound(Unpooled.WrappedBuffer(FrameTail)); } CompositeByteBuffer compositeUncompressedContent = ctx.Allocator.CompositeDirectBuffer(); for (;;) { var partUncompressedContent = this.decoder.ReadInbound <IByteBuffer>(); if (partUncompressedContent == null) { break; } if (!partUncompressedContent.IsReadable()) { partUncompressedContent.Release(); continue; } compositeUncompressedContent.AddComponent(true, partUncompressedContent); } // Correctly handle empty frames // See https://github.com/netty/netty/issues/4348 if (readable && compositeUncompressedContent.NumComponents <= 0) { compositeUncompressedContent.Release(); throw new CodecException("cannot read uncompressed buffer"); } if (msg.IsFinalFragment && this.noContext) { this.Cleanup(); } WebSocketFrame outMsg; if (msg is TextWebSocketFrame) { outMsg = new TextWebSocketFrame(msg.IsFinalFragment, this.NewRsv(msg), compositeUncompressedContent); } else if (msg is BinaryWebSocketFrame) { outMsg = new BinaryWebSocketFrame(msg.IsFinalFragment, this.NewRsv(msg), compositeUncompressedContent); } else if (msg is ContinuationWebSocketFrame) { outMsg = new ContinuationWebSocketFrame(msg.IsFinalFragment, this.NewRsv(msg), compositeUncompressedContent); } else { throw new CodecException($"unexpected frame type: {msg.GetType().Name}"); } output.Add(outMsg); }
/// <summary> /// Flush the content of the given buffer to the remote peer. /// </summary> /// <param name="channelOutboundBuffer">The buffer to write to the remote peer.</param> protected override async void DoWrite(ChannelOutboundBuffer channelOutboundBuffer) { if (Logging.IsEnabled) { Logging.Enter(this, nameof(DoWrite)); } if (channelOutboundBuffer == null) { throw new ArgumentNullException(nameof(channelOutboundBuffer), "The channel outbound buffer cannot be null."); } try { // The ChannelOutboundBuffer might have more than one message per MQTT packet that needs to be written to the websocket as a single frame. // One example of this is a PUBLISH packet, which encodes the payload and topic information as separate messages. // In order to reduce the number of frames sent to the websocket, we will consolidate the individual messages per MQTT packet into a single byte buffer. IByteBufferAllocator allocator = Configuration.Allocator; // The parameter "direct" is used to indicate if operations carried out in the CompositeByteBuffer should be treated as "unsafe". var compositeByteBuffer = new CompositeByteBuffer(allocator, direct: false, maxNumComponents: int.MaxValue); var bytesToBeWritten = new ArraySegment <byte>(); _isWriteInProgress = true; while (true) { object currentMessage = channelOutboundBuffer.Current; // Once there are no more messages pending in ChannelOutboundBuffer, the "Current" property is returned as "null". // This indicates that all pending messages have been dequeued from the ChannelOutboundBuffer and are ready to be written to the websocket. if (currentMessage == null) { // This indicates that the ChannelOutboundBuffer had readable bytes and they have been added to the CompositeByteBuffer. if (compositeByteBuffer.NumComponents > 0) { // All messages have been added to the CompositeByteBuffer and are now ready to be written to the socket. bytesToBeWritten = compositeByteBuffer.GetIoBuffer(); } break; } var byteBuffer = currentMessage as IByteBuffer; Fx.AssertAndThrow(byteBuffer != null, "channelOutBoundBuffer contents must be of type IByteBuffer"); // If the byte buffer has readable bytes then add them to the CompositeByteBuffer. if (byteBuffer.ReadableBytes != 0) { // There are two operations carried out while adding a byte buffer component to a CompositeByteBuffer: // - Increase WriterIndex of the CompositeByteBuffer // - increases the count of readable bytes added to the CompositeByteBuffer. // - Call the method Retain() on the byte buffer being added // - The property ReferenceCount of a byte buffer implementation maintains a counter of the no of messages available for dequeuing. // A ReferenceCount of 0 indicates that all messages have been flushed and the buffer can be deallocated. // By calling the method Retain() on each byte buffer component added to the CompositeByteBuffer, // we increase the ReferenceCount by 1 and mark them as ready for dequeuing. compositeByteBuffer .AddComponent( increaseWriterIndex: true, buffer: (IByteBuffer)byteBuffer.Retain()); } // Once the readable bytes are added to the CompositeByteBuffer they can be removed from the ChannelOutboundBuffer // and the next message, if any, can be processed. channelOutboundBuffer.Remove(); } if (bytesToBeWritten.Count > 0) { if (Logging.IsEnabled) { Logging.Info(this, $"Writing bytes of size {bytesToBeWritten.Count} to the websocket", nameof(DoWrite)); } await _webSocket.SendAsync(bytesToBeWritten, WebSocketMessageType.Binary, true, _writeCancellationTokenSource.Token).ConfigureAwait(false); } _isWriteInProgress = false; } catch (Exception e) when(!e.IsFatal()) { // Since this method returns void, all exceptions must be handled here. _isWriteInProgress = false; Pipeline.FireExceptionCaught(e); await HandleCloseAsync().ConfigureAwait(false); } finally { if (Logging.IsEnabled) { Logging.Exit(this, nameof(DoWrite)); } } }
private async Task TestGatheringWrite0( ServerBootstrap sb, Bootstrap cb, byte[] data, bool composite, bool autoRead) { sb.ChildOption(ChannelOption.AutoRead, autoRead); cb.Option(ChannelOption.AutoRead, autoRead); IPromise serverDonePromise = new DefaultPromise(); TestServerHandler sh = new TestServerHandler(autoRead, serverDonePromise, data.Length); TestHandler ch = new TestHandler(autoRead); cb.Handler(ch); sb.ChildHandler(sh); IChannel sc = await sb.BindAsync(); IChannel cc = await cb.ConnectAsync(sc.LocalAddress); for (int i = 0; i < data.Length;) { int length = Math.Min(s_random.Next(1024 * 8), data.Length - i); if (composite && i % 2 == 0) { int firstBufLength = length / 2; CompositeByteBuffer comp = Unpooled.CompositeBuffer(); comp.AddComponent(true, Unpooled.WrappedBuffer(data, i, firstBufLength)) .AddComponent(true, Unpooled.WrappedBuffer(data, i + firstBufLength, length - firstBufLength)); cc.WriteAsync(comp).Ignore(); } else { cc.WriteAsync(Unpooled.WrappedBuffer(data, i, length)).Ignore(); } i += length; } var cf = cc.WriteAndFlushAsync(Unpooled.Empty); Assert.NotEqual(cc.VoidPromise().Task, cf); try { Assert.True(cf.Wait(60000)); await cf; } catch (Exception) { throw; } await serverDonePromise.Task; await sh._channel.CloseAsync(); await ch._channel.CloseAsync(); await sc.CloseAsync(); if (sh._exception.Value != null && !(sh._exception.Value is SocketException || (sh._exception.Value is ChannelException chexc && chexc.InnerException is OperationException) || sh._exception.Value is OperationException)) { throw sh._exception.Value; } if (sh._exception.Value != null) { throw sh._exception.Value; } if (ch._exception.Value != null && !(ch._exception.Value is SocketException || (sh._exception.Value is ChannelException chexc1 && chexc1.InnerException is OperationException) || sh._exception.Value is OperationException)) { throw ch._exception.Value; } if (ch._exception.Value != null) { throw ch._exception.Value; } IByteBuffer expected = Unpooled.WrappedBuffer(data); Assert.Equal(expected, sh._received); expected.Release(); sh._received.Release(); }
protected internal override void Encode(IChannelHandlerContext ctx, WebSocketFrame msg, List <object> output) { if (this.encoder == null) { this.encoder = new EmbeddedChannel( ZlibCodecFactory.NewZlibEncoder( ZlibWrapper.None, this.compressionLevel, this.windowSize, 8)); } this.encoder.WriteOutbound(msg.Content.Retain()); CompositeByteBuffer fullCompressedContent = ctx.Allocator.CompositeBuffer(); for (;;) { var partCompressedContent = this.encoder.ReadOutbound <IByteBuffer>(); if (partCompressedContent == null) { break; } if (!partCompressedContent.IsReadable()) { partCompressedContent.Release(); continue; } fullCompressedContent.AddComponent(true, partCompressedContent); } if (fullCompressedContent.NumComponents <= 0) { fullCompressedContent.Release(); throw new CodecException("cannot read compressed buffer"); } if (msg.IsFinalFragment && this.noContext) { this.Cleanup(); } IByteBuffer compressedContent; if (this.RemoveFrameTail(msg)) { int realLength = fullCompressedContent.ReadableBytes - FrameTail.Length; compressedContent = fullCompressedContent.Slice(0, realLength); } else { compressedContent = fullCompressedContent; } WebSocketFrame outMsg; if (msg is TextWebSocketFrame) { outMsg = new TextWebSocketFrame(msg.IsFinalFragment, this.Rsv(msg), compressedContent); } else if (msg is BinaryWebSocketFrame) { outMsg = new BinaryWebSocketFrame(msg.IsFinalFragment, this.Rsv(msg), compressedContent); } else if (msg is ContinuationWebSocketFrame) { outMsg = new ContinuationWebSocketFrame(msg.IsFinalFragment, this.Rsv(msg), compressedContent); } else { throw new CodecException($"unexpected frame type: {msg.GetType().Name}"); } output.Add(outMsg); }