/*
 * File      : pcap_netif.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2012, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2012-11-05     Bernard      the first version
 * 2012-11-13     Bernard      merge prife's patch for exclusive
 *                             access pcap driver.
 */

#ifdef _TIME_T_DEFINED
#undef _TIME_T_DEFINED
#endif

#ifdef _MSC_VER
/*
 * we do not want the warnings about the old deprecated and unsecure CRT functions
 * since these examples can be compiled under *nix as well
 */
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <pcap.h>

#include <rtthread.h>
#include <netif/ethernetif.h>

#define MAX_ADDR_LEN 6

#define NETIF_DEVICE(netif) ((struct pcap_netif*)(netif))
#define NETIF_PCAP(netif)   (NETIF_DEVICE(netif)->tap)

struct pcap_netif
{
	/* inherit from ethernet device */
	struct eth_device parent;

    pcap_t *tap;
	/* interface address info. */
	rt_uint8_t  dev_addr[MAX_ADDR_LEN];		/* hw address	*/
};
static struct pcap_netif pcap_netif_device;
static struct rt_semaphore sem_lock;
static rt_mailbox_t packet_mb = RT_NULL;

static void pcap_thread_entry(void* parameter)
{
    pcap_if_t *netif;
    pcap_t *tap;
    char errbuf[PCAP_ERRBUF_SIZE];
    struct pcap_pkthdr *header;
    const u_char *pkt_data;
    int res;

    netif = (pcap_if_t *) parameter;

    /* Open the adapter */
    if ((tap = pcap_open_live(netif->name,
        65536, // portion of the packet to capture. 
        1,     // promiscuous mode (nonzero means promiscuous)
        1,     // read timeout, 0 blocked, -1 no timeout
        errbuf )) == NULL)
    {
        rt_kprintf("Unable to open the adapter. %s is not supported by WinPcap\n", netif->name);
        return;
    }

    NETIF_PCAP(&pcap_netif_device) = tap;

    /* Read the packets */
    while (1)
    {
        struct eth_device* eth;
        struct pbuf *p;

        rt_enter_critical();
        res = pcap_next_ex(tap, &header, &pkt_data);
        rt_exit_critical();

        if (res == 0) continue;

        eth = (struct eth_device*) &pcap_netif_device;

        p = pbuf_alloc(PBUF_LINK, header->len, PBUF_RAM);
        pbuf_take(p, pkt_data, header->len);
        
        /* send to packet mailbox */
        rt_mb_send_wait(packet_mb, (rt_uint32_t)p, RT_WAITING_FOREVER);
        /* notify eth rx thread to receive packet */
        eth_device_ready(eth);
    }
}

static rt_err_t pcap_netif_init(rt_device_t dev)
{
    rt_thread_t tid;
    pcap_if_t *alldevs;
    pcap_if_t *d;
    pcap_t *tap;
    int inum, i=0;
    char errbuf[PCAP_ERRBUF_SIZE];

    /* Retrieve the device list */
    if(pcap_findalldevs(&alldevs, errbuf) == -1)
    {
        rt_kprintf("Error in pcap_findalldevs: %s\n", errbuf);
        return -RT_ERROR;
    }

    /* Print the list */
    for(d = alldevs; d; d = d->next)
    {
        rt_kprintf("%d. %s", ++i, d->name);
        if (d->description)
            rt_kprintf(" (%s)\n", d->description);
        else
            rt_kprintf(" (No description available)\n");
    }
    if(i == 0)
    {
        rt_kprintf("\nNo interfaces found! Make sure WinPcap is installed.\n");
        return -RT_ERROR;
    }

    inum = 1;
    /* Jump to the selected adapter */
    for(d = alldevs, i = 0; i < inum-1 ;d = d->next, i++);

    {
        rt_kprintf("Select (%s) as network interface\n", d->description);
        packet_mb = rt_mb_create("pcap", 64, RT_IPC_FLAG_FIFO);
        tid = rt_thread_create("pcap", pcap_thread_entry, d, 
            2048, RT_THREAD_PRIORITY_MAX - 1, 10);
        if (tid != RT_NULL)
        {
            rt_thread_startup(tid);
        }

        rt_thread_delay(100);
    }

    pcap_freealldevs(alldevs);

    return RT_EOK;
}

static rt_err_t pcap_netif_open(rt_device_t dev, rt_uint16_t oflag)
{
	return RT_EOK;
}

