Subversion Repositories general

Rev

Rev 1232 | Rev 1234 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

// FIXME deny write access to all public properties
// FIXME for text/xml get encoding from body if not specified in header
// FIXME option to not store parsed data, just raw packets and reparse on demand
// FIXME implement all others (not 'identity' and 'chunked') transfer-encodings
// FIXME benchmark one-by-one byte iterator vs. block read
// FIXME recongize "100 (Continue)" response, use next (real) response as part of same 
//       request-response interaction
// FIXME split HttpMessage class to request and response
namespace TCPproxy
{
        public enum BinLogTypes
        {
                None = 1,
                TcpConnection
        }

        public enum LogLevel
        {
                Critical,
                Error,
                Warning,
                Important,
                Info,
                Debug
        }

        public enum SocketState
        {
                None,
                Connecting,
                Connected,
                ShutdownSend,
                ShutdownReceived,
                Closed
        }

        public enum TcpMessageDirection
        {
                None,
                Local,
                Remote
        }

        public enum HttpVersion
        {
                V0_9,
                V1_0,
                V1_1
        }

        public enum HttpTransferEncoding
        {
                None,              // there is no body
                Identity,
                Chunked,
                Gzip,
                Compress,
                Deflate,
                Unknown
        }

        public enum HttpContentEncoding
        {
                None,              // there is no body
                Identity,
                Gzip,
                Compress,
                Deflate,
                Unknown
        }

        public class TcpLogEventArgs : EventArgs
        {
                private readonly LogLevel  level;
                private readonly string    message;
                private readonly Exception exception;

                public LogLevel Level
                {
                        get { return level; }
                }

                public string Message
                {
                        get { return message; }
                }

                public Exception Exception
                {
                        get { return exception; }
                }

                internal TcpLogEventArgs(LogLevel level, string message, Exception exception)
                {
                        this.level     = level;
                        this.message   = message;
                        this.exception = exception;
                }
        }

        public class TcpEventArgs : EventArgs
        {
                internal TcpEventArgs()
                {
                }
        }

        public class TcpConnectionEventArgs : EventArgs
        {
                private readonly TcpConnection tcp;

                public TcpConnection Tcp
                {
                        get { return tcp; }
                }

                internal TcpConnectionEventArgs(TcpConnection tcp)
                {
                        this.tcp = tcp;
                }
        }

        public class TcpHttpEventArgs : EventArgs
        {
                private readonly HttpMessage http;

                public HttpMessage Http
                {
                        get { return http; }
                }

                internal TcpHttpEventArgs(HttpMessage http)
                {
                        this.http = http;
                }
        }

        public delegate void TcpLogEventHandler(object sender, TcpLogEventArgs e);

        public delegate void TcpEventHandler(object sender, TcpEventArgs e);

        public delegate void TcpConnectionEventHandler(object sender, TcpConnectionEventArgs e);

        public delegate void TcpHttpEventHandler(object sender, TcpHttpEventArgs e);

        public class TcpListener
        {
                private int       listenPort     = -1;
                private IPAddress resendHost;
                private int       resendPort     = -1;
                private Socket    socket;
                private int       tcpId          = 0;
                private ArrayList tcpConnections = new ArrayList();

                public TcpListener(int listenPort, IPAddress resendHost, int resendPort)
                {
                        this.listenPort = listenPort;
                        this.resendHost = resendHost;
                        this.resendPort = resendPort;
                }

                public void StartListening()
                {
                        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                        socket.Bind(new IPEndPoint(IPAddress.Any, listenPort));
                        socket.Listen(100);
                        ThreadDebugger.Add(socket.BeginAccept(new AsyncCallback(OnClientConnect), null), "StartListening");
                        SendLog(null, LogLevel.Important, "Listen on " + socket.LocalEndPoint);
                }

                public void StopListening()
                {
                        try
                        {
                                if(socket != null)
                                {
                                        SendLog(null, LogLevel.Important, "Stop listening " + socket.LocalEndPoint);
                                        socket.Close();
                                }
                        }
                        catch(ObjectDisposedException) // socket is already closed
                        {
                        }
                        catch(Exception ex)
                        {
                                Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
                        }
                }

                public void CancelAll()
                {
                        ArrayList tcpConnectionsCopy;

                        lock(tcpConnections)
                        {
                                tcpConnectionsCopy = new ArrayList(tcpConnections);
                        }

                        foreach(TcpConnection tcp in tcpConnectionsCopy)
                        {
                                tcp.Cancel();
                        }
                }

                protected virtual void OnClientConnect(IAsyncResult asyn)
                {
                        try
                        {
                                Socket worker = socket.EndAccept(asyn);
                                ThreadDebugger.Add(socket.BeginAccept(new AsyncCallback(OnClientConnect), null), "OnClientConnect"); // wait for next client

                                TcpConnection tcp = new TcpConnection(string.Format("{0:0000}", tcpId++));
                                tcp.Close += new TcpEventHandler(TcpConnectionClosed);
                                OnNewTcp(new TcpConnectionEventArgs(tcp));

                                lock(tcpConnections)
                                {
                                        tcpConnections.Add(tcp);
                                }

                                tcp.Continue(resendHost, resendPort, worker);
                        }
                        catch(ObjectDisposedException) {}  // socket is closed
                        catch(Exception ex)
                        {
                                Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
                        }
                }

                protected virtual void TcpConnectionClosed(object sender, TcpEventArgs e)
                {
                        lock(tcpConnections)
                        {
                                tcpConnections.Remove((TcpConnection)sender);
                        }
                }

                public event TcpConnectionEventHandler NewTcp;

                protected virtual void OnNewTcp(TcpConnectionEventArgs e)
                {
                        if(NewTcp != null)
                        {
                                NewTcp(this, e);
                        }
                }

                public event TcpLogEventHandler Log;

                protected virtual void OnLog(TcpLogEventArgs e)
                {
                        if(Log != null)
                        {
                                Log(this, e);
                        }
                }

                protected virtual void SendLog(TcpConnection tcp, LogLevel level, string message)
                {
                        TcpLogEventArgs e = new TcpLogEventArgs(level, message, null);
                        OnLog(e);
                }

                protected virtual void SendLog(TcpConnection tcp, LogLevel level, Exception ex)
                {
                        TcpLogEventArgs e = new TcpLogEventArgs(level, null, ex);
                        OnLog(e);
                }

                public void ReadBinLog(BinaryReader reader)
                {
                        // tcp connections
                        TcpConnection tcp;
                        while((tcp = TcpConnection.ReadBinLog(reader)) != null) {
                                OnNewTcp(new TcpConnectionEventArgs(tcp));
                        }
                }
        }

        public class TcpConnection
        {
                private string       id;
                private DateTime     startTimestamp     = DateTime.MinValue;
                private DateTime     localEndTimestamp  = DateTime.MinValue;
                private DateTime     remoteEndTimestamp = DateTime.MinValue;
                private IPEndPoint   localPoint;
                private IPEndPoint   remotePoint;
                private LinkedList   messages    = new LinkedList();
                private SocketState  localState  = SocketState.None;
                private SocketState  remoteState = SocketState.None;
                private SocketWorker worker;
                private LinkedList   https       = new LinkedList();
                private HttpParser   httpParser;

                public string Id
                {
                        get { return id; }
                }

                public DateTime StartTimestamp
                {
                        get { return startTimestamp; }
                }

                public DateTime LocalEndTimestamp
                {
                        get { return localEndTimestamp; }
                }

                public DateTime RemoteEndTimestamp
                {
                        get { return remoteEndTimestamp; }
                }

                public IPEndPoint LocalPoint
                {
                        get { return localPoint; }
                }

                public IPEndPoint RemotePoint
                {
                        get { return remotePoint; }
                }

                public LinkedList Messages
                {
                        get { return messages; }        // FIXME return read-only object
                }

                public SocketState LocalState
                {
                        get { return localState; }
                }

                public SocketState RemoteState
                {
                        get { return remoteState; }
                }

                private int CountBytes(TcpMessageDirection direction)
                {
                        int count = 0;

                        foreach(TcpMessage message in messages)
                        {
                                if(message.Direction == direction)
                                        count += message.Length;
                        }
                        return count;
                }

                public int SentBytes
                {
                        get { return CountBytes(TcpMessageDirection.Local); }
                }

                public int ReceivedBytes
                {
                        get { return CountBytes(TcpMessageDirection.Remote); }
                }

                internal TcpConnection(string id)
                {
                        this.id         = id;
                        this.httpParser = HttpParser.Parse(this);
                }

                internal void Continue(IPAddress resendHost, int resendPort, Socket localSocket)
                {
                        SendLog(LogLevel.Important, "Client connected from " + ((IPEndPoint)localSocket.RemoteEndPoint).ToString());
                        SetLocalState(SocketState.Connected);
                        this.worker = new SocketWorker(resendHost, resendPort, localSocket, this);
                }

                public void Cancel()
                {
                        if(worker != null)
                                worker.Cancel();
                }

                protected TcpMessage Append(TcpMessageDirection direction, byte[] newBytes)
                {
                        return Append(direction, newBytes, newBytes.Length);
                }

                protected TcpMessage Append(TcpMessageDirection direction, byte[] newBytes, int length)
                {
                        if(newBytes == null) return null;

                        TcpMessage message;

                        lock(this)
                        {
                                message = new TcpMessage();
                                message.Direction = direction;
                                messages.Add(message);
                                message.Append(newBytes, length);
                        }

                        httpParser.NewMessageArived();
                        OnUpdate(new TcpEventArgs());

                        return message;
                }

