#include "stack-ndpv6.h"
#include "proto-preprocess.h"
#include "stack-src.h"
#include "util-checksum.h"
#include "rawsock-adapter.h"
#include "rawsock.h"
#include "logger.h"
#include <string.h>



static inline void _append(unsigned char *buf, size_t *r_offset, size_t max, unsigned x)
{
    if (*r_offset >= max)
        return;
    buf[(*r_offset)++] = (unsigned char)x;
}
static inline void _append_bytes(unsigned char *buf, size_t *r_offset, size_t max, const void *v_bytes, size_t len)
{
    const unsigned char *bytes = (const unsigned char *)v_bytes;
    if (*r_offset + len >= max)
        return;
    memcpy(buf + *r_offset, bytes, len);
    *r_offset += len;
}

static inline void
_append_short(unsigned char *buf, size_t *offset, size_t max, unsigned num)
{
    if (2 > max - *offset) {
        *offset = max;
        return;
    }
    buf[(*offset)++] = (unsigned char)(num>>8);
    buf[(*offset)++] = (unsigned char)(num & 0xFF);
}

static inline unsigned
_read_byte(const unsigned char *buf, size_t *offset, size_t max)
{
    if (*offset + 1 < max) {
        return buf[(*offset)++];
    } else
        return (unsigned)~0;
}
static inline unsigned
_read_short(const unsigned char *buf, size_t *offset, size_t max)
{
    if (*offset + 2  <= max) {
        unsigned result;
        result = buf[(*offset)++] << 8;
        result |= buf[(*offset)++];
        return result;
    } else
        return (unsigned)~0;
}

static inline unsigned
_read_number(const unsigned char *buf, size_t *offset, size_t max)
{
    if (*offset + 4  <= max) {
        unsigned result;
        result = buf[(*offset)++] << 24;
        result |= buf[(*offset)++] << 16;
        result |= buf[(*offset)++] << 8;
        result |= buf[(*offset)++];
        return result;
    } else
        return (unsigned)~0;
}

static inline ipv6address_t
_read_ipv6(const unsigned char *buf, size_t *offset, size_t max)
{
    ipv6address_t result = {0,0};
    
    if (*offset + 16 <= max) {
        result = ipv6address_from_bytes(buf + *offset);
        *offset += 16;
    } else {
        *offset = max;
    }
    return result;
}


/**
 * Handle the IPv6 Neighbor Solicitation request.
 * This happens after we've transmitted a packet, a response is on
 * it's way back, and the router needs to give us the response
 * packet. The router sends us a soliticiation, like an ARP request, 
 * to which we must respond.
 */