static rt_err_t pcap_netif_close(rt_device_t dev)
{
    pcap_t *tap;

    tap = NETIF_PCAP(dev);

    pcap_close(tap);
	return RT_EOK;
}

static rt_size_t pcap_netif_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)
{
	rt_set_errno(-RT_ENOSYS);
	return 0;
}

static rt_size_t pcap_netif_write (rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)
{
	rt_set_errno(-RT_ENOSYS);
	return 0;
}

static rt_err_t pcap_netif_control(rt_device_t dev, int cmd, void *args)
{
	switch (cmd)
	{
	case NIOCTL_GADDR:
		/* get mac address */
		if (args) rt_memcpy(args, pcap_netif_device.dev_addr, 6);
		else return -RT_ERROR;
		break;

	default :
		break;
	}

	return RT_EOK;
}

rt_err_t pcap_netif_tx( rt_device_t dev, struct pbuf* p)
{
	struct pbuf *q;
	rt_uint8_t *ptr;
    rt_uint8_t buf[2048];
    rt_err_t result = RT_EOK;
    pcap_t *tap;
    int res;

    tap = NETIF_PCAP(dev);

	/* lock EMAC device */
	rt_sem_take(&sem_lock, RT_WAITING_FOREVER);

	/* copy data to tx buffer */
	q = p;
	ptr = (rt_uint8_t*)buf;
	while (q)
	{
		memcpy(ptr, q->payload, q->len);
		ptr += q->len;
		q = q->next;
	}

    rt_enter_critical();
    res = pcap_sendpacket(tap, buf, p->tot_len);
    rt_exit_critical();

    if (res != 0)
    {
        rt_kprintf("Error sending the packet: \n", pcap_geterr(tap));
        result = -RT_ERROR;
    }

	/* unlock EMAC device */
	rt_sem_release(&sem_lock);

    return result;
}

struct pbuf *pcap_netif_rx(rt_device_t dev)
{
	struct pbuf* p = RT_NULL;

    rt_mb_recv(packet_mb, (rt_uint32_t*)&p, 0);

    return p;
}

void pcap_netif_hw_init(void)
{
	rt_sem_init(&sem_lock, "eth_lock", 1, RT_IPC_FLAG_FIFO);

	pcap_netif_device.dev_addr[0] = 0x00;
	pcap_netif_device.dev_addr[1] = 0x60;
	pcap_netif_device.dev_addr[2] = 0x37;
	/* set mac address: (only for test) */
	pcap_netif_device.dev_addr[3] = 0x12;
	pcap_netif_device.dev_addr[4] = 0x34;
	pcap_netif_device.dev_addr[5] = 0x56;

	pcap_netif_device.parent.parent.init		= pcap_netif_init;
	pcap_netif_device.parent.parent.open		= pcap_netif_open;
	pcap_netif_device.parent.parent.close		= pcap_netif_close;
	pcap_netif_device.parent.parent.read		= pcap_netif_read;
	pcap_netif_device.parent.parent.write		= pcap_netif_write;
	pcap_netif_device.parent.parent.control	    = pcap_netif_control;
	pcap_netif_device.parent.parent.user_data	= RT_NULL;

	pcap_netif_device.parent.eth_rx			= pcap_netif_rx;
	pcap_netif_device.parent.eth_tx			= pcap_netif_tx;

	eth_device_init(&(pcap_netif_device.parent), "e0");
}

#include <finsh.h>
void list_pcap(void)
{
    int i=0;
    pcap_if_t *alldevs;
    pcap_if_t *d;
    char errbuf[PCAP_ERRBUF_SIZE];

    /* Retrieve the device list */
    if(pcap_findalldevs(&alldevs, errbuf) == -1)
    {
        rt_kprintf("Error in pcap_findalldevs: %s\n", errbuf);
        return -RT_ERROR;
    }

    /* Print the list */
    for(d = alldevs; d; d = d->next)
    {
        rt_kprintf("%d. %s", ++i, d->name);
        if (d->description)
            rt_kprintf(" (%s)\n", d->description);
        else
            rt_kprintf(" (No description available)\n");
    }
    if(i == 0)
    {
        rt_kprintf("\nNo interfaces found! Make sure WinPcap is installed.\n");
        return -RT_ERROR;
    }

    pcap_freealldevs(alldevs);

    return ;
}
FINSH_FUNCTION_EXPORT(list_pcap, show host netif adapter);