                internal void AddHttpMessage(HttpMessage http)
                {
                        lock(this)
                        {
                                https.Add(http);
                                TcpHttpEventArgs e = new TcpHttpEventArgs(http);
                                OnNewHttp(e);
                        }
                }

                protected void SetLocalPoint(IPEndPoint localPoint)
                {
                        this.localPoint = localPoint;
                        OnUpdate(new TcpEventArgs());
                }

                protected void SetRemotePoint(IPEndPoint remotePoint)
                {
                        this.remotePoint = remotePoint;
                        OnUpdate(new TcpEventArgs());
                }

                protected void SetLocalState(SocketState localState)
                {
                        if(this.localState == SocketState.None && localState == SocketState.Connecting)
                        {
                                startTimestamp = DateTime.Now;
                        }
                        else if(this.localState == SocketState.None && localState == SocketState.Connected)
                        {
                                startTimestamp = DateTime.Now;
                        }
                        else if(this.localState == SocketState.None && localState == SocketState.Closed)
                        {
                        }
                        else if(this.localState == SocketState.Connecting && localState == SocketState.Connected)
                        {
                        }
                        else if(this.localState == SocketState.Connecting && localState == SocketState.Closed)
                        {
                                if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
                        }
                        else if(this.localState == SocketState.Connected && localState == SocketState.ShutdownSend)
                        {
                        }
                        else if(this.localState == SocketState.Connected && localState == SocketState.ShutdownReceived)
                        {
                                if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
                        }
                        else if(this.localState == SocketState.Connected && localState == SocketState.Closed)
                        {
                                if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
                        }
                        else if(this.localState == SocketState.ShutdownSend && localState == SocketState.Closed)
                        {
                                if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
                        }
                        else if(this.localState == SocketState.ShutdownSend && localState == SocketState.ShutdownReceived)
                        {
                                if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
                        }
                        else if(this.localState == SocketState.ShutdownReceived && localState == SocketState.ShutdownSend)
                        {
                        }
                        else if(this.localState == SocketState.ShutdownReceived && localState == SocketState.Closed)
                        {
                                if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
                        }
                        else if(this.localState == SocketState.Closed && localState == SocketState.Closed)
                        {
                                if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
                        }
                        else
                        {
                                throw new Exception("Wrong local socket state change: from " + this.localState + " to " + localState);
                        }
                        this.localState = localState;
                        if(this.localState == SocketState.Closed) httpParser.NewMessageArived();
                        OnUpdate(new TcpEventArgs());
                }

                protected void SetRemoteState(SocketState remoteState)
                {
                        if(this.remoteState == SocketState.None && remoteState == SocketState.Connecting)
                        {
                        }
                        else if(this.remoteState == SocketState.None && remoteState == SocketState.Connected)
                        {
                        }
                        else if(this.remoteState == SocketState.None && remoteState == SocketState.Closed)
                        {
                        }
                        else if(this.remoteState == SocketState.Connecting && remoteState == SocketState.Connected)
                        {
                        }
                        else if(this.remoteState == SocketState.Connecting && remoteState == SocketState.Closed)
                        {
                                if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
                        }
                        else if(this.remoteState == SocketState.Connected && remoteState == SocketState.ShutdownSend)
                        {
                        }
                        else if(this.remoteState == SocketState.Connected && remoteState == SocketState.ShutdownReceived)
                        {
                                if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
                        }
                        else if(this.remoteState == SocketState.Connected && remoteState == SocketState.Closed)
                        {
                                if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
                        }
                        else if(this.remoteState == SocketState.ShutdownSend && remoteState == SocketState.Closed)
                        {
                                if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
                        }
                        else if(this.remoteState == SocketState.ShutdownSend && remoteState == SocketState.ShutdownReceived)
                        {
                                if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
                        }
                        else if(this.remoteState == SocketState.ShutdownReceived && remoteState == SocketState.ShutdownSend)
                        {
                        }
                        else if(this.remoteState == SocketState.ShutdownReceived && remoteState == SocketState.Closed)
                        {
                                if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
                        }
                        else if(this.remoteState == SocketState.Closed && remoteState == SocketState.Closed)
                        {
                                if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
                        }
                        else
                        {
                                throw new Exception("Wrong remote socket state change: from " + this.remoteState + " to " + remoteState);
                        }
                        this.remoteState = remoteState;
                        if(this.remoteState == SocketState.Closed) httpParser.NewMessageArived();
                        OnUpdate(new TcpEventArgs());
                }

                public event TcpEventHandler Update;

                protected virtual void OnUpdate(TcpEventArgs e)
                {
                        if(Update != null)
                        {
                                Update(this, e);
                        }
                }

                public event TcpHttpEventHandler NewHttp;

                protected virtual void OnNewHttp(TcpHttpEventArgs e)
                {
                        if(NewHttp != null)
                        {
                                NewHttp(this, e);
                        }
                }

                public event TcpEventHandler Close;

                protected virtual void OnClose(TcpEventArgs e)
                {
                        if(Close != null)
                        {
                                Close(this, e);
                        }
                }

                public event TcpLogEventHandler Log;

                protected virtual void OnLog(TcpLogEventArgs e)
                {
                        if(Log != null)
                        {
                                Log(this, e);
                        }
                }

                protected virtual void SendLog(LogLevel level, string message)
                {
                        TcpLogEventArgs e = new TcpLogEventArgs(level, message, null);
                        OnLog(e);
                }

                protected virtual void SendLog(LogLevel level, Exception ex)
                {
                        TcpLogEventArgs e = new TcpLogEventArgs(level, null, ex);
                        OnLog(e);
                }

                private void WriteEndPoint(BinaryWriter writer, IPEndPoint endPoint)
                {
                        // end point as (family as int, ip length, ip as bytes, port)
                        byte[] ipBuf = endPoint.Address.GetAddressBytes();
                        writer.Write((UInt32)endPoint.AddressFamily);
                        writer.Write((UInt32)ipBuf.Length);
                        writer.Write(ipBuf);
                        writer.Write((UInt32)endPoint.Port);
                }

                public void WriteBinLog(BinaryWriter writer)
                {
                        // header
                        writer.Write((byte)BinLogTypes.TcpConnection);

                        // id as (length, UTF-8)
                        byte[] idBuf = Encoding.UTF8.GetBytes(id);
                        writer.Write((UInt32)idBuf.Length);
                        writer.Write(idBuf);

                        // timestamps as ticks
                        writer.Write((UInt64)startTimestamp.Ticks);
                        writer.Write((UInt64)localEndTimestamp.Ticks);
                        writer.Write((UInt64)remoteEndTimestamp.Ticks);

                        // end points as (family as int, ip length, ip as bytes, port)
                        WriteEndPoint(writer, localPoint);
                        WriteEndPoint(writer, remotePoint);

                        // states as byte
                        writer.Write((byte)localState);
                        writer.Write((byte)remoteState);

                        // each tcp message as (direction as byte, timestamp in ticks, length, content)
                        foreach(TcpMessage message in messages) {
                                writer.Write((byte)message.Direction);
                                writer.Write((UInt64)message.Timestamp.Ticks);
                                writer.Write((UInt32)message.Length);
                                writer.Write(message.Bytes, 0, message.Length);
                        }

                        // end of stream marker
                        writer.Write((byte)TcpMessageDirection.None);
                }

                private static IPEndPoint ReadEndPoint(BinaryReader reader)
                {
                        // end point as (family as int, ip length, ip as bytes, port)
                        AddressFamily fam           = (AddressFamily)reader.ReadInt32();
                        int           addressLength = reader.ReadInt32();
                        byte[]        addressBytes  = reader.ReadBytes(addressLength);
                        int           port          = reader.ReadInt32();
                        IPAddress     address       = new IPAddress(addressBytes);

                        return new IPEndPoint(address, port);
                }

                internal static TcpConnection ReadBinLog(BinaryReader reader)
                {
                        // header
                        BinLogTypes recordType = (BinLogTypes)reader.ReadByte();

                        if(recordType == BinLogTypes.None)
                                return null;
                        else if(recordType == BinLogTypes.TcpConnection) {
                        }
                        else
                                throw new Exception("Wrong data type");

                        // id as (length, UTF-8)
                        int    idLength = (int)reader.ReadUInt32();
                        string id       = Encoding.UTF8.GetString(reader.ReadBytes(idLength));

                        TcpConnection tcp = new TcpConnection(id);

                        // timestamps as ticks
                        tcp.startTimestamp     = new DateTime((long)reader.ReadUInt64());
                        tcp.localEndTimestamp  = new DateTime((long)reader.ReadUInt64());
                        tcp.remoteEndTimestamp = new DateTime((long)reader.ReadUInt64());

                        // end points as (family as int, ip length, ip as bytes, port)
                        tcp.localPoint  = ReadEndPoint(reader);
                        tcp.remotePoint = ReadEndPoint(reader);

                        // states as byte - read but ignore
                        reader.ReadByte();
                        reader.ReadByte();

                        // each tcp message as (direction as byte, timestamp in ticks, length, content)
                        tcp.localState  = SocketState.Closed;
                        tcp.remoteState = SocketState.Closed;
                        for(;;) {
                                TcpMessageDirection direction = (TcpMessageDirection)reader.ReadByte();
                                if(direction == TcpMessageDirection.None) break; // end of stream marker

                                DateTime timestamp = new DateTime((long)reader.ReadUInt64());
                                int      bufLength = (int)reader.ReadUInt32();
                                byte[]   buf       = reader.ReadBytes(bufLength);

                                TcpMessage msg = tcp.Append(direction, buf);
                                msg.SetTimestamp(timestamp);
                        }
                        tcp.OnUpdate(new TcpEventArgs());

                        return tcp;
                }

