#include <stdio.h>
#include <string.h>
#include <core.h>
#include <sys/cpu.h>
#include <lwip/opt.h>		/* DNS_MAX_SERVERS */
#include "pxe.h"

char LocalDomain[256];

int over_load;
uint8_t uuid_type;
uint8_t uuid[16];

static void parse_dhcp_options(const void *, int, uint8_t);

static void subnet_mask(const void *data, int opt_len)
{
    if (opt_len != 4)
	return;
    IPInfo.netmask = *(const uint32_t *)data;
}

static void router(const void *data, int opt_len)
{
    if (opt_len != 4)
	return;
    IPInfo.gateway = *(const uint32_t *)data;
}

static void dns_servers(const void *data, int opt_len)
{
    const uint32_t *dp = data;
    int num = 0;

    while (num < DNS_MAX_SERVERS) {
	uint32_t ip;

	if (opt_len < 4)
	    break;

	opt_len -= 4;
	ip = *dp++;
	if (ip_ok(ip))
	    dns_server[num++] = ip;
    }
    while (num < DNS_MAX_SERVERS)
	dns_server[num++] = 0;
}

static void local_domain(const void *data, int opt_len)
{
    memcpy(LocalDomain, data, opt_len);
    LocalDomain[opt_len] = 0;
}

static void vendor_encaps(const void *data, int opt_len)
{
    /* Only recognize PXELINUX options */
    parse_dhcp_options(data, opt_len, 208);
}

static void option_overload(const void *data, int opt_len)
{
    if (opt_len != 1)
	return;
    over_load = *(uint8_t *)data;
}

static void server(const void *data, int opt_len)
{
    uint32_t ip;

    if (opt_len != 4)
	return;

    if (IPInfo.serverip)
        return;

    ip = *(uint32_t *)data;
    if (ip_ok(ip))
        IPInfo.serverip = ip;
}

static void client_identifier(const void *data, int opt_len)
{
    if (opt_len > MAC_MAX || opt_len < 2 ||
        MAC_len != (opt_len >> 8) ||
        *(uint8_t *)data != MAC_type)
        return;

    opt_len --;
    MAC_len = opt_len & 0xff;
    memcpy(MAC, data+1, opt_len);
    MAC[opt_len] = 0;
}

static void bootfile_name(const void *data, int opt_len)
{
    memcpy(boot_file, data, opt_len);
    boot_file[opt_len] = 0;
}

static void uuid_client_identifier(const void *data, int opt_len)
{
    int type = *(const uint8_t *)data;
    if (opt_len != 17 || type != 0 || have_uuid)
        return;

    have_uuid = true;
    uuid_type = type;
    memcpy(uuid, data+1, 16);
}

static void pxelinux_configfile(const void *data, int opt_len)
{
    DHCPMagic |= 2;
    memcpy(ConfigName, data, opt_len);
    ConfigName[opt_len] = 0;
}

static void pxelinux_pathprefix(const void *data, int opt_len)
{
    DHCPMagic |= 4;
    memcpy(path_prefix, data, opt_len);
    path_prefix[opt_len] = 0;
}

static void pxelinux_reboottime(const void *data, int opt_len)
{
    if (opt_len != 4)
        return;

    RebootTime = ntohl(*(const uint32_t *)data);
    DHCPMagic |= 8;     /* Got reboot time */
}


struct dhcp_options {
    int opt_num;
    void (*fun)(const void *, int);
};

static const struct dhcp_options dhcp_opts[] = {
    {1,   subnet_mask},
    {3,   router},
    {6,   dns_servers},
    {15,  local_domain},
    {43,  vendor_encaps},
    {52,  option_overload},
    {54,  server},
    {61,  client_identifier},
    {67,  bootfile_name},
    {97,  uuid_client_identifier},
    {209, pxelinux_configfile},
    {210, pxelinux_pathprefix},
    {211, pxelinux_reboottime}
};

/*
 * Parse a sequence of DHCP options, pointed to by _option_;
 * -- some DHCP servers leave option fields unterminated
 * in violation of the spec.
 *
 * filter  contains the minimum value for the option to recognize
 * -- this is used to restrict parsing to PXELINUX-specific options only.
 */
static void parse_dhcp_options(const void *option, int size, uint8_t opt_filter)
{
    int opt_num;
    int opt_len;
    const int opt_entries = sizeof(dhcp_opts) / sizeof(dhcp_opts[0]);
    int i = 0;
    const uint8_t *p = option;
    const struct dhcp_options *opt;

    /* The only 1-byte options are 00 and FF, neither of which matter */
    while (size >= 2) {
        opt_num = *p++;
	size--;

        if (opt_num == 0)
            continue;
        if (opt_num == 0xff)
            break;

        /* Anything else will have a length field */
        opt_len = *p++; /* c  <- option lenght */
        size -= opt_len + 1;
        if (size < 0)
            break;

	if (opt_num >= opt_filter) {
	    opt = dhcp_opts;
	    for (i = 0; i < opt_entries; i++) {
		if (opt_num == opt->opt_num) {
		    opt->fun(p, opt_len);
		    break;
		}
		opt++;
	    }
	}

        /* parse next */
        p += opt_len;
    }
}

/*
 * parse_dhcp
 *
 * Parse a DHCP packet.  This includes dealing with "overloaded"
 * option fields (see RFC 2132, section 9.3)
 *
 * This should fill in the following global variables, if the
 * information is present:
 *
 * MyIP		- client IP address
 * server_ip	- boot server IP address
 * net_mask	- network mask
 * gate_way	- default gateway router IP
 * boot_file	- boot file name
 * DNSServers	- DNS server IPs
 * LocalDomain	- Local domain name
 * MAC_len, MAC	- Client identifier, if MAC_len == 0
 *
 * This assumes the DHCP packet is in "trackbuf".
 *
 */
void parse_dhcp(int pkt_len)
{
    struct bootp_t *dhcp = (struct bootp_t *)trackbuf;
    int opt_len;

    IPInfo.ipv4 = 4;		/* This is IPv4 only for now... */

    over_load = 0;
    if (ip_ok(dhcp->yip))
        IPInfo.myip = dhcp->yip;

    if (ip_ok(dhcp->sip))
        IPInfo.serverip = dhcp->sip;

    opt_len = (char *)dhcp + pkt_len - (char *)&dhcp->options;
    if (opt_len && (dhcp->option_magic == BOOTP_OPTION_MAGIC))
        parse_dhcp_options(&dhcp->options, opt_len, 0);

    if (over_load & 1)
        parse_dhcp_options(&dhcp->bootfile, 128, 0);
    else if (dhcp->bootfile[0])
	strcpy(boot_file, dhcp->bootfile);

    if (over_load & 2)
        parse_dhcp_options(dhcp->sname, 64, 0);
}
