List <Message> BufferMessages(ref byte[] buffer, ref int bufferOffset, ref int messageOffset, ref int remainingData, ref MessageHeader header, ref BufferValueReader reader, Func <MessageHeader, bool> messageIdCallback = null)
        {
            List <Message> messages = new List <Message>();

            string callCategory = null;

                        #if TRACE
            int c = GetNextCallId();
            callCategory = String.Format("{0} {1}:BufferMessages({2},{3},{4},{5},{6})", this.connectionType, c, buffer.Length, bufferOffset, messageOffset, remainingData, reader.Position);
                        #endif
            Trace.WriteLineIf(NTrace.TraceVerbose, "Entering", callCategory);

            BufferValueReader currentReader = reader;

            int length = 0;
            while (remainingData >= BaseHeaderLength)
            {
                if (!TryGetHeader(currentReader, remainingData, ref header))
                {
                    Trace.WriteLineIf(NTrace.TraceVerbose, "Message not ready", callCategory);
                    break;
                }

                if (header == null || header.Message == null)
                {
                    header = null;
                    Disconnect();
                    Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting (header not found)", callCategory);
                    return(null);
                }

                length = header.MessageLength;
                if (length > MaxMessageSize)
                {
                    header = null;
                    Disconnect();
                    Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting (bad message size)", callCategory);
                    return(null);
                }

                if (header.State == HeaderState.IV)
                {
                    DecryptMessage(header, ref currentReader);
                    header.IsStillEncrypted = false;
                    continue;
                }

                if (messageIdCallback != null && !messageIdCallback(header))
                {
                    header = null;
                    Disconnect();
                    Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting (message id callback was false)", callCategory);
                    return(null);
                }

                if (remainingData < length)
                {
                    bufferOffset += remainingData;
                    Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("Message not fully received (boffset={0})", bufferOffset), callCategory);
                    break;
                }

                try
                {
                    Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("Reading payload for message {0}", header.Message), callCategory);
                    header.Message.ReadPayload(header.SerializationContext, currentReader);

                    if (!header.Message.Encrypted && header.Message.Authenticated)
                    {
                        // Zero out length for message signing comparison
                                                #if SAFE
                        for (int i = LengthOffset + messageOffset; i < LengthOffset + sizeof(int) + messageOffset; ++i)
                        {
                            buffer[i] = 0;
                        }
                                                #else
                        fixed(byte *bptr = buffer)
                        * ((int *)(bptr + (LengthOffset + messageOffset))) = 0;
                                                #endif

                        int    payloadLength = reader.Position;
                        byte[] signature     = reader.ReadBytes();
                        if (!VerifyMessage(this.signingHashAlgorithm, header.Message, signature, buffer, messageOffset, payloadLength - messageOffset))
                        {
                            Disconnect(ConnectionResult.MessageAuthenticationFailed);
                            Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting (message auth failed)", callCategory);
                            return(null);
                        }
                    }
                } catch (Exception ex) {
                    header = null;
                    Disconnect();
                    Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting for error: " + ex, callCategory);
                    return(null);
                }

                messages.Add(header.Message);

                currentReader = reader;
                header        = null;

                if (length < buffer.Length)
                {
                    messageOffset += length;
                    bufferOffset   = messageOffset;
                    remainingData -= length;
                }
                else
                {
                    messageOffset          = 0;
                    bufferOffset           = 0;
                    remainingData          = 0;
                    currentReader.Position = 0;
                }

                Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("EOL: moffset={0},boffest={1},rdata={2},rpos={3}", messageOffset, bufferOffset, remainingData, reader.Position), callCategory);
            }

            if (remainingData > 0 || messageOffset + BaseHeaderLength >= buffer.Length)
            {
                Trace.WriteLineIf(NTrace.TraceVerbose, (remainingData > 0) ? String.Format("Data remaining: {0:N0}", remainingData) : "Insufficient room for a header", callCategory);

                int knownRoomNeeded = (remainingData > BaseHeaderLength) ? remainingData : BaseHeaderLength;
                if (header != null && remainingData >= BaseHeaderLength)
                {
                    knownRoomNeeded = header.MessageLength;
                }

                int pos = reader.Position - messageOffset;

                Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("Room needed: {0:N0} bytes", knownRoomNeeded), callCategory);
                if (messageOffset + knownRoomNeeded <= buffer.Length)
                {
                    // bufferOffset is only moved on complete headers, so it's still == messageOffset.
                    bufferOffset = messageOffset + remainingData;
                    //reader.Position = pos;

                    Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("Exiting (sufficient room; boffest={0},rpos={1})", bufferOffset, pos), callCategory);
                    return(messages);
                }

                byte[] destinationBuffer = buffer;
                if (knownRoomNeeded > buffer.Length)
                {
                    reader.Dispose();

                    destinationBuffer = new byte[header.MessageLength];
                    reader            = new BufferValueReader(destinationBuffer);
                }

                Buffer.BlockCopy(buffer, messageOffset, destinationBuffer, 0, remainingData);
                reader.Position = pos;
                messageOffset   = 0;
                bufferOffset    = remainingData;
                buffer          = destinationBuffer;

                Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("Exiting (moved message to front, moffset={1},boffset={2},rpos={0})", reader.Position, messageOffset, bufferOffset), callCategory);
            }
            else
            {
                Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting", callCategory);
            }

            return(messages);
        }
        public bool TryGetHeader(BufferValueReader reader, int remaining, ref MessageHeader header)
        {
            string callCategory = null;

                        #if TRACE
            int c = GetNextCallId();
            callCategory = String.Format("{0} {1}:TryGetHeader({2},{3})", this.connectionType, c, reader.Position, remaining);
                        #endif
            Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("Entering {0}", (header == null) ? "without existing header" : "with existing header"), callCategory);

            int mlen; bool isContinued; Message msg = null; Protocol p;

            int headerLength = BaseHeaderLength;

            if (header == null)
            {
                header = new MessageHeader();
            }
            else if (header.State == HeaderState.Complete)
            {
                return(true);
            }
            else if (header.HeaderLength > 0)
            {
                headerLength = header.HeaderLength;
            }

            try
            {
                if (header.State >= HeaderState.Protocol)
                {
                    p = header.Protocol;
                }
                else
                {
                    byte pid = reader.ReadByte();

                    if (!this.protocols.TryGetValue(pid, out p))
                    {
                        Trace.WriteLineIf(NTrace.TraceWarning, "Exiting (Protocol " + pid + " not found)", callCategory);
                        return(true);
                    }

                    header.Protocol = p;
                    header.State    = HeaderState.Protocol;
                    if (this.serializationContext == null)
                    {
                        if (this.connection != null)
                        {
                            this.serializationContext = new SerializationContext(this.connection, this.protocols);
                        }
                        else
                        {
                            this.serializationContext = new SerializationContext(this.protocols);
                        }
                    }

                    header.SerializationContext = this.serializationContext;
                }

                if (header.State < HeaderState.CID)
                {
                    header.ConnectionId = reader.ReadInt32();
                    header.State        = HeaderState.CID;
                }

                if (header.State >= HeaderState.Type)
                {
                    msg = header.Message;
                }
                else
                {
                    ushort type = reader.ReadUInt16();

                    msg          = header.Message = p.Create(type);
                    header.State = HeaderState.Type;

                    if (msg == null)
                    {
                        Trace.WriteLineIf(NTrace.TraceWarning, "Exiting (Message " + type + " not found)", callCategory);
                        return(true);
                    }

                    msg.Header = header;

                    if (msg.Encrypted)
                    {
                        header.IsStillEncrypted = true;
                    }

                    Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("Have " + msg.GetType().Name), callCategory);
                }

                if (header.State >= HeaderState.Length)
                {
                    mlen = header.MessageLength;
                }
                else
                {
                    mlen = reader.ReadInt32();

                    if (mlen <= 0)
                    {
                        Trace.WriteLineIf(NTrace.TraceWarning, "Exiting (length invalid)", callCategory);
                        return(true);
                    }

                    header.MessageLength = mlen;
                    header.State         = HeaderState.Length;

                    Trace.WriteLineIf(NTrace.TraceVerbose, String.Format("Have message of length: {0}", mlen), callCategory);
                }

                if (header.State == HeaderState.IV)
                {
                    if (header.IsStillEncrypted)
                    {
                        Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting (message not buffered)", callCategory);
                        return(!(remaining < mlen));
                    }
                    else if (header.Message.Encrypted)
                    {
                        reader.Position = 0;
                    }
                }
                else if (msg.Encrypted)                // && AES != null)
                {
                    int ivLength = reader.ReadInt32(); //AES.IV.Length;
                    headerLength += ivLength + sizeof(int);

                    if (remaining < headerLength)
                    {
                        reader.Position -= sizeof(int);
                        Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting (header not buffered (IV))", callCategory);
                        return(false);
                    }

                    byte[] iv = reader.ReadBytes(ivLength);

                    header.HeaderLength = headerLength;
                    header.State        = HeaderState.IV;
                    header.IV           = iv;

                    if (remaining < mlen)
                    {
                        Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting (message not buffered)", callCategory);
                        return(false);
                    }

                    Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting (need to decrypt)", callCategory);
                    return(true);
                }

                if (header.State < HeaderState.MessageId)
                {
                    int identV = reader.ReadInt32();
                    header.MessageId  = identV & ~ResponseFlag;
                    header.IsResponse = (identV & ResponseFlag) == ResponseFlag;

                    header.State = (header.IsResponse) ? HeaderState.MessageId : HeaderState.Complete;

                    Trace.WriteLineIf(NTrace.TraceVerbose, "Have message ID: " + header.MessageId, callCategory);
                }

                if (header.State < HeaderState.ResponseMessageId)
                {
                    header.ResponseMessageId = reader.ReadInt32();
                    header.State             = HeaderState.Complete;

                    Trace.WriteLineIf(NTrace.TraceVerbose, "Have message in resoponse to ID: " + header.ResponseMessageId);
                }

                Trace.WriteLineIf(NTrace.TraceVerbose, "Exiting", callCategory);
                return(true);
            }
            catch (Exception ex)
            {
                Trace.WriteLineIf(NTrace.TraceError, "Exiting (error): " + ex, callCategory);
                header = null;
                return(true);
            }
        }