                protected class SocketWorker
                {
                        private enum SendCommandType
                        {
                                Send,
                                Shutdown,
                                Reset
                        }

                        private class SendCommand
                        {
                                public byte[]          buffer  = null;
                                public int             length  = 0;
                                public SendCommandType cmdType = SendCommandType.Send;
                        }

                        private static int BUF_SIZE = 2048;

                        private TcpConnection    tcp;
                        private Socket           localSocket;
                        private Socket           remoteSocket;
                        private byte[]           localDataBuffer;
                        private byte[]           remoteDataBuffer;
                        private AsyncCallback    receiveLocalMethod;
                        private AsyncCallback    receiveRemoteMethod;
                        private Queue            localSendQueue              = new Queue();
                        private Queue            remoteSendQueue             = new Queue();
                        private AutoResetEvent   localSendEvent              = new AutoResetEvent(false);
                        private AutoResetEvent   remoteSendEvent             = new AutoResetEvent(false);
                        private bool             localSocketSendShutdown     = false;
                        private bool             localSocketReceiveShutdown  = false;
                        private bool             remoteSocketSendShutdown    = false;
                        private bool             remoteSocketReceiveShutdown = false;
                        private ManualResetEvent remoteSocketEvent           = new ManualResetEvent(false);
                        private Thread           localSendThread;
                        private Thread           remoteSendThread;

                        public SocketWorker(IPAddress resendHost, int resendPort, Socket localSocket, TcpConnection tcp)
                        {
                                try
                                {
                                        tcp.SendLog(LogLevel.Debug, string.Format("Local socket: {0}:{1} <-> {2}:{3}",
                                                ((IPEndPoint)localSocket.LocalEndPoint).Address,
                                                ((IPEndPoint)localSocket.LocalEndPoint).Port,
                                                ((IPEndPoint)localSocket.RemoteEndPoint).Address,
                                                ((IPEndPoint)localSocket.RemoteEndPoint).Port));

                                        this.localSocket    = localSocket;
                                        this.tcp            = tcp;
                                        receiveLocalMethod  = new AsyncCallback(OnLocalReceived);
                                        receiveRemoteMethod = new AsyncCallback(OnRemoteReceived);

                                        tcp.SetLocalPoint((IPEndPoint)localSocket.RemoteEndPoint);
                                        this.localSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);

                                        localSendThread = new Thread(new ThreadStart(LocalSendProc));
                                        ThreadDebugger.Add(localSendThread, "LocalSendProc");
                                        localSendThread.Name = "SocketWorker.LocalSendProc";
                                        localSendThread.Start();

                                        ContinueLocalReceive();

                                        if(resendHost == null)
                                        {
                                                remoteSocket = null;
                                        }
                                        else
                                        {
                                                tcp.SetRemoteState(SocketState.Connecting);

                                                IPEndPoint point = new IPEndPoint(resendHost, resendPort);
                                                remoteSocket = new Socket(point.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                                                remoteSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
                                                remoteSocket.Connect(point);
                                                tcp.SetRemoteState(SocketState.Connected);
                                                tcp.SetRemotePoint((IPEndPoint)remoteSocket.RemoteEndPoint);

                                                remoteSendThread = new Thread(new ThreadStart(RemoteSendProc));
                                                ThreadDebugger.Add(remoteSendThread, "RemoteSendProc");
                                                remoteSendThread.Name = "SocketWorker.RemoteSendProc";
                                                remoteSendThread.Start();

                                                ContinueRemoteReceive();
                                                remoteSocketEvent.Set(); // remote socket ready to send data
                                                tcp.SendLog(LogLevel.Info, "Connected to server " + tcp.RemotePoint.ToString());

                                                tcp.SendLog(LogLevel.Debug, string.Format("Remote socket: {0}:{1} <-> {2}:{3}",
                                                        ((IPEndPoint)remoteSocket.LocalEndPoint).Address,
                                                        ((IPEndPoint)remoteSocket.LocalEndPoint).Port,
                                                        ((IPEndPoint)remoteSocket.RemoteEndPoint).Address,
                                                        ((IPEndPoint)remoteSocket.RemoteEndPoint).Port));
                                        }
                                }
                                catch(Exception ex)
                                {
                                        tcp.SendLog(LogLevel.Warning, ex);
                                        Cancel();
                                }
                        }

                        private void ContinueLocalReceive()
                        {
                                try
                                {
                                        localDataBuffer = new byte[BUF_SIZE];
                                        ThreadDebugger.Add(
                                                localSocket.BeginReceive(localDataBuffer, 0, BUF_SIZE, SocketFlags.None, receiveLocalMethod, this),
                                                "ContinueLocalReceive");
                                }
                                catch(ObjectDisposedException ex) // the socket is closed
                                {
                                        tcp.SendLog(LogLevel.Info, ex);
                                        Cancel();
                                }
                                catch(Exception ex)
                                {
                                        tcp.SendLog(LogLevel.Warning, ex);
                                        Cancel();
                                }
                        }

                        private void ContinueRemoteReceive()
                        {
                                try
                                {
                                        remoteDataBuffer = new byte[BUF_SIZE];
                                        ThreadDebugger.Add(
                                                remoteSocket.BeginReceive(remoteDataBuffer, 0, BUF_SIZE, SocketFlags.None, receiveRemoteMethod, this),
                                                "ContinueRemoteReceive");
                                }
                                catch(ObjectDisposedException ex) // the socket is closed
                                {
                                        tcp.SendLog(LogLevel.Info, ex);
                                        Cancel();
                                }
                                catch(Exception ex)
                                {
                                        tcp.SendLog(LogLevel.Warning, ex);
                                        Cancel();
                                }
                        }

                        private void CheckLocalSocket()
                        {
                                lock(localSocket)
                                {
                                        try
                                        {
                                                if(localSocketReceiveShutdown && localSocketSendShutdown)
                                                {
                                                        if(localSocket.Connected) localSocket.Close();
                                                        tcp.SetLocalState(SocketState.Closed);
                                                }
                                        }
                                        catch(Exception ex) // in any case we want to close the socket
                                        {
                                                tcp.SendLog(LogLevel.Warning, ex);
                                        }
                                }
                        }

                        private void CheckRemoteSocket()
                        {
                                lock(remoteSocket)
                                {
                                        try
                                        {
                                                if(remoteSocketReceiveShutdown && remoteSocketSendShutdown)
                                                {
                                                        if(remoteSocket.Connected) remoteSocket.Close();
                                                        tcp.SetRemoteState(SocketState.Closed);
                                                }
                                        }
                                        catch(Exception ex) // in any case we want to close the socket
                                        {
                                                tcp.SendLog(LogLevel.Warning, ex);
                                        }
                                }
                        }

                        private void OnLocalReceived(IAsyncResult asyn)
                        {
                                try
                                {
                                        int  bytesReceived = 0;
                                        bool reset         = false;

                                        try
                                        {
                                                bytesReceived = localSocket.EndReceive(asyn);
                                        }
                                        catch(ObjectDisposedException)
                                        {
                                                reset = true;
                                        }
                                        catch(SocketException ex)
                                        {
                                                if(ex.ErrorCode == 10054)
                                                        reset = true;
                                                else
                                                        throw ex;
                                        }

                                        if(reset)
                                        {
                                                tcp.SendLog(LogLevel.Info, "Got reset from local end");

                                                lock(localSocket)
                                                {
                                                        if(localSocket.Connected) localSocket.Close();
                                                        tcp.SetLocalState(SocketState.Closed);
                                                }

                                                SendCommand cmd = new SendCommand();
                                                cmd.cmdType = SendCommandType.Reset;
                                                lock(localSendQueue)
                                                {
                                                        localSendQueue.Enqueue(cmd);
                                                        localSendEvent.Set();
                                                }
                                        }
                                        else if(bytesReceived <= 0)
                                        {
                                                tcp.SendLog(LogLevel.Info, "Got showdown from local end");

                                                localSocket.Shutdown(SocketShutdown.Receive);
                                                tcp.SetLocalState(SocketState.ShutdownReceived);
                                                localSocketReceiveShutdown = true;
                                                CheckLocalSocket();

                                                SendCommand cmd = new SendCommand();
                                                cmd.cmdType = SendCommandType.Shutdown;
                                                lock(localSendQueue)
                                                {
                                                        localSendQueue.Enqueue(cmd);
                                                        localSendEvent.Set();
                                                }
                                        }
                                        else
                                        {
                                                tcp.SendLog(LogLevel.Debug, string.Format("Local received {0} bytes", bytesReceived));

                                                SendCommand cmd = new SendCommand();
                                                cmd.buffer = localDataBuffer;
                                                cmd.length = bytesReceived;
                                                lock(localSendQueue)
                                                {
                                                        localSendQueue.Enqueue(cmd);
                                                        localSendEvent.Set();
                                                }
                                                ContinueLocalReceive();
                                        }
                                }
                                catch(Exception ex)
                                {
                                        tcp.SendLog(LogLevel.Warning, ex);
                                        Cancel();
                                }
                        }

