void DisposeAssembler(StreamMessageAssembler assembler)
 {
     assembler.Dispose();
     _assemblers.Remove(assembler.MessageId);
 }
        public async Task <Message> ReceiveMessage(CancellationToken ct)
        {
            var buffer = ArrayPool <byte> .Shared.Rent(_receiveBufferSize);

            try
            {
                while (true)
                {
                    ct.ThrowIfCancellationRequested();

                    var read           = 0;
                    var partialMsgSize = -1;
                    var msgSize        = 0;
                    while (partialMsgSize == -1 || read < partialMsgSize)
                    {
                        var len     = partialMsgSize == -1 ? 8 : partialMsgSize - read;
                        var segment = new ArraySegment <byte>(buffer, read, len);

                        read += await _transportChannel.ReceiveAsync(segment, ct);

                        if (partialMsgSize == -1 && read >= 8)
                        {
                            partialMsgSize = BitConverter.ToInt32(buffer, _partialMsgSizeIndex);
                            msgSize        = BitConverter.ToInt32(buffer, _msgSizeIndex);
                            if (partialMsgSize < _minPartialMsgSize)
                            {
                                throw new Exception($"Received message size {partialMsgSize} while minimum is {_minPartialMsgSize}");
                            }
                            if (partialMsgSize > _receiveBufferSize)
                            {
                                var newBuffer = ArrayPool <byte> .Shared.Rent(partialMsgSize);

                                Buffer.BlockCopy(buffer, 0, newBuffer, 0, read);
                                ArrayPool <byte> .Shared.Return(buffer);

                                buffer = newBuffer;
                            }
                            if (msgSize > _maxMessageSize)
                            {
                                throw new Exception($"Received message size {partialMsgSize} while maximum is {_maxMessageSize}");
                            }
                        }

                        ct.ThrowIfCancellationRequested();
                    }

                    var msgId = new Guid(BufferHelper.GetSubArray(buffer, _idIndex, 16));
                    if (msgSize == partialMsgSize - _msgSizeIndex)
                    {
                        var    msgType       = (MessageType)buffer[_partialMsgTypeIndex];
                        byte[] payload       = null;
                        var    payloadLength = partialMsgSize - _partialPayloadIndex;
                        if (payloadLength > 0)
                        {
                            payload = BufferHelper.GetSubArray(buffer, _partialPayloadIndex, payloadLength);
                        }

                        return(new Message(msgType, msgId, payload));
                    }
                    else
                    {
                        var nextIndex = _msgSizeIndex;
                        if (!_assemblers.TryGetValue(msgId, out var assembler))
                        {
                            assembler = new StreamMessageAssembler(msgId, msgSize);
                            _assemblers.Add(msgId, assembler);
                        }
                        else
                        {
                            nextIndex = _partialPayloadIndex;
                        }
                        var nextSegment = new ArraySegment <byte>(buffer, nextIndex, partialMsgSize - nextIndex);
                        if (assembler.AddPartialMessage(nextSegment))
                        {
                            var    msgBuffer     = assembler.GetMessage();
                            var    msgType       = (MessageType)msgBuffer[_msgTypeIndex];
                            byte[] payload       = null;
                            var    payloadLength = msgSize - _payloadIndex;
                            if (payloadLength > 0)
                            {
                                payload = BufferHelper.GetSubArray(msgBuffer, _payloadIndex, payloadLength);
                            }

                            var msg = new Message(msgType, msgId, payload);
                            DisposeAssembler(assembler);
                            return(msg);
                        }
                    }
                }
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(buffer);
            }
        }