int
stack_ndpv6_incoming_request(struct stack_t *stack, struct PreprocessedInfo *parsed,  const unsigned char *px, size_t length)
{
    struct PacketBuffer *response = 0;
    size_t offset;
    size_t remaining;
    ipaddress target_ip;
    const unsigned char *target_ip_buf;
    macaddress_t target_mac = stack->source_mac;
    unsigned xsum;
    unsigned char *buf2;
    static const size_t max = sizeof(response->px);
    size_t offset_ip = parsed->ip_offset;
    size_t offset_ip_src = offset_ip + 8; /* offset in packet to the source IPv6 address */
    size_t offset_ip_dst = offset_ip + 24;
    size_t offset_icmpv6 = parsed->transport_offset;
    
    /* Verify it's a "Neighbor Solitication" opcode */
    if (parsed->opcode != 135)
        return -1;

    /* Make sure there's at least a full header */
    offset = parsed->transport_offset;
    remaining = length - offset;
    if (remaining < 24)
        return -1;

    /* Make sure it's looking for our own address */
    target_ip_buf = px + offset + 8;
    target_ip.version = 6;
    target_ip.ipv6 = ipv6address_from_bytes(target_ip_buf);
    if (!is_my_ip(stack->src, target_ip))
        return -1;

    /* Print a log message */
    {
        ipv6address_t a = ipv6address_from_bytes(px + offset_ip_src);
        ipaddress_formatted_t fmt1 = ipv6address_fmt(a);
        ipaddress_formatted_t fmt2 = ipaddress_fmt(target_ip);
        LOG(1, "[+] received NDP request from %s for %s\n", fmt1.string, fmt2.string);
    }
    
    /* Get a buffer for sending the response packet. This thread doesn't
     * send the packet itself. Instead, it formats a packet, then hands
     * that packet off to a transmit thread for later transmission. */
    response = stack_get_packetbuffer(stack);
    if (response == NULL)
        return -1; 


    /* Use the request packet as a template for the response */
    memcpy(response->px, px, length);
    buf2 = response->px;
    
    /* Set the destination MAC address and destination IPv6 adress*/
    memcpy(buf2 + 0, px + 6, 6);
    memcpy(buf2 + offset_ip_dst, px + offset_ip_src, 16);

    /* Set the source MAC address and source IPv6 address */
    memcpy(buf2 + offset_ip_src, target_ip_buf, 16);
    memcpy(buf2 + 6, target_mac.addr, 6);
    
    /* Format the response */
    _append(buf2, &offset, max, 136); /* type */
    _append(buf2, &offset, max, 0); /* code */
    _append(buf2, &offset, max, 0); /*checksum[hi] */
    _append(buf2, &offset, max, 0); /*checksum[lo] */
    _append(buf2, &offset, max, 0x60); /* flags*/ 
    _append(buf2, &offset, max, 0);
    _append(buf2, &offset, max, 0);
    _append(buf2, &offset, max, 0);
    _append_bytes(buf2, &offset, max, target_ip_buf, 16);
    _append(buf2, &offset, max, 2);
    _append(buf2, &offset, max, 1);
    _append_bytes(buf2, &offset, max, target_mac.addr, 6);

    xsum = checksum_ipv6(   buf2 + offset_ip_src, 
                            buf2 + offset_ip_dst, 
                            58,  
                            offset - offset_icmpv6, 
                            buf2 +offset_icmpv6);
    buf2[offset_icmpv6 + 2] = (unsigned char)(xsum >> 8);
    buf2[offset_icmpv6 + 3] = (unsigned char)(xsum >> 0);

    /* Transmit the packet-buffer */
    response->length = offset;
    stack_transmit_packetbuffer(stack, response);
    return 0;
}