                        private void LocalSendProc()
                        {
                                try
                                {
                                        while(true)
                                        {
                                                SendCommand cmd;

                                                if(localSendQueue.Count == 0)
                                                {
                                                        localSendEvent.WaitOne();
                                                }

                                                lock(localSendQueue)
                                                {
                                                        localSendEvent.Reset();
                                                        cmd = (SendCommand)localSendQueue.Dequeue();
                                                }

                                                if(cmd.cmdType == SendCommandType.Reset) // reset marker
                                                {
                                                        if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
                                                        tcp.SendLog(LogLevel.Debug, string.Format("Send reset to remote end"));
                                                        lock(remoteSocket)
                                                        {
                                                                if(!remoteSocket.Connected) remoteSocket.Close();
                                                                tcp.SetRemoteState(SocketState.Closed);
                                                        }

                                                        break; // no more send allowed
                                                }
                                                else if(cmd.cmdType == SendCommandType.Shutdown) // shutdown marker
                                                {
                                                        if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
                                                        tcp.SendLog(LogLevel.Debug, string.Format("Send shutdown to remote end"));
                                                        remoteSocket.Shutdown(SocketShutdown.Send);
                                                        tcp.SetRemoteState(SocketState.ShutdownSend);
                                                        remoteSocketSendShutdown = true;
                                                        CheckRemoteSocket();

                                                        break; // no more send allowed
                                                }
                                                else
                                                {
                                                        // store received bytes
                                                        tcp.Append(TcpMessageDirection.Local, cmd.buffer, cmd.length);

                                                        // forward it
                                                        if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
                                                        tcp.SendLog(LogLevel.Debug, string.Format("Send {0} bytes to remote end", cmd.length));
                                                        remoteSocket.Send(cmd.buffer, cmd.length, SocketFlags.None);
                                                }
                                        }
                                }
                                catch(ThreadAbortException)
                                {
                                }
                                catch(Exception ex)
                                {
                                        tcp.SendLog(LogLevel.Warning, ex);
                                        Cancel();
                                }
                        }

                        private void OnRemoteReceived(IAsyncResult asyn)
                        {
                                try
                                {
                                        int  bytesReceived = 0;
                                        bool reset         = false;

                                        try
                                        {
                                                bytesReceived = remoteSocket.EndReceive(asyn);
                                        }
                                        catch(ObjectDisposedException)
                                        {
                                                reset = true;
                                        }
                                        catch(SocketException ex)
                                        {
                                                if(ex.ErrorCode == 10054)
                                                        reset = true;
                                                else
                                                        throw ex;
                                        }

                                        if(reset)
                                        {
                                                tcp.SendLog(LogLevel.Info, "Got reset from remote end");

                                                lock(remoteSocket)
                                                {
                                                        if(remoteSocket.Connected) remoteSocket.Close();
                                                        tcp.SetRemoteState(SocketState.Closed);
                                                }

                                                SendCommand cmd = new SendCommand();
                                                cmd.cmdType = SendCommandType.Reset;
                                                lock(remoteSendQueue)
                                                {
                                                        remoteSendQueue.Enqueue(cmd);
                                                        remoteSendEvent.Set();
                                                }
                                        }
                                        else if(bytesReceived <= 0)
                                        {
                                                tcp.SendLog(LogLevel.Info, "Got showdown from remote end");

                                                remoteSocket.Shutdown(SocketShutdown.Receive);
                                                tcp.SetRemoteState(SocketState.ShutdownReceived);
                                                remoteSocketReceiveShutdown = true;
                                                CheckRemoteSocket();

                                                SendCommand cmd = new SendCommand();
                                                cmd.cmdType = SendCommandType.Shutdown;
                                                lock(remoteSendQueue)
                                                {
                                                        remoteSendQueue.Enqueue(cmd);
                                                        remoteSendEvent.Set();
                                                }
                                        }
                                        else
                                        {
                                                tcp.SendLog(LogLevel.Debug, string.Format("Remote received {0} bytes", bytesReceived));

                                                SendCommand cmd = new SendCommand();
                                                cmd.buffer = remoteDataBuffer;
                                                cmd.length = bytesReceived;
                                                lock(remoteSendQueue)
                                                {
                                                        remoteSendQueue.Enqueue(cmd);
                                                        remoteSendEvent.Set();
                                                }
                                                ContinueRemoteReceive();
                                        }
                                }
                                catch(Exception ex)
                                {
                                        tcp.SendLog(LogLevel.Warning, ex);
                                        Cancel();
                                }
                        }

                        private void RemoteSendProc()
                        {
                                try
                                {
                                        while(true)
                                        {
                                                SendCommand cmd;

                                                if(remoteSendQueue.Count == 0)
                                                {
                                                        remoteSendEvent.WaitOne();
                                                }

                                                lock(remoteSendQueue)
                                                {
                                                        remoteSendEvent.Reset();
                                                        cmd = (SendCommand)remoteSendQueue.Dequeue();
                                                }

                                                if(cmd.cmdType == SendCommandType.Reset) // reset marker
                                                {
                                                        tcp.SendLog(LogLevel.Debug, string.Format("Send reset to local end"));
                                                        lock(localSocket)
                                                        {
                                                                if(localSocket.Connected) localSocket.Close();
                                                                tcp.SetLocalState(SocketState.Closed);
                                                        }

                                                        break; // no more send allowed
                                                }
                                                else if(cmd.cmdType == SendCommandType.Shutdown) // shutdown marker
                                                {
                                                        tcp.SendLog(LogLevel.Debug, string.Format("Send shutdown to local end"));
                                                        localSocket.Shutdown(SocketShutdown.Send);
                                                        tcp.SetLocalState(SocketState.ShutdownSend);
                                                        localSocketSendShutdown = true;
                                                        CheckLocalSocket();

                                                        break; // no more send allowed
                                                }
                                                else
                                                {
                                                        // store received bytes
                                                        tcp.Append(TcpMessageDirection.Remote, cmd.buffer, cmd.length);

                                                        // forward it
                                                        tcp.SendLog(LogLevel.Debug, string.Format("Send {0} bytes to local end", cmd.length));
                                                        localSocket.Send(cmd.buffer, cmd.length, SocketFlags.None);
                                                }
                                        }
                                }
                                catch(ThreadAbortException)
                                {
                                }
                                catch(Exception ex)
                                {
                                        tcp.SendLog(LogLevel.Warning, ex);
                                        Cancel();
                                }
                        }

                        public void Cancel()
                        {
                                tcp.SendLog(LogLevel.Important, "Connection canceled");

                                try
                                {
                                        if(localSendThread != null  && localSendThread.IsAlive)  localSendThread.Abort();
                                        if(remoteSendThread != null && remoteSendThread.IsAlive) remoteSendThread.Abort();

                                        // close sockets
                                        try
                                        {
                                                if(localSocket != null)
                                                {
                                                        lock(localSocket)
                                                        {
                                                                if(localSocket.Connected) localSocket.Close();
                                                        }
                                                }
                                        }
                                        catch(Exception ex) // in any case we want to close the socket
                                        {
                                                tcp.SendLog(LogLevel.Warning, ex);
                                        }
                                        tcp.SetLocalState(SocketState.Closed);

                                        try
                                        {
                                                if(remoteSocket != null)
                                                {
                                                        lock(remoteSocket)
                                                        {
                                                                if(remoteSocket.Connected) remoteSocket.Close();
                                                        }
                                                }
                                        }
                                        catch(Exception ex) // in any case we want to close the socket
                                        {
                                                tcp.SendLog(LogLevel.Warning, ex);
                                        }
                                        tcp.SetRemoteState(SocketState.Closed);

                                        // return
                                        tcp.OnClose(new TcpEventArgs());
                                }
                                catch(Exception ex)
                                {
                                        tcp.SendLog(LogLevel.Warning, ex);
                                }
                        }
                }
        }

        internal class HttpParser
        {
                private enum HttpCharType
                {
                        None,
                        Control,
                        Digit,
                        UpAlpha,
                        LoAlpha,
                        NonChar,
                        Separator,
                        CrLf,
                }

                private static HttpCharType[] charTypes  = null;
                private static bool[]         tokenChars = null;
                private static char[]         charValues = {
                         '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
                         '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
                         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'', '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
                         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',  '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
                         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',  'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
                         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',  'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
                         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
                         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  '{',  '|',  '}',  '~',  '\0'
                 };

