/*
 *  TAP-Windows -- A kernel driver to provide virtual tap
 *                 device functionality on Windows.
 *
 *  This code was inspired by the CIPE-Win32 driver by Damion K. Wilson.
 *
 *  This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc.,
 *  and is released under the GPL version 2 (see below).
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program (see the file COPYING included with this
 *  distribution); if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "tap.h"

//-----------------
// DEBUGGING OUTPUT
//-----------------

const char *g_LastErrorFilename;
int g_LastErrorLineNumber;

#if DBG

DebugOutput g_Debug;

BOOLEAN
NewlineExists (const char *str, int len)
{
    while (len-- > 0)
    {
        const char c = *str++;
        if (c == '\n')
            return TRUE;
        else if (c == '\0')
            break;
    }
    return FALSE;
}

VOID
MyDebugInit (unsigned int bufsiz)
{
    NdisZeroMemory (&g_Debug, sizeof (g_Debug));
    g_Debug.text = (char *) MemAlloc (bufsiz, FALSE);

    if (g_Debug.text)
    {
        g_Debug.capacity = bufsiz;
    }
}

VOID
MyDebugFree ()
{
    if (g_Debug.text)
    {
        MemFree (g_Debug.text, g_Debug.capacity);
    }

    NdisZeroMemory (&g_Debug, sizeof (g_Debug));
}

VOID
MyDebugPrint (const unsigned char* format, ...)
{
    if (g_Debug.text && g_Debug.capacity > 0 && CAN_WE_PRINT)
    {
        BOOLEAN owned;
        ACQUIRE_MUTEX_ADAPTIVE (&g_Debug.lock, owned);
        if (owned)
        {
            const int remaining = (int)g_Debug.capacity - (int)g_Debug.out;

            if (remaining > 0)
            {
                va_list args;
                NTSTATUS status;
                char *end;

#ifdef DBG_PRINT
                va_start (args, format);
                vDbgPrintEx (DPFLTR_IHVNETWORK_ID, DPFLTR_INFO_LEVEL, format, args);
                va_end (args);
#endif
                va_start (args, format);
                status = RtlStringCchVPrintfExA (g_Debug.text + g_Debug.out,
                    remaining,
                    &end,
                    NULL,
                    STRSAFE_NO_TRUNCATION | STRSAFE_IGNORE_NULLS,
                    format,
                    args);
                va_end (args);
                va_start (args, format);
                vDbgPrintEx(DPFLTR_IHVDRIVER_ID , 1, format, args);
                va_end (args);
                if (status == STATUS_SUCCESS)
                    g_Debug.out = (unsigned int) (end - g_Debug.text);
                else
                    g_Debug.error = TRUE;
            }
            else
                g_Debug.error = TRUE;

            RELEASE_MUTEX (&g_Debug.lock);
        }
        else
            g_Debug.error = TRUE;
    }
}

BOOLEAN
GetDebugLine (
    __in char *buf,
    __in const int len
    )
{
    static const char *truncated = "[OUTPUT TRUNCATED]\n";
    BOOLEAN ret = FALSE;

    NdisZeroMemory (buf, len);

    if (g_Debug.text && g_Debug.capacity > 0)
    {
        BOOLEAN owned;
        ACQUIRE_MUTEX_ADAPTIVE (&g_Debug.lock, owned);
        if (owned)
        {
            int i = 0;

            if (g_Debug.error || NewlineExists (g_Debug.text + g_Debug.in, (int)g_Debug.out - (int)g_Debug.in))
            {
                while (i < (len - 1) && g_Debug.in < g_Debug.out)
                {
                    const char c = g_Debug.text[g_Debug.in++];
                    if (c == '\n')
                        break;
                    buf[i++] = c;
                }
                if (i < len)
                    buf[i] = '\0';
            }

            if (!i)
            {
                if (g_Debug.in == g_Debug.out)
                {
                    g_Debug.in = g_Debug.out = 0;
                    if (g_Debug.error)
                    {
                        const unsigned int tlen = strlen (truncated);
                        if (tlen < g_Debug.capacity)
                        {
                            NdisMoveMemory (g_Debug.text, truncated, tlen+1);
                            g_Debug.out = tlen;
                        }
                        g_Debug.error = FALSE;
                    }
                }
            }
            else
                ret = TRUE;

            RELEASE_MUTEX (&g_Debug.lock);
        }      
    }
    return ret;
}

VOID
PrMac (const MACADDR mac)
{
  DEBUGP (("%x:%x:%x:%x:%x:%x",
	    mac[0], mac[1], mac[2],
	    mac[3], mac[4], mac[5]));
}

VOID
PrIP (IPADDR ip_addr)
{
  const unsigned char *ip = (const unsigned char *) &ip_addr;

  DEBUGP (("%d.%d.%d.%d",
	    ip[0], ip[1], ip[2], ip[3]));
}

const char *
PrIPProto (int proto)
{
    switch (proto)
    {
    case IPPROTO_UDP:
        return "UDP";

    case IPPROTO_TCP:
        return "TCP";

    case IPPROTO_ICMP:
        return "ICMP";

    case IPPROTO_IGMP:
        return "IGMP";

    default:
        return "???";
    }
}

VOID
DumpARP (const char *prefix, const ARP_PACKET *arp)
{
  DEBUGP (("%s ARP src=", prefix));
  PrMac (arp->m_MAC_Source);
  DEBUGP ((" dest="));
  PrMac (arp->m_MAC_Destination);
  DEBUGP ((" OP=0x%04x",
	    (int)ntohs(arp->m_ARP_Operation)));
  DEBUGP ((" M=0x%04x(%d)",
	    (int)ntohs(arp->m_MAC_AddressType),
	    (int)arp->m_MAC_AddressSize));
  DEBUGP ((" P=0x%04x(%d)",
	    (int)ntohs(arp->m_PROTO_AddressType),
	    (int)arp->m_PROTO_AddressSize));

  DEBUGP ((" MacSrc="));
  PrMac (arp->m_ARP_MAC_Source);
  DEBUGP ((" MacDest="));
  PrMac (arp->m_ARP_MAC_Destination);

  DEBUGP ((" IPSrc="));
  PrIP (arp->m_ARP_IP_Source);
  DEBUGP ((" IPDest="));
  PrIP (arp->m_ARP_IP_Destination);

  DEBUGP (("\n"));
}

struct ethpayload
{
  ETH_HEADER eth;
  UCHAR payload[DEFAULT_PACKET_LOOKAHEAD];
};

#ifdef ALLOW_PACKET_DUMP

VOID
DumpPacket2(
    __in const char *prefix,
    __in const ETH_HEADER *eth,
    __in const unsigned char *data,
    __in unsigned int len
    )
{
    struct ethpayload *ep = (struct ethpayload *) MemAlloc (sizeof (struct ethpayload), TRUE);
    if (ep)
    {
        if (len > DEFAULT_PACKET_LOOKAHEAD)
            len = DEFAULT_PACKET_LOOKAHEAD;
        ep->eth = *eth;
        NdisMoveMemory (ep->payload, data, len);
        DumpPacket (prefix, (unsigned char *) ep, sizeof (ETH_HEADER) + len);
        MemFree (ep, sizeof (struct ethpayload));
    }
}

VOID
DumpPacket(
    __in const char *prefix,
    __in const unsigned char *data,
    __in unsigned int len
    )
{
    const ETH_HEADER *eth = (const ETH_HEADER *) data;
    const IPHDR *ip = (const IPHDR *) (data + sizeof (ETH_HEADER));

    if (len < sizeof (ETH_HEADER))
    {
        DEBUGP (("%s TRUNCATED PACKET LEN=%d\n", prefix, len));
        return;
    }

    // ARP Packet?
    if (len >= sizeof (ARP_PACKET) && eth->proto == htons (ETH_P_ARP))
    {
        DumpARP (prefix, (const ARP_PACKET *) data);
        return;
    }

    // IPv4 packet?
    if (len >= (sizeof (IPHDR) + sizeof (ETH_HEADER))
        && eth->proto == htons (ETH_P_IP)
        && IPH_GET_VER (ip->version_len) == 4)
    {
        const int hlen = IPH_GET_LEN (ip->version_len);
        const int blen = len - sizeof (ETH_HEADER);
        BOOLEAN did = FALSE;

        DEBUGP (("%s IPv4 %s[%d]", prefix, PrIPProto (ip->protocol), len));

        if (!(ntohs (ip->tot_len) == blen && hlen <= blen))
        {
            DEBUGP ((" XXX"));
            return;
        }

        // TCP packet?
        if (ip->protocol == IPPROTO_TCP
            && blen - hlen >= (sizeof (TCPHDR)))
        {
            const TCPHDR *tcp = (TCPHDR *) (data + sizeof (ETH_HEADER) + hlen);
            DEBUGP ((" "));
            PrIP (ip->saddr);
            DEBUGP ((":%d", ntohs (tcp->source)));
            DEBUGP ((" -> "));
            PrIP (ip->daddr);
            DEBUGP ((":%d", ntohs (tcp->dest)));
            did = TRUE;
        }

        // UDP packet?
        else if ((ntohs (ip->frag_off) & IP_OFFMASK) == 0
            && ip->protocol == IPPROTO_UDP
            && blen - hlen >= (sizeof (UDPHDR)))
        {
            const UDPHDR *udp = (UDPHDR *) (data + sizeof (ETH_HEADER) + hlen);

            // DHCP packet?
            if ((udp->dest == htons (BOOTPC_PORT) || udp->dest == htons (BOOTPS_PORT))
                && blen - hlen >= (sizeof (UDPHDR) + sizeof (DHCP)))
            {
                const DHCP *dhcp = (DHCP *) (data
                    + hlen
                    + sizeof (ETH_HEADER)
                    + sizeof (UDPHDR));

                int optlen = len
                    - sizeof (ETH_HEADER)
                    - hlen
                    - sizeof (UDPHDR)
                    - sizeof (DHCP);

                if (optlen < 0)
                    optlen = 0;

                DumpDHCP (eth, ip, udp, dhcp, optlen);
                did = TRUE;
            }

            if (!did)
            {
                DEBUGP ((" "));
                PrIP (ip->saddr);
                DEBUGP ((":%d", ntohs (udp->source)));
                DEBUGP ((" -> "));
                PrIP (ip->daddr);
                DEBUGP ((":%d", ntohs (udp->dest)));
                did = TRUE;
            }
        }

        if (!did)
        {
            DEBUGP ((" ipproto=%d ", ip->protocol));
            PrIP (ip->saddr);
            DEBUGP ((" -> "));
            PrIP (ip->daddr);
        }

        DEBUGP (("\n"));
        return;
    }

    {
        DEBUGP (("%s ??? src=", prefix));
        PrMac (eth->src);
        DEBUGP ((" dest="));
        PrMac (eth->dest);
        DEBUGP ((" proto=0x%04x len=%d\n",
            (int) ntohs(eth->proto),
            len));
    }
}

#endif // ALLOW_PACKET_DUMP

#endif