static int 
_extract_router_advertisement(
    const unsigned char *buf, 
    size_t length,
    struct PreprocessedInfo *parsed,
    ipv6address my_ipv6,
    ipv6address *router_ip, 
    macaddress_t *router_mac)
{
    size_t offset;
    int is_same_prefix = 1;
    int is_mac_explicit = 0;

    if (parsed->ip_version != 6)
        return 1;
    
    *router_ip = parsed->src_ip.ipv6;

    if (parsed->ip_protocol != 58)
        return 1;
    offset = parsed->transport_offset;
    
    /* type = Router Advertisment */
    if (_read_byte(buf, &offset, length) != 134)
        return 1;

    /* code = 0 */
    if (_read_byte(buf, &offset, length) != 0)
        return 1;

    /* checksum */
    _read_short(buf, &offset, length);

    /* hop limit */
    _read_byte(buf, &offset, length);

    /* flags */
    _read_byte(buf, &offset, length);

    /* router life time */
    _read_short(buf, &offset, length);

    /* reachable time */
    _read_number(buf, &offset, length);

    /* retrans timer */
    _read_number(buf, &offset, length);

    while (offset + 8 <= length) {
        unsigned type = buf[offset + 0];
        size_t len2 = buf[offset + 1] * 8;
        size_t off2 = 2;
        const unsigned char *buf2 = buf + offset;

        switch (type) {
            case 3: /* prefix info */
            {
                unsigned prefix_len;
                ipv6address prefix;
                ipaddress_formatted_t fmt;
                
                prefix_len = _read_byte(buf2, &off2, len2);
                _read_byte(buf2, &off2, len2); /* flags */
                _read_number(buf2, &off2, len2); /* valid lifetime */
                _read_number(buf2, &off2, len2); /* preferred lifetime */
                _read_number(buf2, &off2, len2); /* reserved */
                prefix = _read_ipv6(buf2, &off2, len2);
                
                fmt = ipv6address_fmt(prefix);
                LOG(1, "[+] IPv6.prefix = %s/%u\n", fmt.string, prefix_len);
                if (ipv6address_is_equal_prefixed(my_ipv6, prefix, prefix_len)) {
                    is_same_prefix = 1;
                } else {
                    ipaddress_formatted_t fmt1 = ipv6address_fmt(my_ipv6);
                    ipaddress_formatted_t fmt2 = ipv6address_fmt(prefix);
                    
                    LOG(0, "[-] WARNING: our source-ip is %s, but router prefix announces %s/%u\n",
                            fmt1.string, fmt2.string, prefix_len);
                    is_same_prefix = 0;
                }

            }
                break;
            case 25: /* recursive DNS server */
                _read_short(buf2, &off2, len2);
                _read_number(buf2, &off2, len2);
                
                while (off2 + 16 <= len2) {
                    ipv6address resolver = _read_ipv6(buf2, &off2, len2);
                    ipaddress_formatted_t fmt = ipv6address_fmt(resolver);
                    LOG(1, "[+] IPv6.DNS = %s\n", fmt.string);
                }
                break;
            case 1:
                if (len2 == 8) {
                    memcpy(router_mac->addr, buf2 + 2, 6);
                    is_mac_explicit = 1;
                }
                break;
        }

        offset += len2;
    }

    if (!is_mac_explicit) {
        /* The router advertisement didn't include an explicit
         * source address. Therefore, pull the response from
         * the Etherent header of the packet instead */
        memcpy(router_mac->addr, parsed->mac_src, 6);
    }
    
    if (!is_same_prefix) {
        /* We had a valid router advertisement, but it didn't
         * match the IPv6 address we are using. This presumably
         * means there are multiple possible IPv6 routers on the
         * network. Therefore, we are going to discard this
         * packet and wait for another one */
        return 1;
    }
    
    return 0;
}

/****************************************************************************
 ****************************************************************************/