                private static void InitTables()
                {
                        if(charTypes != null) return;

                        // main table
                        charTypes = new HttpCharType[256];

                        for(int i = 0; i < charTypes.Length; i++) charTypes[i] = HttpCharType.None;

                        for(int i = 0; i <= 31; i++) charTypes[i] = HttpCharType.Control;
                        charTypes[127] = HttpCharType.Control;   // <del>

                        for(int i = 48; i <= 57; i++) charTypes[i] = HttpCharType.Digit;

                        for(int i = 65; i <=  90; i++) charTypes[i] = HttpCharType.UpAlpha;
                        for(int i = 97; i <= 122; i++) charTypes[i] = HttpCharType.LoAlpha;

                        for(int i = 128; i < charTypes.Length; i++) charTypes[i] = HttpCharType.NonChar;

                        charTypes[ 40] = HttpCharType.Separator;   // (
                        charTypes[ 41] = HttpCharType.Separator;   // )
                        charTypes[ 60] = HttpCharType.Separator;   // <
                        charTypes[ 62] = HttpCharType.Separator;   // >
                        charTypes[ 64] = HttpCharType.Separator;   // @
                        charTypes[ 44] = HttpCharType.Separator;   // ,
                        charTypes[ 59] = HttpCharType.Separator;   // ;
                        charTypes[ 58] = HttpCharType.Separator;   // :
                        charTypes[ 92] = HttpCharType.Separator;   // \
                        charTypes[ 34] = HttpCharType.Separator;   // "
                        charTypes[ 47] = HttpCharType.Separator;   // /
                        charTypes[ 91] = HttpCharType.Separator;   // [
                        charTypes[ 93] = HttpCharType.Separator;   // ]
                        charTypes[ 63] = HttpCharType.Separator;   // ?
                        charTypes[ 61] = HttpCharType.Separator;   // =
                        charTypes[123] = HttpCharType.Separator;   // {
                        charTypes[125] = HttpCharType.Separator;   // }
                        charTypes[ 32] = HttpCharType.Separator;   // <space>
                        charTypes[  9] = HttpCharType.Separator;   // <tab>

                        charTypes[ 13] = HttpCharType.CrLf;        // <CR>
                        charTypes[ 10] = HttpCharType.CrLf;        // <LF>

                        // token table
                        tokenChars = new bool[256];
                        for(int i = 0; i < tokenChars.Length; i++)
                        {
                                tokenChars[i] = !(charTypes[i] == HttpCharType.NonChar
                                        || charTypes[i] == HttpCharType.Control || charTypes[i] == HttpCharType.Separator
                                        || charTypes[i] == HttpCharType.CrLf);
                        }
                }

                private class ParsePosition
                {
                        private TcpConnection  messages;
                        private IEnumerator    messagesEnum;
                        private TcpMessage     tcp    = null;
                        private int            tcpPos;
                        private int            tcpLen;
                        private bool           tcpEnd = false;
                        private AutoResetEvent newMessageEvent;
                        private AutoResetEvent nextMessageEvent;

                        public ParsePosition(TcpConnection messages, AutoResetEvent nextMessageEvent)
                        {
                                this.messages         = messages;
                                this.messagesEnum     = messages.Messages.GetEnumerator();
                                this.newMessageEvent  = new AutoResetEvent(false);
                                this.nextMessageEvent = nextMessageEvent;
                        }

                        public AutoResetEvent NewMessageEvent
                        {
                                get { return newMessageEvent; }
                        }

                        public bool IsEnd
                        {
                                get { return tcpEnd; }
                        }

                        public TcpMessage CurrentMessage
                        {
                                get { return tcp; }
                        }

                        private bool MoveNext()
                        {
                                for(bool moved = false; !moved; )
                                {
                                        lock(messages)
                                        {
                                                newMessageEvent.Reset();
                                                moved = messagesEnum.MoveNext();

                                                if(!moved && messages.LocalState == SocketState.Closed && messages.RemoteState == SocketState.Closed)
                                                        return false;
                                        }

                                        if(moved) break;

                                        if(!newMessageEvent.WaitOne())
                                                throw new Exception("Cannot get next TCP message");

                                        lock(messages)
                                        {
                                                if(messages.LocalState == SocketState.Closed && messages.RemoteState == SocketState.Closed)
                                                        return false;

                                                moved = messagesEnum.MoveNext();
                                        }
                                }

                                return true;
                        }

                        private void NextTcp()
                        {
                                if(tcpEnd) return;

                                TcpMessage newTcp = null;

                                do
                                {
                                        if(!MoveNext())
                                        {
                                                tcpEnd = true;
                                                break;
                                        }
                                        newTcp = (TcpMessage)messagesEnum.Current;
                                }
                                while(tcp != null && tcp.Direction != newTcp.Direction);
                                // FIXME correct? no hang?

                                if(!tcpEnd)
                                {
                                        tcp    = newTcp;
                                        tcpLen = tcp.Length;
                                        tcpPos = -1;
                                        if(nextMessageEvent != null) nextMessageEvent.Set();
                                }
                        }

                        public void Back()
                        {
                                tcpPos--; // FIXME double check if it's always possible to go to _one_ step back
                        }

                        public byte NextOctet()
                        {
                                tcpPos++;

                                if(tcp == null || tcpPos >= tcpLen)
                                {
                                        NextTcp();
                                        tcpPos++;
                                }
                                if(tcpEnd) return 0;

                                return tcp.Bytes[tcpPos];
                        }

                        public byte NextOctetAndBack()
                        {
                                byte b = NextOctet();
                                if(!tcpEnd) Back();

                                return b;
                        }

                        public void SetDirection(TcpMessageDirection direction)
                        {
                                do
                                {
                                        if(!MoveNext())
                                        {
                                                tcp    = null;
                                                tcpEnd = true;
                                                break;
                                        }
                                        tcp = (TcpMessage)messagesEnum.Current;
                                }
                                while(tcp.Direction != direction);

                                if(!tcpEnd)
                                {
                                        tcpLen = tcp.Length;
                                        tcpPos = -1;
                                }
                        }
                }

                private TcpConnection    messages;
                private AutoResetEvent   requestEvent     = new AutoResetEvent(false); // new request found
                private AutoResetEvent   nextMessageEvent = new AutoResetEvent(false); // request goes to next TCP message
                private LinkedList       https            = new LinkedList();
                private ParsePosition    requestPos;
                private ParsePosition    responsePos;
                private Thread           runThread;

                public static HttpParser Parse(TcpConnection messages)
                {
                        HttpParser parser = new HttpParser(messages);
                        parser.runThread = new Thread(new ThreadStart(parser.Run));
                        ThreadDebugger.Add(parser.runThread, "HttpParser.Run");
                        parser.runThread.Name = "HttpParser.Run";
                        parser.runThread.Start();

                        return parser;
                }

                public void NewMessageArived()
                {
                        requestPos.NewMessageEvent.Set();
                        responsePos.NewMessageEvent.Set();
                }

                public Thread RunThread
                {
                        get { return runThread; }
                }

                private HttpParser(TcpConnection messages)
                {
                        this.messages    = messages;
                        this.requestPos  = new ParsePosition(messages, nextMessageEvent);
                        this.responsePos = new ParsePosition(messages, null);
                        InitTables();
                }

                /// <summary>
                /// Try to recognize the stored TCP packets as sequence of HTTP messages (request-response)
                /// </summary>
                private void Run()
                {
                        Thread responseThread = null;

                        try
                        {
                                responseThread = new Thread(new ThreadStart(MatchResponses));
                                ThreadDebugger.Add(responseThread, "MatchResponses");
                                responseThread.Name = "HttpParser.MatchResponses";
                                responseThread.Start();

                                // find requests
                                while(!requestPos.IsEnd)
                                {
                                        HttpMessage http = new HttpMessage();
                                        lock(https)
                                        {
                                                https.Add(http);
                                                requestEvent.Set(); // new request available
                                        }

                                        messages.AddHttpMessage(http);
                                        SkipEmptyLines(requestPos);
                    http.RequestStartTimestamp = requestPos.CurrentMessage.Timestamp;

                                        ParseRequestLine(requestPos, http);
                                        http.UpdateHttpMessage();

                                        ParseHeaders(requestPos, http, true);
                                        SetRequestProperties(http);
                                        http.UpdateHttpMessage();

                                        ParseBody(requestPos, http, true);
                                        if("text" == http.RequestContentType && "xml" == http.RequestContentSubtype)
                                        {
                                                http.RequestXml = new XmlMessage(http.RequestText);
                                        }
                                        http.UpdateHttpMessage();

                                        http.RequestComplete = true;
                                        http.UpdateHttpMessage();

                                        SkipEmptyLines(requestPos);
                                }

                                responseThread.Join();
                        }
                        catch(Exception ex)
                        {
                                Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
                                if(responseThread != null) responseThread.Abort();
                        }
                }

                private void MatchResponses()
                {
                        try
                        {
                                IEnumerator httpEnum = https.GetEnumerator();

                                if(!nextMessageEvent.WaitOne()) throw new Exception("Cannot get first message of request");

                                responsePos.SetDirection(requestPos.CurrentMessage.Direction == TcpMessageDirection.Local
                                        ? TcpMessageDirection.Remote : TcpMessageDirection.Local);

                                while(!responsePos.IsEnd)
                                {
                                        bool moved;

                                        lock(https)
                                        {
                                                requestEvent.Reset();
                                                moved = httpEnum.MoveNext();
                                        }

                                        if(!moved)
                                        {
                                                if(!requestEvent.WaitOne())
                                                        throw new Exception("Cannot get next request");

                                                lock(https)
                                                {
                                                        if(!httpEnum.MoveNext())
                                                                throw new Exception("Tried to find response by no HTTP message available");
                                                }
                                        }

                                        HttpMessage http = (HttpMessage)httpEnum.Current;

                                        ParseResponseLine(responsePos, http);
                    http.ResponseStartTimestamp = responsePos.CurrentMessage.Timestamp;
                                        http.UpdateHttpMessage();

                                        ParseHeaders(responsePos, http, false);
                                        SetResponseProperties(http);
                                        http.UpdateHttpMessage();

                                        ParseBody(responsePos, http, false);
                                        if("text" == http.ResponseContentType && "xml" == http.ResponseContentSubtype)
                                        {
                                                http.ResponseXml = new XmlMessage(http.ResponseText);
                                        }
                                        http.UpdateHttpMessage();

                                        http.ResponseComplete = true;
                                        http.UpdateHttpMessage();
                                }
                        }
                        catch(Exception ex)
                        {
                                Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
                        }
                }

