// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

// #define DEBUG_TCP

///
// Microsoft Research, Cambridge
// 

using System;
using System.Collections;
using System.Diagnostics;
using System.Net.IP;

#if !SINGULARITY
using System.Net;
using System.Text;
#endif

using Drivers.Net;

using NetStack.Common;
using NetStack.Protocols;

namespace NetStack.Runtime
{
    ///
    // This module implements the TCP protocol
    // Notice: It is a simplified implementation (the very basic)
    // 
    public class TcpModule : IProtocol
    {
        // the ip handler
        protected IProtocol ip;

        // the TCP retransmit timeout
        protected int retransTimeout;

        // INetModule interfaces
        // ------------------------
        string INetModule.ModuleName    { get { return "TCP"; } }
        ushort INetModule.ModuleVersion { get { return 0x01; } }  // 0.1

        public bool StartModule()
        {
#if DEBUG_TCP
            Core.Log("Starting TCP module...\n");
#endif
            ip = Core.Instance().GetProtocolByName("IP");
            Debug.Assert(ip != null);
            return true;
        }

        public bool StopModule()
        {
            return true;
        }

        public bool DestroyModule()
        {
            return true;
        }

        // IProtocol interfaces
        // ------------------------
        public bool Initialize(ProtocolParams parameters)
        {
            Debug.Assert(parameters == null || parameters["name"] == "TCP");
            Core.Instance().RegisterProtocol(this);
            TcpSessionPool.SetTcpModule(this);
            return true;
        }

        [ Conditional("DEBUG_TCP") ]
        private static void DebugPrint(string format, params object [] args)
        {
            Core.Log(format, args);
        }

        public ushort GetProtocolID()
        {
            return ((ushort)IPFormat.Protocol.TCP);
        }

        Session IProtocol.CreateSession()
        {
            TcpSession tcp = (TcpSession) TcpSessionPool.Get();
            Core.Instance().RegisterSession(this, tcp);
            return tcp;
        }

        public TcpSession! ReInitializeSession(TcpSession! tcp)
        {
            tcp.ReInitialize(this);
            Core.Instance().RegisterSession(this, tcp);
            return tcp;
        }

        // handle incoming TCP packets
        public NetStatus OnProtocolReceive(NetPacket! packet)
        {
            // IP should have passed us a context which
            // is the IPHeader... lets see...
            IPFormat.IPHeader! ipHeader = (IPFormat.IPHeader!) packet.OverlapContext;

            // read the TCP header
            TcpFormat.TcpHeader tcpHeader;
            if (!TcpFormat.ReadTcpHeader(packet, out tcpHeader)) {
                DebugPrint("Bad TCP Header.  Packet rejected.");
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }

            // check validity
            if (tcpHeader.checksum != 0 &&
                TcpFormat.IsChecksumValid(ipHeader,tcpHeader, packet) == false)
            {
                DebugPrint("TCP checksum failed. Packet rejected.");
                return NetStatus.Code.PROTOCOL_DROP_CHKSUM;
            }

            // where is our data?
            // skip over options (that we currently ignore)
            int tcpHeaderSize = 4 * TcpFormat.GetOffset(ref tcpHeader);
            int startIndex = EthernetFormat.Size + IPFormat.Size + tcpHeaderSize;
            int segmentSize = ipHeader.totalLength - IPFormat.Size - tcpHeaderSize;
            packet.Clip(startIndex,segmentSize - 1);

            // find the relevant session...
            Session tcpSession = FindSession(ipHeader, ref tcpHeader);
            if (tcpSession == null) {
                DebugPrint("TCP Session not found.  TCP Packet dropped.");
            }
            else {
                // Deliver the packet to the session.  The tpcHeader is passed
                //  as a context and the ipHeader is passed as a
                //  packet.OverlapContext since we will not use it anymore
                //  (and TCP may need it).
                packet.OverlapContext = ipHeader;
                return tcpSession.OnReceive(this, packet, tcpHeader);
            }

            return NetStatus.Code.PROTOCOL_DROP_ERROR;
        }

        // Find the relevant session for this connection
        protected TcpSession FindSession(IPFormat.IPHeader! ipHeader,
                                         ref TcpFormat.TcpHeader tcpHeader)
        {
            // Get the session table
            ArrayList sessions = Core.Instance().GetSessions(this);
            if (sessions == null) {
                return null;
            }

            // First priority is a full match (Loc/Rem X Addr/Port all match),
            //  but next priority is a "passive" match (a Broadcast to "me").
            TcpSession passiveSession = null;  // this is the passive session

            lock (sessions.SyncRoot) {
                // BUGBUG: This should use HashTables (KeyedCollections  // TODO
                // BUGBUG:  if we get Generics) for Performance reasons. // TODO
                // BUGBUG:  We need one for full and one for passive.    // TODO
                // BUGBUG:  The full hashtable uses local and remote     // TODO
                // BUGBUG:  addresses as the keys and can be limited to  // TODO
                // BUGBUG:  sessions without Remote Broadcast addresses; // TODO
                // BUGBUG:  the passive hashtable is limited to sessions // TODO
                // BUGBUG:  with Remote Broadcast addresses and uses the // TODO
                // BUGBUG:  local address only as the key.               // TODO
                // BUGBUG: Note that this is done for every TCP Packet.  // TODO
                foreach (TcpSession! tcpSession in sessions) {
                    bool matchLocal =
                        (tcpSession.LocalAddress == ipHeader.Destination) &&
                        (tcpSession.LocalPort == tcpHeader.destPort);

                    // Both full and passive matches require full Local match.
                    if (matchLocal) {

                        // Check the Remote Match for a Full Match
                        bool matchRemote =
                            (tcpSession.RemoteAddress == ipHeader.Source) &&
                            (tcpSession.RemotePort == tcpHeader.sourcePort);

                        // If a full match (local && remote) return the session.
                        if (matchRemote) {
                            return tcpSession;
                        }

                        // Check for "Broadcast" address.
                        bool matchBroadcast =
                            (tcpSession.RemoteAddress == IPv4.Broadcast) &&
                            (tcpSession.RemotePort == 0);

                        // No full match, but save passiveSession
                        //  if matchLocal && matchBroadcast.
                        if (matchBroadcast) {
                            passiveSession = tcpSession;
                        }
                    }
                }
            }

            // If we didn't find an exact match, return
            //  the passive match, if any (may be null).
            return passiveSession;
        }

        // Send TCP packet, constructed by the caller, using the IP layer.
        public NetStatus OnProtocolSend(NetPacket! packet)
        {
            // if the ARP hasn't resolved the address yet, the
            // runtime will try this again (on the next handle
            // of outgoing sessions' queues)
            return ip.OnProtocolSend(packet);
        }

        public NetStatus SetProtocolSpecific(ushort opcode, byte[]! data)
        {
            return NetStatus.Code.PROTOCOL_OK;
        }

        public NetStatus GetProtocolSpecific(ushort opcode, out byte[] data)
        {
            data = null;
            return NetStatus.Code.PROTOCOL_OK;
        }

        public TcpModule()
        {
        }
    }
}
