#include <stdio.h>
#include <pcap.h>
#include <sys/types.h>
#include <stdint.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <assert.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>

#include "explore-paquets-v6.h"

int
main(int argc, char *argv[])
{
    char           *filename, errbuf[PCAP_ERRBUF_SIZE];
    pcap_t         *handle;
    const uint8_t  *packet;     /* The actual packet */
    const uint8_t  *where_am_i; /* Cursor in packet */
    struct pcap_pkthdr header;  /* The header that pcap gives us */
    const struct sniff_ethernet *ethernet;      /* The ethernet header */
    const struct sniff_ip *ip;  /* The IP header */
    const struct sniff_eh *eh;  /* The IPv6 extension header, if present */
    const struct sniff_frag *frag;
    int             linktype;

    unsigned int    i, total, size_header;
    unsigned short  end_of_headers, fragmented, raw;
    uint8_t         next;
    unsigned int    udp, tcp, icmp, fragments, options, unknown;

    if (argc < 2) {
        fprintf(stderr, "Usage: readfile filename(s) \n");
        return (2);
    }
    total = udp = tcp = icmp = fragments = options = unknown = 0;
    for (i = 1; i < argc; i++) {
        filename = argv[i];
        handle = pcap_open_offline(filename, errbuf);
        if (handle == NULL) {
            fprintf(stderr, "Couldn't open file %s: %s\n", filename, errbuf);
            return (2);
        }
        linktype = pcap_datalink(handle);
        if (linktype == DLT_RAW) {
            raw = 1;
        } else if (linktype == DLT_EN10MB) {
            raw = 0;
        } else {
            fprintf(stderr, "Unknown link type %i in file %s\n", linktype, filename);
            return (2);
        }
        for (;;) {
            /* Grab a packet */
            packet = pcap_next(handle, &header);
            if (packet == NULL) {       /* End of file */
                break;
            }
            /* TODO: get packet length, header.len, for further testing */
            where_am_i = packet;
            if (!raw) {
                ethernet = (struct sniff_ethernet *) where_am_i;
                where_am_i = where_am_i + SIZE_ETHERNET;
                if (ntohs(ethernet->ether_type) != IPv6_ETHERTYPE) {
                    /* Ignore non-IPv6 packets */
                    continue;
                }
            }
            /* TODO: if (sizeof(struct sniff_ip)) > packet_length, stop, the
             * captured packet is too short */
            ip = (struct sniff_ip *) (where_am_i);
            if (!raw) {
                assert(IP_VERSION(ip) == 6);
            } else {
                if (IP_VERSION(ip) != 6) {
                    continue;
                }
            }
            total++;
            next = ip->ip_nxt;
            size_header = SIZE_IPv6;
            end_of_headers = 0;
            fragmented = 0;
            /* Skip the extension headers. Not easy since they do not all have the
             * same format. We have to hardwire the formats for those we know. One
             * unknown extension header and we miss the transport part * * of the
             * packet */
            while (!end_of_headers) {
                where_am_i = where_am_i + size_header;
                /* Extension headers defined in RFC 2460, section 4 */
                if (next == 0 ||
                    next == 43 || next == 50 || next == 51 || next == 60 ||
                    /* Extension header for SHIM6 (RFC 5533) */
                    next == 140) {
                    eh = (struct sniff_eh *) (where_am_i);
                    next = eh->eh_next;
                    size_header = eh->eh_length;
                    options++;
                }
                /* Fragment */
                else if (next == 44) {
                    fragmented = 1;
                    frag = (struct sniff_frag *) (where_am_i);
                    next = frag->frag_next;
                    size_header = SIZE_FRAGMENT_HDR;
                } else {
                    end_of_headers = 1;
                }
            }
            if (!fragmented || FRAG_OFFSET(frag) == 0) {
                if (next == UDP) {
                    udp++;
                } else if (next == TCP) {
                    tcp++;
                } else if (next == ICMP) {
                    icmp++;
                } else {
                    unknown++;
                }
            } else {
                fragments++;
            }
        }
        pcap_close(handle);
    }
    printf("%u IPv6 packets processed\n", total);
    printf("%u TCP packets found\n", tcp);
    printf("%u UDP packets found\n", udp);
    printf("%u ICMP packets found\n", icmp);
    printf("%u unknown packets found (other transports or new extension headers)\n",
           unknown);
    printf("%u non-initial fragments found\n", fragments);
    printf("%u options found (some packets may have several)\n", options);
    return (0);
}