                private string GetToken(ParsePosition pos, int limit)
                {
                        StringBuilder res = new StringBuilder(100);
                        int           len = 0;

                        for(byte b = pos.NextOctet(); !pos.IsEnd && tokenChars[b]; b = pos.NextOctet())
                        {
                                res.Append(charValues[b]);
                                if(limit > 0 && limit < ++len) return null; // length limit
                        }
                        pos.Back();

                        return res.ToString();
                }

                private string GetUntilSpace(ParsePosition pos, int limit)
                {
                        StringBuilder res = new StringBuilder(1024);
                        int           len = 0;

                        for(byte b = pos.NextOctet(); !pos.IsEnd && b != 32 && b != 13 && b != 10; b = pos.NextOctet())
                        {                                     //     <space>    <cr>       <lf>
                                res.Append(charValues[b]);
                                if(limit > 0 && limit < ++len) return null; // length limit
                        }
                        pos.Back();

                        return res.ToString();
                }

                private string GetUntilEoL(ParsePosition pos, int limit)
                {
                        StringBuilder res = new StringBuilder(1024);
                        int           len = 0;

                        for(byte b = pos.NextOctet(); !pos.IsEnd && b != 13; b = pos.NextOctet())
                        {                                     //     <cr>
                                res.Append(charValues[b]);
                                if(limit > 0 && limit < ++len) return null; // length limit
                        }
                        pos.Back();

                        return res.ToString();
                }

                private void ExpectSpace(ParsePosition pos)
                {
                        if(pos.IsEnd || pos.NextOctet() != 32)
                                throw new HttpParseException("Space expected");
                }

                private void ExpectCRLF(ParsePosition pos)
                {
                        if(pos.IsEnd || pos.NextOctet() != 13)
                                throw new HttpParseException("Carriage return expected");
                        if(pos.IsEnd || pos.NextOctet() != 10)
                                throw new HttpParseException("Linefeed expected");
                }

                private void SkipEmptyLines(ParsePosition pos)
                {
                        while(pos.NextOctetAndBack() == 13)
                        {
                                ExpectCRLF(pos);
                        }
                }

                private void ParseRequestLine(ParsePosition pos, HttpMessage http)
                {
                        // method
                        http.RequestMethod = GetToken(pos, 1024);
                        if(http.RequestMethod == null || http.RequestMethod.Length == 0)
                                throw new HttpParseException("Request method name expected");
                        ExpectSpace(pos);

                        // URI
                        http.RequestUri = GetUntilSpace(pos, 1024);
                        if(http.RequestUri == null || http.RequestUri.Length == 0)
                                throw new HttpParseException("Request URI expected");

                        if(pos.IsEnd)
                                throw new HttpParseException("Unexpected end of message");

                        // EoL or version
                        byte b = pos.NextOctet();
                        if(b == 13)
                        {
                                pos.Back();
                                ExpectCRLF(pos);
                                http.RequestVersion = HttpVersion.V0_9;
                        }
                        else if(b != 32)
                        {
                                throw new HttpParseException("HTTP version expected");
                        }

                        // check version
                        string versionStr = GetUntilEoL(pos, 20);
                        if(pos.IsEnd || versionStr == null || versionStr.Length == 0)
                                throw new HttpParseException("HTTP version expected");

                        if(versionStr == "HTTP/1.0")
                        {
                                http.RequestVersion = HttpVersion.V1_0;
                        }
                        else if(versionStr == "HTTP/1.1")
                        {
                                http.RequestVersion = HttpVersion.V1_1;
                        }
                        else
                        {
                                throw new HttpParseException("Unknown HTTP version: " + versionStr);
                        }

                        ExpectCRLF(pos);
                }

                private void ParseHeaders(ParsePosition pos, HttpMessage http, bool request)
                {
                        if(pos.IsEnd) return;  // end of TCP messages

                        while(true)
                        {
                                if(pos.IsEnd)
                                        throw new HttpParseException("Unexpected end of message");

                                if(pos.NextOctetAndBack() == 13)
                                {
                                        ExpectCRLF(pos);
                                        return; // end of header, move to body
                                }
                                if(pos.IsEnd) return;  // end of TCP messages

                                string name = GetToken(pos, 0);
                                if(name == null || name.Length == 0)
                                        throw new HttpParseException("Request header name expected");

                                if(pos.IsEnd || pos.NextOctet() != 58)   // :
                                        throw new HttpParseException("Request header value expected");

                                string s = TrimHeaderValue(GetUntilEoL(pos, 0));

                                ExpectCRLF(pos);

                                if(request)
                                        http.AddRequestHeader(name, s);
                                else
                                        http.AddResponseHeader(name, s);
                        }
                }

                enum HeaderValueState
                {
                        Space,
                        Token,
                        Quoted
                }

                private string TrimHeaderValue(string s)
                {
                        if(s == null) return null;

                        HeaderValueState state = HeaderValueState.Space;
                        StringBuilder    buf   = new StringBuilder();

                        for(int i = 0, l = s.Length; i < l; i++)
                        {
                                char c = s[i];
                                switch(state)
                                {
                                        case HeaderValueState.Space:
                                                if(c != ' ' && c != '\t')
                                                {
                                                        if(c == '"')
                                                        {
                                                                if(buf.Length > 0) buf.Append(' ');
                                                                buf.Append(c);
                                                                state = HeaderValueState.Quoted;
                                                        }
                                                        else
                                                        {
                                                                if(buf.Length > 0) buf.Append(' ');
                                                                buf.Append(c);
                                                                state = HeaderValueState.Token;
                                                        }
                                                }
                                                break;

                                        case HeaderValueState.Token:
                                                if(c == ' ' || c == '\t')
                                                {
                                                        state = HeaderValueState.Space;
                                                }
                                                else if(c == '"')
                                                {
                                                        buf.Append(c);
                                                        state = HeaderValueState.Quoted;
                                                }
                                                else
                                                {
                                                        buf.Append(c);
                                                }
                                                break;

                                        case HeaderValueState.Quoted:
                                                if(c == '"')
                                                {
                                                        buf.Append(c);
                                                        i++;
                                                        if(i < l)
                                                        {
                                                                c = s[i];
                                                                if(c == ' ' || c == '\t')
                                                                {
                                                                        state = HeaderValueState.Space;
                                                                }
                                                                else if(c == '"')
                                                                {
                                                                        buf.Append(c);
                                                                        state = HeaderValueState.Quoted;
                                                                }
                                                                else
                                                                {
                                                                        buf.Append(c);
                                                                        state = HeaderValueState.Token;
                                                                }
                                                        }
                                                }
                                                else
                                                {
                                                        buf.Append(c);
                                                }
                                                break;
                                }
                        }

                        return buf.ToString();
                }

                private HttpTransferEncoding ParseTransferEncoding(string encoding)
                {
                        if(encoding == null) return HttpTransferEncoding.None;
                        encoding = encoding.ToLower();

                        if(encoding == "identity")
                                return HttpTransferEncoding.Identity;
                        else if(encoding == "chunked")
                                return HttpTransferEncoding.Chunked;
                        else if(encoding == "gzip")
                                return HttpTransferEncoding.Gzip;
                        else if(encoding == "compress")
                                return HttpTransferEncoding.Compress;
                        else if(encoding == "deflate")
                                return HttpTransferEncoding.Deflate;
                        else
                                return HttpTransferEncoding.Unknown;
                }

                private HttpContentEncoding ParseContentEncoding(string encoding)
                {
                        if(encoding == null) return HttpContentEncoding.None;
                        encoding = encoding.ToLower();

                        if(encoding == "identity")
                                return HttpContentEncoding.Identity;
                        else if(encoding == "gzip")
                                return HttpContentEncoding.Gzip;
                        else if(encoding == "compress")
                                return HttpContentEncoding.Compress;
                        else if(encoding == "deflate")
                                return HttpContentEncoding.Deflate;
                        else
                                return HttpContentEncoding.Unknown;
                }

                private void SetRequestProperties(HttpMessage http)
                {
                        // length
                        string contentLength = (string)http.RequestHeadersHash["content-length"];
                        if(contentLength != null)
                        {
                                http.RequestLength = int.Parse(contentLength);
                        }

                        // transfer encoding
                        http.RequestTransferEncoding = ParseTransferEncoding((string)http.RequestHeadersHash["transfer-encoding"]);

                        // content encoding
                        http.RequestContentEncoding = ParseContentEncoding((string)http.RequestHeadersHash["content-encoding"]);

                        // type & charset
                        string contentType = (string)http.RequestHeadersHash["content-type"];
                        if(contentType != null)
                        {
                                Match match = Regex.Match(contentType, @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)");
                                if(match.Success)
                                {
                                        http.RequestContentType    = match.Groups[1].Captures[0].Value;
                                        http.RequestContentSubtype = match.Groups[2].Captures[0].Value;
                                        if(match.Groups.Count >= 6) http.RequestCharset = match.Groups[5].Captures[0].Value.Trim('"');
                                }
                        }

                        // soap action
                        string soapAction = (string)http.RequestHeadersHash["soapaction"];
                        if(soapAction != null)
                        {
                                http.SoapAction = soapAction.Trim('"');
                        }
                }

