///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Netstack / Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   UdpModule.cs
//
//  Note:
//

// #define DEBUG_UDP

using NetStack.Common;
using System;
using System.Collections;
using System.Diagnostics;

#if SINGULARITY
using Microsoft.Singularity;
#else
using System.Net;
#endif

using System.Net.IP;
using Drivers.Net;
using NetStack.Protocols;

namespace NetStack.Runtime
{
    public class UdpModule : IProtocol
    {
        protected IProtocol ip;

        public UdpModule()
        {
        }

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

        private bool started = false;
        public bool StartModule()
        {
            Debug.Assert(started == false);
            started = true;
            ip = Core.Instance().GetProtocolByName("IP");
            return ip != null;
        }

        public bool StopModule()
        {
            return true;
        }

        public bool DestroyModule()
        {
            return true;
        }

        public bool Initialize(ProtocolParams parameters)
        {
            Core.Instance().RegisterProtocol(this);
            return true;
        }

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

        public Session CreateSession()
        {
            UdpSession s = new UdpSession(this);
            Core.Instance().RegisterSession(this, s);
            return s;
        }

        public UdpSession CreateUnboundSession()
        {
            UdpSession s = new UdpSession(this);
            Core.Instance().RegisterSession(this, s);
            return s;
        }

        public UdpSession! CreateBoundSession(IPv4   localAddress,
                                              ushort localPort,
                                              IPv4   remoteAddress,
                                              ushort remotePort)
        {
            UdpSession s = new UdpSession(this);
            Core.Instance().RegisterSession(this, s);
            s.BindEndPoints(localAddress, localPort,
                            remoteAddress, remotePort);
            return s;
        }

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

        public NetStatus OnProtocolReceive(NetPacket! packet)
        {
            DebugPrint("Received UDP packet!\n");

            UdpFormat.UdpHeader udpHeader = new UdpFormat.UdpHeader();
            if (UdpFormat.ReadUdpHeader(packet, out udpHeader) == false) {
                DebugPrint("UDP session received unreadable packet.\n");
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }

            IPFormat.IPHeader! ipHeader = (IPFormat.IPHeader!) packet.OverlapContext;
            if (udpHeader.checksum != 0 &&
                UdpFormat.IsChecksumValid(ipHeader,udpHeader, packet) == false)
            {
                DebugPrint("UDP checksum failed.  NO cigar and NO packet!");
                return NetStatus.Code.PROTOCOL_DROP_CHKSUM;
            }

            packet.OverlapContext = ipHeader;

            ArrayList udpSessions = Core.Instance().GetSessions(this);
            if (udpSessions == null) {
                DebugPrint("No UDP Sessions\n");
                return NetStatus.Code.PROTOCOL_DROP_ERROR;
            }

            DebugPrint("Packet : Source {0}:{1} Destination {2}:{3}\n",
                       ipHeader.Source, udpHeader.srcPort,
                       ipHeader.Destination, udpHeader.dstPort);

            lock (udpSessions.SyncRoot) {
                foreach (UdpSession! u in udpSessions) {
                    DebugPrint("Session : Source {0}:{1} Destination {2}:{3}\n",
                               u.RemoteAddress, u.RemotePort,
                               u.LocalAddress, u.LocalPort);

                    // Match conditions
                    //
                    // Bound remote port is zero (not-specified)
                    // or matches exactly Bound remote IP is Any
                    // (not-specified) or matches exactly
                    if ((u.RemoteAddress == IPv4.Any ||
                         u.RemoteAddress == ipHeader.Source)     &&
                        (u.RemotePort == 0           ||
                         u.RemotePort == udpHeader.srcPort)      &&
                        (u.LocalAddress == IPv4.Any  ||
                         u.LocalAddress == ipHeader.Destination) &&
                        (u.LocalPort == udpHeader.dstPort))
                    {
                        DebugPrint("Delivered to UDP session\n");
                        return u.OnReceive(this, packet, udpHeader);
                    }
                }
            }

            return NetStatus.Code.PROTOCOL_DROP_ERROR;
        }

        public NetStatus OnProtocolSend(NetPacket! pkt)
        {
            return ip.OnProtocolSend(pkt);
        }

        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;
        }
    }
}