int
stack_ndpv6_resolve(struct Adapter *adapter,
    ipv6address my_ipv6,
    macaddress_t my_mac_address,
    macaddress_t *router_mac)
{
    unsigned char buf[128];
    size_t max = sizeof(buf);
    size_t offset = 0;
    unsigned i;
    time_t start;
    unsigned is_arp_notice_given = 0;
    int is_delay_reported = 0;
    size_t offset_ip;
    size_t offset_ip_src;
    size_t offset_ip_dst;
    size_t offset_icmpv6;
    unsigned xsum;
    struct PreprocessedInfo parsed = {0};

    /*
     * [KLUDGE]
     *  If this is a VPN connection, then there is no answer
     */
    if (stack_if_datalink(adapter) == 12) {
        memcpy(router_mac->addr, "\0\0\0\0\0\2", 6);
        return 0; /* success */
    }

    
    /*
     * Ethernet header
     */
    _append_bytes(buf, &offset, max, "\x33\x33\x00\x00\x00\x02", 6);
    _append_bytes(buf, &offset, max, my_mac_address.addr, 6);
    
    if (adapter->is_vlan) {
        _append_short(buf, &offset, max, 0x8100);
        _append_short(buf, &offset, max, adapter->vlan_id);
    }
    _append_short(buf, &offset, max, 0x86dd);

    /*
     * Create IPv6 header
     */
    offset_ip = offset;
    _append(buf, &offset, max, 0x60); /* version = 6 */
    _append(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0); /* length = 0 */
    _append(buf, &offset, max, 58); /* proto = ICMPv6 */
    _append(buf, &offset, max, 255); /*hop limit = 255 */

    /* Link local source address based on MAC address */
    offset_ip_src = offset;
    _append_short(buf, &offset, max, 0xfe80);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0);
    _append_bytes(buf, &offset, max, my_mac_address.addr, 3);
    buf[offset-3] |= 2;
    _append_short(buf, &offset, max, 0xfffe);
    _append_bytes(buf, &offset, max, my_mac_address.addr+3, 3);

    /* All-routers link local address */
    offset_ip_dst = offset;
    _append_short(buf, &offset, max, 0xff02);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 2);
    
    /* ICMPv6 Router Solicitation */
    offset_icmpv6 = offset;
    _append(buf, &offset, max, 133); /* type = Router Solicitation */
    _append(buf, &offset, max, 0);
    _append_short(buf, &offset, max, 0); /* checksum = 0 (for the moment) */
    _append_short(buf, &offset, max, 0); /* reserved */
    _append_short(buf, &offset, max, 0); /* reserved */
    _append(buf, &offset, max, 1); /* option = source link layer address */
    _append(buf, &offset, max, 1); /* length = 2 + 6 / 8*/
    _append_bytes(buf, &offset, max, my_mac_address.addr, 6);
    
    buf[offset_ip + 4] = (unsigned char)( (offset - offset_icmpv6) >> 8);
    buf[offset_ip + 5] = (unsigned char)( (offset - offset_icmpv6) & 0xFF);
    xsum = checksum_ipv6(   buf + offset_ip_src, 
                            buf + offset_ip_dst, 
                            58,  
                            offset - offset_icmpv6, 
                            buf + offset_icmpv6);
    buf[offset_icmpv6 + 2] = (unsigned char)(xsum >> 8);
    buf[offset_icmpv6 + 3] = (unsigned char)(xsum >> 0);
    rawsock_send_packet(adapter, buf, (unsigned)offset, 1);

    /*
     * Send a shorter version after the long version. I don't know
     * why, but some do this on the Internet.
     */
    offset -= 8;
    buf[offset_ip + 4] = (unsigned char)( (offset - offset_icmpv6) >> 8);
    buf[offset_ip + 5] = (unsigned char)( (offset - offset_icmpv6) & 0xFF);
    xsum = checksum_ipv6(   buf + offset_ip_src, 
                            buf + offset_ip_dst, 
                            58,  
                            offset - offset_icmpv6, 
                            buf + offset_icmpv6);
    buf[offset_icmpv6 + 2] = (unsigned char)(xsum >> 8);
    buf[offset_icmpv6 + 3] = (unsigned char)(xsum >> 0);
    rawsock_send_packet(adapter, buf, (unsigned)offset, 1);
    

    start = time(0);
    i = 0;
    for (;;) {
        unsigned length2;
        unsigned secs;
        unsigned usecs;
        const unsigned char *buf2;
        int err;
        ipv6address router_ip;

        /* Resend every so often */
        if (time(0) != start) {
            start = time(0);
            rawsock_send_packet(adapter, buf, (unsigned)offset, 1);
            if (i++ >= 10)
                break; /* timeout */

            /* It's taking too long, so notify the user */
            if (!is_delay_reported) {
                fprintf(stderr, "[ ] resolving IPv6 router MAC address (may take some time)...\n");
                is_delay_reported = 1;
            }
        }

        /* If we aren't getting a response back to our ARP, then print a
         * status message */
        if (time(0) > start+1 && !is_arp_notice_given) {
            fprintf(stderr, "[ ] resolving local IPv6 router\n");
            is_arp_notice_given = 1;
        }

        err =  rawsock_recv_packet(
                    adapter,
                    &length2,
                    &secs,
                    &usecs,
                    &buf2);

        if (err != 0)
            continue;

        /*
         * Parse the packet. We'll get lots of packets we aren't interested
         * in,so we'll just loop around and keep searching until we find
         * one.
         */
        err = preprocess_frame(buf2, length2, 1, &parsed);
        if (err != 1)
            continue;
        if (parsed.found != FOUND_NDPv6)
            continue;
        
        /* We've found a packet that may be the one we want, so parse it */
        err = _extract_router_advertisement(buf2, length2, &parsed, my_ipv6, &router_ip, router_mac);
        if (err)
            continue;
        
        /* The previus call found 'router_mac", so now return */
        return 0;
    }

    return 1;
}