                // RFC 2616: 4.3
                private bool WaitForBody(HttpMessage http, bool isRequest)
                {
                        if(isRequest)
                        {
                                return (http.RequestLength > 0) 
                                        || (http.RequestTransferEncoding != HttpTransferEncoding.None);
                        }
                        else 
                        {
                                if(http.RequestMethod == "HEAD") return false;
                                if(http.ResponseStatusCode <  200) return false;
                                if(http.ResponseStatusCode == 204) return false;
                                if(http.ResponseStatusCode == 304) return false;

                                return true;
                        }
                }

                private void ParseBody(ParsePosition pos, HttpMessage http, bool isRequest)
                {
                        if(!WaitForBody(http, isRequest)) return;

                        // FIXME parse and save on-the-fly, dont wait util end of message

                        byte[]               bin              = new byte[8*1024];
                        int                  len              = 0;   // current bin biffer length
                        int                  limit            = (isRequest ? http.RequestLength           : http.ResponseLength);
                        int                  chunkLen         = -1;  // current length of current chunk
                        int                  chunkLimit       = -1;
                        HttpTransferEncoding transferEncoding = (isRequest ? http.RequestTransferEncoding : http.ResponseTransferEncoding);
                        HttpContentEncoding  contentEncoding  = (isRequest ? http.RequestContentEncoding  : http.ResponseContentEncoding);
                        string               contentType      = (isRequest ? http.RequestContentType      : http.ResponseContentType);
                        string               contentSubtype   = (isRequest ? http.RequestContentSubtype   : http.ResponseContentSubtype);
                        string               charset          = (isRequest ? http.RequestCharset          : http.ResponseCharset);

                        for(byte b = pos.NextOctet(); !pos.IsEnd; b = pos.NextOctet())
                        {
                                // RFC 2616: 3.6.1
                                if(transferEncoding == HttpTransferEncoding.Chunked && chunkLimit < 0)
                                {
                                        // FIXME recognize chunk-extension here

                                        // get chunk length
                                        pos.Back();
                                        string chunkLimitStr = GetUntilEoL(pos, 40);
                                        if(pos.IsEnd || chunkLimitStr == null || chunkLimitStr.Length == 0)
                                                throw new HttpParseException("Chunk length expected");

                                        try
                                        {
                                                chunkLimit = Convert.ToInt32(chunkLimitStr, 16);
                                        }
                                        catch(Exception)
                                        {
                                                throw new HttpParseException("Cannot parse chunk length");
                                        }

                                        ExpectCRLF(pos);

                                        if(chunkLimit == 0) { // the end marker
                                                ExpectCRLF(pos);
                                                break;
                                        }

                                        chunkLen = 0;
                                        b        = pos.NextOctet();
                                }

                                // grow array if full
                                if(len >= bin.Length)
                                {
                                        byte[] newBin = new byte[bin.Length*2];
                                        Array.Copy(bin, newBin, len);
                                        bin = newBin;
                                }

                                bin[len++] = b;
                                chunkLen++;
                                if(chunkLimit > 0)
                                {
                                        if(chunkLen >= chunkLimit)  // full chunk
                                        {
                                                ExpectCRLF(pos);
                                                chunkLimit = -1; // try to find length of next chunk on next iteration
                                        }
                                }
                                else 
                                {
                                        if(limit > 0 && len >= limit)  // full length
                                        {
                                                break;
                                        }
                                }
                        }
                        if(transferEncoding == HttpTransferEncoding.Chunked && chunkLimit > 0 && chunkLen != chunkLimit)
                        {
                                throw new HttpParseException("Incomplete chunk found");
                        }

                        // FIXME parse entity-headers for chunked encoding here

                        string text = null;
                        if(contentEncoding == HttpContentEncoding.Identity && contentType == "text")
                        {
                                try
                                {
                                        Encoding enc = Encoding.GetEncoding(charset == null ? (contentSubtype == "xml" ? "UTF-8" : "ASCII") : charset);
                                        text = enc.GetString(bin, 0, len);
                                }
                                catch(NotSupportedException)
                                {
                                        Console.WriteLine("Unsupported charset: " + charset);
                                }
                        }

                        if(isRequest)
                        {
                                http.RequestLength = len;
                                http.RequestBody   = bin;
                                http.RequestText   = text;
                        }
                        else
                        {
                                http.ResponseLength = len;
                                http.ResponseBody   = bin;
                                http.ResponseText   = text;
                        }
                }

                private void ParseResponseLine(ParsePosition pos, HttpMessage http)
                {
                        // version
                        string versionStr = GetUntilSpace(pos, 20);
                        if(pos.IsEnd || versionStr == null || versionStr.Length == 0)
                                throw new HttpParseException("HTTP version expected");

                        if(versionStr == "HTTP/1.0")
                        {
                                http.ResponseVersion = HttpVersion.V1_0;
                        }
                        else if(versionStr == "HTTP/1.1")
                        {
                                http.ResponseVersion = HttpVersion.V1_1;
                        }
                        else
                        {
                                throw new HttpParseException("Unknown HTTP version: " + versionStr);
                        }
                        ExpectSpace(pos);

                        // status code
                        string code = GetToken(pos, 3);
                        if(code == null || code.Length != 3)
                                throw new HttpParseException("Status code expected");

                        try
                        {
                                int c = int.Parse(code);
                                if(c < 100 || c >= 1000) throw new HttpParseException("Status code expected");
                                http.ResponseStatusCode = c;
                        }
                        catch(FormatException)
                        {
                                throw new HttpParseException("Status code expected");
                        }
                        ExpectSpace(pos);

                        // status message
                        http.ResponseStatusMessage = GetUntilEoL(pos, 0);

                        if(pos.IsEnd)
                                throw new HttpParseException("Unexpected end of message");

                        ExpectCRLF(pos);
                }

                private void SetResponseProperties(HttpMessage http)
                {
                        // length
                        HttpHeader contentLength = (HttpHeader)http.ResponseHeadersHash["content-length"];
                        if(contentLength != null)
                        {
                                http.ResponseLength = int.Parse(contentLength.Values[0]);
                        }

                        // transfer encoding
                        HttpHeader transferEncoding = (HttpHeader)http.ResponseHeadersHash["transfer-encoding"];
                        http.ResponseTransferEncoding = ParseTransferEncoding((transferEncoding == null) ? null : transferEncoding.Values[0]);

                        // content encoding
                        HttpHeader contentEncoding = (HttpHeader)http.ResponseHeadersHash["content-encoding"];
                        http.ResponseContentEncoding = ParseContentEncoding((contentEncoding == null) ? null : contentEncoding.Values[0]);

                        // type & charset
                        HttpHeader contentType = (HttpHeader)http.ResponseHeadersHash["content-type"];
                        if(contentType != null)
                        {
                                Match match = Regex.Match(contentType.Values[0], @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)");
                                if(match.Success)
                                {
                                        http.ResponseContentType    = match.Groups[1].Captures[0].Value;
                                        http.ResponseContentSubtype = match.Groups[2].Captures[0].Value;
                                        if(match.Groups.Count >= 6 && match.Groups[5].Captures.Count > 0)
                                                http.ResponseCharset = match.Groups[5].Captures[0].Value.Trim('"');
                                }
                        }
                }
        }

        public class HttpHeader
        {
                private string   name;
                private string[] headerValues;

                public string Name
                {
                        get { return name; }
                }

                public string[] Values
                {
                        get { return headerValues; }
                }

                internal HttpHeader()
                {
                }

                internal HttpHeader(string name, string headerValue)
                {
                        this.name = name;
                        AddValue(headerValue);
                }

                internal void AddValue(string value)
                {
                        if(headerValues == null)
                        {
                                headerValues = new string[1];
                        }
                        else
                        {
                                string[] newValues = new string[headerValues.Length + 1];
                                Array.Copy(headerValues, 0, newValues, 0, headerValues.Length);
                                headerValues = newValues;
                        }

                        headerValues[headerValues.Length-1] = value;
                }
        }

        public class HttpMessage
        {
                private bool                 requestComplete         = false;
                private HttpVersion          requestVersion;
                private string               requestMethod;
                private string               requestUri;
                private LinkedList           requestHeaders          = new LinkedList();
                private Hashtable            requestHeadersHash      = new Hashtable();
                private int                  requestLength           = -1; // -1 == unknown
                private HttpTransferEncoding requestTransferEncoding = HttpTransferEncoding.None;
                private HttpContentEncoding  requestContentEncoding  = HttpContentEncoding.None;
                private string               requestContentType;
                private string               requestContentSubtype;
                private string               requestCharset;
                private string               soapAction;
                private byte[]               requestBody;
                private string               requestText;
                private XmlMessage           requestXml;
                private DateTime             requestStartTimestamp    = DateTime.MinValue;

                private bool                 responseComplete         = false;
                private HttpVersion          responseVersion;
                private int                  responseStatusCode;
                private string               responseStatusMessage;
                private LinkedList           responseHeaders          = new LinkedList();
                private Hashtable            responseHeadersHash      = new Hashtable();
                private int                  responseLength           = -1; // -1 == unknown
                private HttpTransferEncoding responseTransferEncoding = HttpTransferEncoding.None;
                private HttpContentEncoding  responseContentEncoding  = HttpContentEncoding.None;
                private string               responseContentType;
                private string               responseContentSubtype;
                private string               responseCharset;
                private byte[]               responseBody;
                private string               responseText;
                private XmlMessage           responseXml;
                private DateTime             responseStartTimestamp   = DateTime.MinValue;

                public bool RequestComplete
                {
                        get { return requestComplete; }
                        set { requestComplete = value; }
                }

                public HttpVersion RequestVersion
                {
                        get { return requestVersion; }
                        set { requestVersion = value; }
                }

                public string RequestMethod
                {
                        get { return requestMethod; }
                        set { requestMethod = value; }
                }

                public string RequestUri
                {
                        get { return requestUri; }
                        set { requestUri = value; }
                }

                public LinkedList RequestHeaders
                {
                        get { return requestHeaders; }
                }

                public IDictionary RequestHeadersHash
                {
                        get { return requestHeadersHash; }
                }

                public int RequestLength
                {
                        get { return requestLength; }
                        set { requestLength = value; }
                }

                public HttpTransferEncoding RequestTransferEncoding
                {
                        get { return requestTransferEncoding; }
                        set { requestTransferEncoding = value; }
                }

                public HttpContentEncoding RequestContentEncoding
                {
                        get { return requestContentEncoding; }
                        set { requestContentEncoding = value; }
                }

                public string RequestContentType
                {
                        get { return requestContentType; }
                        set { requestContentType = value; }
                }

                public string RequestContentSubtype
                {
                        get { return requestContentSubtype; }
                        set { requestContentSubtype = value; }
                }

                public string RequestCharset
                {
                        get { return requestCharset; }
                        set { requestCharset = value; }
                }

                public string SoapAction
                {
                        get { return soapAction; }
                        set { soapAction = value; }
                }

                public byte[] RequestBody
                {
                        get { return requestBody; }
                        set { requestBody = value; }
                }

                public string RequestText
                {
                        get { return requestText; }
                        set { requestText = value; }
                }

                public XmlMessage RequestXml
                {
                        get { return requestXml; }
                        set { requestXml = value; }
                }

        public DateTime RequestStartTimestamp
        {
                get { return requestStartTimestamp; }
                set { requestStartTimestamp = value; }
        }

                public bool ResponseComplete
                {
                        get { return responseComplete; }
                        set { responseComplete = value; }
                }

                public HttpVersion ResponseVersion
                {
                        get { return responseVersion; }
                        set { responseVersion = value; }
                }

                public int ResponseStatusCode
                {
                        get { return responseStatusCode; }
                        set { responseStatusCode = value; }
                }

                public string ResponseStatusMessage
                {
                        get { return responseStatusMessage; }
                        set { responseStatusMessage = value; }
                }

                public LinkedList ResponseHeaders
                {
                        get { return responseHeaders; }
                }

                public IDictionary ResponseHeadersHash
                {
                        get { return responseHeadersHash; }
                }

                public int ResponseLength
                {
                        get { return responseLength; }
                        set { responseLength = value; }
                }

                public HttpTransferEncoding ResponseTransferEncoding
                {
                        get { return responseTransferEncoding; }
                        set { responseTransferEncoding = value; }
                }

                public HttpContentEncoding ResponseContentEncoding
                {
                        get { return responseContentEncoding; }
                        set { responseContentEncoding = value; }
                }

                public string ResponseContentType
                {
                        get { return responseContentType; }
                        set { responseContentType = value; }
                }

                public string ResponseContentSubtype
                {
                        get { return responseContentSubtype; }
                        set { responseContentSubtype = value; }
                }

                public string ResponseCharset
                {
                        get { return responseCharset; }
                        set { responseCharset = value; }
                }

                public byte[] ResponseBody
                {
                        get { return responseBody; }
                        set { responseBody = value; }
                }

                public string ResponseText
                {
                        get { return responseText; }
                        set { responseText = value; }
                }

                public XmlMessage ResponseXml
                {
                        get { return responseXml; }
                        set { responseXml = value; }
                }

        public DateTime ResponseStartTimestamp
        {
                get { return responseStartTimestamp; }
                set { responseStartTimestamp = value; }
        }

                public void AddRequestHeader(string name, string headerValue)
                {
                        requestHeaders.Add(new HttpHeader(name, headerValue));
                        requestHeadersHash.Add(name.ToLower(), headerValue);
                }

                public void AddResponseHeader(string name, string headerValue)
                {
                        HttpHeader header = (HttpHeader)responseHeadersHash[name];
                        if(header == null)
                        {
                                header = new HttpHeader(name, headerValue);
                                responseHeaders.Add(header);
                                responseHeadersHash.Add(name.ToLower(), header);
                        }
                        else
                        {
                                header.AddValue(headerValue);
                        }
                }

                public event TcpEventHandler Update;

                protected virtual void OnUpdate(TcpEventArgs e)
                {
                        if(Update != null)
                        {
                                Update(this, e);
                        }
                }

                internal void UpdateHttpMessage()
                {
                        OnUpdate(new TcpEventArgs());
                }
        }

        internal class HttpParseException : Exception
        {
                public HttpParseException() : base()
                {
                }

                public HttpParseException(string message) : base(message)
                {
                }

                public HttpParseException(System.Runtime.Serialization.SerializationInfo info,
                        System.Runtime.Serialization.StreamingContext context) : base(info, context)
                {
                }

                public HttpParseException(string message, Exception innerException) : base(message, innerException)
                {
                }
        }

        public class XmlMessage
        {
                private XmlDocument xml;
                private XmlException parseException;

                public XmlDocument Xml
                {
                        get { return xml; }
                }

                public XmlException ParseException
                {
                        get { return parseException; }
                }

                internal XmlMessage(string text)
                {
                        try
                        {
                                this.xml = new XmlDocument();
                                this.xml.LoadXml(text);
                        }
                        catch(XmlException ex)
                        {
                                parseException = ex;
                        }
                }
        }

        public class TcpMessage
        {
                private TcpMessageDirection direction = TcpMessageDirection.None;
                private byte[]              bytes;
                private int                 length = 0;
                private DateTime            timestamp;

                public TcpMessageDirection Direction
                {
                        get { return direction; }
                        set { direction = value; }
                }

                public int Length
                {
                        get { return length; }
                }

                public byte[] Bytes
                {
                        get
                        {
                                return bytes;
                        }
                        set
                        {
                                length = 0;
                                Append(value);
                        }
                }

                public DateTime Timestamp
                {
                        get { return timestamp; }
                }

                internal void SetTimestamp(DateTime timestamp)
                {
                        this.timestamp = timestamp;
                }

                internal TcpMessage()
                {
                        this.timestamp = DateTime.Now;
                        this.bytes  = new byte[1024];
                }

                internal TcpMessage(byte[] bytes, int length)
                {
                        this.timestamp = DateTime.Now;
                        this.bytes     = new byte[length];
                        this.length   = length;
                        Array.Copy(this.bytes, bytes, length);
                }

                internal TcpMessage Append(byte[] newBytes)
                {
                        if(newBytes == null) return this;

                        return Append(newBytes, newBytes.Length);
                }

                internal TcpMessage Append(byte[] newBytes, int length)
                {
                        if(newBytes == null) return this;

                        lock(this)
                        {
                                // grow array
                                if(this.length + length > bytes.Length)
                                {
                                        int newLength = bytes.Length;
                                        while(this.length + length > newLength) newLength *= 2;
                                        byte[] newArray = new byte[newLength];

                                        Array.Copy(bytes, newArray, this.length);
                                        bytes = newArray;
                                }

                                // store received bytes
                                Array.Copy(newBytes, 0, bytes, this.length, length);
                                this.length += length;

                                return this;
                        }
                }
        }

        public class ThreadDebugger
        {
                private static ArrayList threads = new ArrayList();
                private static ArrayList asyncs  = new ArrayList();

                internal static void Add(Thread thread, string comment)
                {
                        threads.Add(new ThreadItem(thread, comment));
                        Console.WriteLine("ThreadDebugger: thread added {0}", comment);
                }

                internal static void Add(IAsyncResult async, string comment)
                {
                        asyncs.Add(new AsyncItem(async, comment));
                        Console.WriteLine("ThreadDebugger: async added ", comment);
                }

                public static void PrintStatus()
                {
                        Console.WriteLine("=== ThreadDebugger Status Begin ======");
                        Console.WriteLine("--- Threads --------------------------");
                        foreach(ThreadItem t in threads)
                        {
                                Console.WriteLine("{0} ({1}): {2}", t.thread.Name, t.comment, t.thread.IsAlive ? "alive" : "dead");
                        }
                        Console.WriteLine("--- Asyncs ---------------------------");
                        foreach(AsyncItem a in asyncs)
                        {
                                Console.WriteLine("{0}: {1}", a.comment, a.async.IsCompleted ? "alive" : "dead");
                        }
                        Console.WriteLine("=== ThreadDebugger Status End ========");
                }

                private class ThreadItem
                {
                        public Thread thread;
                        public string comment;

                        public ThreadItem(Thread thread, string comment)
                        {
                                this.thread  = thread;
                                this.comment = comment;
                        }
                }

                private class AsyncItem
                {
                        public IAsyncResult async;
                        public string       comment;

                        public AsyncItem(IAsyncResult async, string comment)
                        {
                                this.async   = async;
                                this.comment = comment;
                        }
                }
        }
}