/*********************************************************************
   PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights reserved.
   See COPYING, LICENSE.GPLv2 and LICENSE.GPLv3 for usage.

   Authors: Daniele Lacamera
 ********************************************************************/

#include "pico_stack.h"
#include "pico_config.h"
#include "pico_device.h"
#include "pico_ipv4.h"
#include "pico_arp.h"
#include "pico_socket.h"
#include "pico_olsr.h"
#ifdef PICO_SUPPORT_OLSR
#define DGRAM_MAX_SIZE (100 - 28)
#define MAX_OLSR_MEM (4 * DGRAM_MAX_SIZE)

#ifdef DEBUG_OLSR
#define olsr_dbg dbg
#else
#define olsr_dbg(...) do {} while(0)
#endif

int OOM(void);


#define OLSR_HELLO_INTERVAL   ((uint32_t)5000)
#define OLSR_TC_INTERVAL      ((uint32_t)9000)
#define OLSR_MAXJITTER        ((uint32_t)(OLSR_HELLO_INTERVAL >> 2))
static const struct pico_ip4 HOST_NETMASK = {
    0xffffffff
};
#ifndef MIN
# define MIN(a, b) (a < b ? a : b)
#endif

#define fresher(a, b) ((a > b) || ((b - a) > 32768))


static uint16_t msg_counter; /* Global message sequence number */

/* Objects */
struct olsr_dev_entry
{
    struct olsr_dev_entry *next;
    struct pico_device *dev;
    uint16_t pkt_counter;
};


/* OLSR Protocol */
#define OLSRMSG_HELLO   0xc9
#define OLSRMSG_MID    0x03
#define OLSRMSG_TC    0xca

#define OLSRLINK_SYMMETRIC 0x06
#define OLSRLINK_UNKNOWN 0x08
#define OLSRLINK_MPR  0x0a


#define OLSR_PORT (short_be((uint16_t)698))


/* Headers */

PACKED_STRUCT_DEF olsr_link
{
    uint8_t link_code;
    uint8_t reserved;
    uint16_t link_msg_size;
};

PACKED_STRUCT_DEF olsr_neighbor
{
    uint32_t addr;
    uint8_t lq;
    uint8_t nlq;
    uint16_t reserved;
};

PACKED_STRUCT_DEF olsr_hmsg_hello
{
    uint16_t reserved;
    uint8_t htime;
    uint8_t willingness;
};

PACKED_STRUCT_DEF olsr_hmsg_tc
{
    uint16_t ansn;
    uint16_t reserved;
};


PACKED_STRUCT_DEF olsrmsg
{
    uint8_t type;
    uint8_t vtime;
    uint16_t size;
    struct pico_ip4 orig;
    uint8_t ttl;
    uint8_t hop;
    uint16_t seq;
};

PACKED_STRUCT_DEF olsrhdr
{
    uint16_t len;
    uint16_t seq;
};



/* Globals */
static struct pico_socket *udpsock = NULL;
uint16_t my_ansn = 0;
static struct olsr_route_entry  *Local_interfaces = NULL;
static struct olsr_dev_entry    *Local_devices    = NULL;

static struct olsr_dev_entry *olsr_get_deventry(struct pico_device *dev)
{
    struct olsr_dev_entry *cur = Local_devices;
    while(cur) {
        if (cur->dev == dev)
            return cur;

        cur = cur->next;
    }
    return NULL;
}

struct olsr_route_entry *olsr_get_ethentry(struct pico_device *vif)
{
    struct olsr_route_entry *cur = Local_interfaces;
    while(cur) {
        if (cur->iface == vif)
            return cur;

        cur = cur->next;
    }
    return NULL;
}

static struct olsr_route_entry *get_next_hop(struct olsr_route_entry *dst)
{
    struct olsr_route_entry *hop = dst;
    while(hop) {
        /* olsr_dbg("Finding next hop to %08x m=%d\n", hop->destination.addr, hop->metric); */
        if(hop->metric <= 1)
            return hop;

        hop = hop->gateway;
    }
    return NULL;
}

static inline void olsr_route_add(struct olsr_route_entry *el)
{
    /* char dest[16],nxdest[16]; */
    struct olsr_route_entry *nexthop;

    if(!el)
        return;

    my_ansn++;

    nexthop = get_next_hop(el);
    if (el->gateway && nexthop && (nexthop->destination.addr != el->destination.addr)) {
        /* 2-hops route or more */
        el->next = el->gateway->children;
        el->gateway->children = el;
        el->link_type = OLSRLINK_MPR;
        olsr_dbg("[OLSR]  ----------Adding route to %08x via %08x metric %d\n", el->destination.addr, nexthop->destination.addr, el->metric);
        pico_ipv4_route_add(el->destination, HOST_NETMASK, nexthop->destination, (int) el->metric, NULL);
    } else if (el->iface) {
        /* neighbor */
        struct olsr_route_entry *ei = olsr_get_ethentry(el->iface);
        struct pico_ip4 no_gw = {
            0U
        };
        if (el->link_type == OLSRLINK_UNKNOWN)
            el->link_type = OLSRLINK_SYMMETRIC;

        if (ei) {
            el->next = ei->children;
            ei->children = el;
        }

        olsr_dbg("[OLSR]  ----------Adding neighbor %08x iface %s\n", el->destination.addr, el->iface->name);

        pico_ipv4_route_add(el->destination, HOST_NETMASK, no_gw, 1, pico_ipv4_link_by_dev(el->iface));
    }
}

static inline void olsr_route_del(struct olsr_route_entry *r)
{
    struct olsr_route_entry *cur, *prev = NULL, *lst;
    /* olsr_dbg("[OLSR] DELETING route..................\n"); */
    my_ansn++;
    if (r->gateway) {
        lst = r->gateway->children;
    } else if (r->iface) {
        lst = olsr_get_ethentry(r->iface);
    } else {
        lst = Local_interfaces;
    }

    cur = lst, prev = NULL;
    while(cur) {
        if (cur == r) {
            /* found */
            if (r->gateway) {
                pico_ipv4_route_del(r->destination, HOST_NETMASK, r->metric);
                olsr_dbg("[OLSR] Deleting route to %08x \n", r->destination.addr);
                if (!prev)
                    r->gateway->children = r->next;
                else
                    prev->next = r->next;
            }

            while (r->children) {
                olsr_route_del(r->children);
                /* Orphans must die. */
                /* PICO_FREE(r->children); */
            }
            return;
        }

        prev = cur;
        cur = cur->next;
    }
}

static struct olsr_route_entry *get_route_by_address(struct olsr_route_entry *lst, uint32_t ip)
{
    struct olsr_route_entry *found;
    if(lst) {
        if (lst->destination.addr == ip) {
            return lst;
        }

        /* recursive function, could be dangerous for stack overflow if a lot of routes are available... */
        found = get_route_by_address(lst->children, ip);
        if (found)
            return found;

        found = get_route_by_address(lst->next, ip);
        if (found)
            return found;
    }

    return NULL;
}

#define OLSR_C_SHIFT (uint32_t)4 /* 1/16 */
#define DEFAULT_VTIME 288UL

static uint8_t seconds2olsr(uint32_t seconds)
{
    uint16_t a, b;
    /* olsr_dbg("seconds=%u\n", (uint16_t)seconds); */

    if (seconds > 32767)
        seconds = 32767;

    /* find largest b such as seconds/C >= 2^b */
    for (b = 1; b <= 0x0fu; b++) {
        if ((uint16_t)(seconds * 16u) < (1u << b)) {
            b--;
            break;
        }
    }
    /* olsr_dbg("b=%u", b); */
    /* compute the expression 16*(T/(C*(2^b))-1), which may not be a
       integer, and round it up.  This results in the value for 'a' */
    /* a = (T / ( C * (1u << b) ) ) - 1u; */
    {
        uint16_t den = ((uint16_t)(1u << b) >> 4u);
        /* olsr_dbg(" den=%u ", den); */
        if (den == 0)
        {
            /* olsr_dbg("div by 0!\n"); */
            den = 1u;
        }

        a = (uint16_t)(((uint16_t)seconds / den) - (uint16_t)1);
    }
    /* a = a & 0x0Fu; */

    /* olsr_dbg(" a=%u\n", a); */

    /* if 'a' is equal to 16: increment 'b' by one, and set 'a' to 0 */
    if (16u == a) {
        b++;
        a = 0u;
    }

    return (uint8_t)((a << 4u) + b);
}

static uint32_t olsr2seconds(uint8_t olsr)
{
    uint8_t a, b;
    uint16_t seconds;
    /* olsr_dbg("olsr format: %u -- ", olsr); */
    a = (olsr >> 4) & 0xFu;
    b = olsr & 0x0f;
    /* olsr_dbg("o2s: a=%u, b=%u\n", a,b); */
    if (b < 4)
        seconds = (uint16_t)(((1u << b) + (uint16_t)(((uint16_t)(a << b) >> 4u) & 0xFu)) >> OLSR_C_SHIFT);
    else
        seconds = (uint16_t)(((1u << b) + (uint16_t)(((uint16_t)(a << (b - 4))) & 0xFu)) >> OLSR_C_SHIFT);

    /* olsr_dbg("o2s: seconds: %u\n", seconds); */
    return seconds;
}

static void olsr_garbage_collector(struct olsr_route_entry *sublist)
{
    if(!sublist)
        return;

    if (sublist->time_left <= 0) {
        olsr_route_del(sublist);
        PICO_FREE(sublist);
        return;
    } else {
        /* sublist->time_left -= 2u; */
        sublist->time_left -= 8u;
    }

    olsr_garbage_collector(sublist->children);
    olsr_garbage_collector(sublist->next);
}

struct olsr_fwd_pkt
{
    void *buf;
    uint16_t len;
    struct pico_device *pdev;
};

static uint32_t buffer_mem_used = 0U;

static void olsr_process_out(pico_time now, void *arg)
{
    struct olsr_fwd_pkt *p = (struct olsr_fwd_pkt *)arg;
    struct pico_ip4 bcast;
    struct pico_ipv4_link *addr;
    struct olsr_dev_entry *pdev = Local_devices;
    struct olsrhdr *ohdr;
    (void)now;

    /* Send the thing out */
    ohdr = (struct olsrhdr *)p->buf;
    ohdr->len = short_be((uint16_t)p->len);

    if (p->pdev) {
        struct olsr_dev_entry *odev = olsr_get_deventry(p->pdev);
        if (!odev) {
            goto out_free;
        }

        addr = pico_ipv4_link_by_dev(p->pdev);
        if (!addr)
            goto out_free;

        ohdr->seq = short_be((uint16_t)(odev->pkt_counter)++);
        if (addr->address.addr)
            bcast.addr = (addr->netmask.addr & addr->address.addr) | (~addr->netmask.addr);
        else
            bcast.addr = 0xFFFFFFFFu;

        if ( 0 > pico_socket_sendto(udpsock, p->buf, p->len, &bcast, OLSR_PORT)) {
            olsr_dbg("olsr send\n");
        }
    } else {
        while(pdev) {
            ohdr->seq = short_be((uint16_t)(pdev->pkt_counter++));
            addr = pico_ipv4_link_by_dev(pdev->dev);
            if (!addr)
                continue;

            if (addr->address.addr)
                bcast.addr = (addr->netmask.addr & addr->address.addr) | (~addr->netmask.addr);
            else
                bcast.addr = 0xFFFFFFFFu;

            if ( 0 > pico_socket_sendto(udpsock, p->buf, p->len, &bcast, OLSR_PORT)) {
                olsr_dbg("olsr send\n");
            }

            pdev = pdev->next;
        }
    }

out_free:
    PICO_FREE(p->buf); /* XXX <-- broken? */
    buffer_mem_used -= DGRAM_MAX_SIZE;
    PICO_FREE(p);
}

static void olsr_scheduled_output(uint32_t when, void *buffer, uint16_t size, struct pico_device *pdev)
{
    struct olsr_fwd_pkt *p;
    /* olsr_dbg("Scheduling olsr packet, type:%s, size: %x\n", when == OLSR_HELLO_INTERVAL?"HELLO":"TC", size); */
    if ((buffer_mem_used + DGRAM_MAX_SIZE) > MAX_OLSR_MEM) {
        PICO_FREE(buffer);
        return;
    }

    p = PICO_ZALLOC(sizeof(struct olsr_fwd_pkt));
    if (!p) {
        OOM();
        PICO_FREE(buffer);
        return;
    }

    p->buf = buffer;
    p->len = size;
    p->pdev = pdev;
    buffer_mem_used += DGRAM_MAX_SIZE;
    if (!pico_timer_add(1 + when - ((pico_rand() % OLSR_MAXJITTER)), &olsr_process_out, p)) {
        olsr_dbg("OLSR: Failed to start process timer\n");
        OOM();
        PICO_FREE(p);
        PICO_FREE(buffer);
    }
}


static void refresh_routes(void)
{
    struct olsr_route_entry *local;
    struct olsr_dev_entry *icur = Local_devices;

    /* Refresh local entries */
    /* Step 1: set zero expire time for local addresses and neighbors*/
    local = Local_interfaces;
    while(local) {
        local = local->next;
    }
    /* Step 2: refresh timer for entries that are still valid.
     * Add new entries.
     */
    while(icur) {
        struct pico_ipv4_link *lnk = NULL;
        do {
            lnk = pico_ipv4_link_by_dev_next(icur->dev, lnk);
            if (!lnk) break;

            local = olsr_get_ethentry(icur->dev);
            if (local) {
                local->time_left = (OLSR_HELLO_INTERVAL << 2);
            } else if (lnk) {
                struct olsr_route_entry *e = PICO_ZALLOC(sizeof (struct olsr_route_entry));
                if (!e) {
                    olsr_dbg("olsr: adding local route entry\n");
                    OOM();
                    return;
                }

                e->destination.addr = lnk->address.addr; /* Always pick the first address */
                e->time_left = (OLSR_HELLO_INTERVAL << 2);
                e->iface = icur->dev;
                e->metric = 0;
                e->lq = 0xFF;
                e->nlq = 0xFF;
                e->next = Local_interfaces;
                Local_interfaces = e;
            }
        } while (lnk);

        /* disabled if device type != eth */
        /* refresh_neighbors(icur->dev); */
        icur = icur->next;
    }
}

static uint32_t olsr_build_hello_neighbors(uint8_t *buf, uint32_t size, struct olsr_route_entry **bookmark)
{
    uint32_t ret = 0;
    struct olsr_route_entry *local, *neighbor;
    struct olsr_neighbor *dst = (struct olsr_neighbor *) buf;
    uint32_t total_link_size = sizeof(struct olsr_neighbor) + sizeof(struct olsr_link);
    local = Local_interfaces;
    while (local) {
        neighbor = local->children;
        if (*bookmark) {
            while ((neighbor) && *bookmark != neighbor)
                neighbor = neighbor->next;
        }

        while (neighbor) {
            struct olsr_link *li = (struct olsr_link *) (buf + ret);

            if ((size - ret) < total_link_size) {
                /* Incomplete list, new datagram needed. */
                *bookmark = neighbor;
                return ret;
            }

            li->link_code = neighbor->link_type;
            li->reserved = 0;
            li->link_msg_size = short_be((uint16_t)total_link_size);
            ret += (uint32_t)sizeof(struct olsr_link);
            dst = (struct olsr_neighbor *) (buf + ret);
            dst->addr = neighbor->destination.addr;
            dst->nlq = neighbor->nlq;
            dst->lq = neighbor->lq;
            dst->reserved = 0;
            ret += (uint32_t)sizeof(struct olsr_neighbor);
            neighbor = neighbor->next;
        }
        local = local->next;
    }
    *bookmark = NULL; /* All the list was visited, no more dgrams needed */
    return ret;
}

static uint32_t olsr_build_tc_neighbors(uint8_t *buf, uint32_t size, struct olsr_route_entry **bookmark)
{
    uint32_t ret = 0;
    struct olsr_route_entry *local, *neighbor;
    struct olsr_neighbor *dst = (struct olsr_neighbor *) buf;
    local = Local_interfaces;
    while (local) {
        neighbor = local->children;
        if (*bookmark) {
            while ((neighbor) && *bookmark != neighbor)
                neighbor = neighbor->next;
        }

        while (neighbor) {
            if (size - ret < sizeof(struct olsr_neighbor)) {
                /* Incomplete list, new datagram needed. */
                *bookmark = neighbor;
                return ret;
            }

            dst->addr = neighbor->destination.addr;
            dst->nlq = neighbor->nlq;
            dst->lq = neighbor->lq;
            dst->reserved = 0;
            ret += (uint32_t)sizeof(struct olsr_neighbor);
            dst = (struct olsr_neighbor *) (buf + ret);
            neighbor = neighbor->next;
        }
        local = local->next;
    }
    *bookmark = NULL; /* All the list was visited, no more dgrams needed */
    return ret;
}

static uint32_t olsr_build_mid(uint8_t *buf, uint32_t size, struct pico_device *excluded)
{
    uint32_t ret = 0;
    struct olsr_route_entry *local;
    struct pico_ip4 *dst = (struct pico_ip4 *) buf;
    local = Local_interfaces;
    while (local) {
        if (local->iface != excluded) {
            dst->addr = local->destination.addr;
            ret += (uint32_t)sizeof(uint32_t);
            dst = (struct pico_ip4 *) (buf + ret);
            if (ret >= size)
                return (uint32_t)(ret - sizeof(uint32_t));
        }

        local = local->next;
    }
    return ret;
}


static void olsr_compose_tc_dgram(struct pico_device *pdev, struct pico_ipv4_link *ep)
{
    struct olsrmsg *msg_tc, *msg_mid;
    uint32_t size = 0, r;
    struct olsr_route_entry *last_neighbor = NULL;
    uint8_t *dgram;
    struct olsr_hmsg_tc *tc;
    do {
        dgram = PICO_ZALLOC(DGRAM_MAX_SIZE);
        if (!dgram) {
            OOM();
            return;
        }

        size = (uint32_t)sizeof(struct olsrhdr);
        ep = pico_ipv4_link_by_dev(pdev);
        if (!ep) {
            PICO_FREE(dgram);
            return;
        }


        if (!last_neighbor) {
            /* MID Message */

            msg_mid = (struct olsrmsg *)(dgram + size);
            size += (uint32_t)sizeof(struct olsrmsg);
            msg_mid->type = OLSRMSG_MID;
            msg_mid->vtime = seconds2olsr(60);
            msg_mid->orig.addr = ep->address.addr;
            msg_mid->ttl = 0xFF;
            msg_mid->hop = 0;
            msg_mid->seq = short_be(msg_counter++);
            r = olsr_build_mid(dgram + size, DGRAM_MAX_SIZE - size, pdev);
            if (r == 0) {
                size -= (uint32_t)sizeof(struct olsrmsg);
            } else {
                if ((size + r) > DGRAM_MAX_SIZE)
                    return;

                size += r;
                msg_mid->size = short_be((uint16_t)(sizeof(struct olsrmsg) + r));
            }
        }

        if (size + sizeof(struct olsrmsg) > DGRAM_MAX_SIZE)
            return;

        msg_tc = (struct olsrmsg *) (dgram + size);
        size += (uint32_t)sizeof(struct olsrmsg);
        msg_tc->type = OLSRMSG_TC;
        msg_tc->vtime = seconds2olsr(DEFAULT_VTIME);
        msg_tc->orig.addr = ep->address.addr;
        msg_tc->ttl = 0xFF;
        msg_tc->hop = 0;
        msg_tc->seq = short_be(msg_counter++);
        tc = (struct olsr_hmsg_tc *)(dgram + size);
        size += (uint32_t)sizeof(struct olsr_hmsg_tc);
        if (size > DGRAM_MAX_SIZE)
            return;

        tc->ansn = short_be(my_ansn);
        r = olsr_build_tc_neighbors(dgram + size, DGRAM_MAX_SIZE  - size, &last_neighbor);
        size += r;
        msg_tc->size = short_be((uint16_t)(sizeof(struct olsrmsg) + sizeof(struct olsr_hmsg_tc) + r));
        olsr_scheduled_output(OLSR_TC_INTERVAL, dgram, (uint16_t)size, pdev );
    } while(last_neighbor);
}

static void olsr_compose_hello_dgram(struct pico_device *pdev, struct pico_ipv4_link *ep)
{
    struct olsrmsg *msg_hello;
    uint32_t size = 0, r;
    struct olsr_route_entry *last_neighbor = NULL;
    uint8_t *dgram;
    struct olsr_hmsg_hello *hello;
    /* HELLO Message */
    do {
        dgram = PICO_ZALLOC(DGRAM_MAX_SIZE);
        if (!dgram) {
            OOM();
            return;
        }

        size = (uint32_t)sizeof(struct olsrhdr);
        msg_hello = (struct olsrmsg *) (dgram + size);
        size += (uint32_t)sizeof(struct olsrmsg);
        msg_hello->type = OLSRMSG_HELLO;
        msg_hello->vtime = seconds2olsr(DEFAULT_VTIME);
        msg_hello->orig.addr = ep->address.addr;
        msg_hello->ttl = 1;
        msg_hello->hop = 0;
        msg_hello->seq = short_be(msg_counter++);
        hello = (struct olsr_hmsg_hello *)(dgram + size);
        size += (uint32_t)sizeof(struct olsr_hmsg_hello);
        hello->reserved = 0;
        hello->htime = seconds2olsr(OLSR_HELLO_INTERVAL);
        hello->htime = 0x05; /* Todo: find and define values */
        hello->willingness = 0x07;
        if (DGRAM_MAX_SIZE > size) {
            r = olsr_build_hello_neighbors(dgram + size, DGRAM_MAX_SIZE - size, &last_neighbor);
            if (r == 0) {
                /* olsr_dbg("Building hello message\n"); */
                PICO_FREE(dgram);
                return;
            }
        }

        size += r;
        msg_hello->size = short_be((uint16_t)(sizeof(struct olsrmsg) + sizeof(struct olsr_hmsg_hello) + r));
        olsr_scheduled_output(OLSR_HELLO_INTERVAL, dgram, (uint16_t)size, pdev );
    } while(last_neighbor);
}

static void olsr_make_dgram(struct pico_device *pdev, int full)
{
    struct pico_ipv4_link *ep;
    ep = pico_ipv4_link_by_dev(pdev);
    if (!ep) {
        return;
    }

    if (!full) {
        olsr_compose_hello_dgram(pdev, ep);
    } else {
        olsr_compose_tc_dgram(pdev, ep);
    } /*if full */

}

/* Old code was relying on ethernet arp requests */
#define arp_storm(...) do {} while(0)

static void recv_mid(uint8_t *buffer, uint32_t len, struct olsr_route_entry *origin)
{
    uint32_t parsed = 0;
    uint32_t *address;
    struct olsr_route_entry *e;

    if (len % sizeof(uint32_t)) /*drop*/
        return;

    while (len > parsed) {
        address = (uint32_t *)(buffer + parsed);
        e = get_route_by_address(Local_interfaces, *address);
        if (!e) {
            e = PICO_ZALLOC(sizeof(struct olsr_route_entry));
            if (!e) {
                olsr_dbg("olsr allocating route\n");
                OOM();
                return;
            }

            e->time_left = (OLSR_HELLO_INTERVAL << 2);
            e->destination.addr = *address;
            e->gateway = origin;
            /* e->iface = origin->iface; */
            e->iface = NULL;
            e->metric = (uint16_t)(origin->metric + 1u);
            e->lq = origin->lq;
            e->nlq = origin->nlq;
            olsr_route_add(e);
            arp_storm(&e->destination);
        } else if (e->metric > (origin->metric + 1)) {
            olsr_route_del(e);
            e->metric = (uint16_t)(origin->metric + 1u);
            e->gateway = origin;
            e->time_left = (OLSR_HELLO_INTERVAL << 2);
            olsr_route_add(e);
        }

        parsed += (uint32_t)sizeof(uint32_t);
    }
}

/* static void recv_hello(uint8_t *buffer, uint32_t len, struct olsr_route_entry *origin) */
static void recv_hello(uint8_t *buffer, uint32_t len, struct olsr_route_entry *origin, uint16_t hops)
{
    struct olsr_link *li;
    struct olsr_route_entry *e;
    uint32_t parsed = 0;
    struct olsr_neighbor *neigh;

    if (!origin)
        return;

    /* Don't parse hello messages that were forwarded */
    if (hops > 0 || origin->metric > 1)
        return;

    if (pico_ipv4_link_get(&origin->destination))
        return;


    while (len > parsed) {
        li = (struct olsr_link *) buffer;
        neigh = (struct olsr_neighbor *)(buffer + parsed + sizeof(struct olsr_link));
        parsed += short_be(li->link_msg_size);
        e = get_route_by_address(Local_interfaces, neigh->addr);
        if (!e) {
            e = PICO_ZALLOC(sizeof(struct olsr_route_entry));
            if (!e) {
                olsr_dbg("olsr allocating route\n");
                OOM();
                return;
            }

            e->time_left = (OLSR_HELLO_INTERVAL << 2);
            e->destination.addr = neigh->addr;
            e->gateway = origin;
            e->iface = NULL;
            e->metric = (uint16_t)(origin->metric + hops + 1);
            e->link_type = OLSRLINK_UNKNOWN;
            e->lq = MIN(origin->lq, neigh->lq);
            e->nlq = MIN(origin->nlq, neigh->nlq);
            olsr_route_add(e);
            arp_storm(&e->destination);
        } else if ((e->gateway != origin) && (origin->metric > 1) && (e->metric > (origin->metric + hops + 1))) {
            olsr_route_del(e);
            e->metric = (uint16_t)(origin->metric + hops + 1);
            e->gateway = origin;
            e->time_left = (OLSR_HELLO_INTERVAL << 2);
            olsr_route_add(e);
        } else {
            e->time_left = (OLSR_HELLO_INTERVAL << 2);
        }
    }
}

/* static uint32_t reconsider_topology(uint8_t *buf, uint32_t size, struct olsr_route_entry *e) */
static uint32_t reconsider_topology(uint8_t *buf, uint32_t size, struct olsr_route_entry *e)
{
    struct olsr_hmsg_tc *tc = (struct olsr_hmsg_tc *) buf;
    uint16_t new_ansn = short_be(tc->ansn);
    uint32_t parsed = sizeof(struct olsr_hmsg_tc);
    struct olsr_route_entry *rt;
    struct olsr_neighbor *n;
    uint32_t retval = 0;

    if (!e->advertised_tc)
        retval = 1;

    if (e->advertised_tc && fresher(new_ansn, e->ansn))
    {
        PICO_FREE(e->advertised_tc); /* <--- XXX check invalid free? */
        e->advertised_tc = NULL;
        retval = 1;
    }

    /* Ignore self packets */
    if (pico_ipv4_link_get(&e->destination))
        return 0;

    if (!e->advertised_tc) {
        e->advertised_tc = PICO_ZALLOC(size);
        if (!e->advertised_tc) {
            OOM();
            olsr_dbg("Allocating forward packet\n");
            return 0;
        }

        memcpy(e->advertised_tc, buf, size);
        e->ansn = new_ansn;
        while (parsed < size) {
            n = (struct olsr_neighbor *) (buf + parsed);
            parsed += (uint32_t)sizeof(struct olsr_neighbor);
            rt = get_route_by_address(Local_interfaces, n->addr);
            if (rt && (rt->gateway == e)) {
                /* Refresh existing node */
                rt->time_left = e->time_left;
            } else if (!rt || (rt->metric > (e->metric + 1)) || (rt->nlq < n->nlq)) {
                if (!rt) {
                    rt = PICO_ZALLOC(sizeof (struct olsr_route_entry));
                    if (!rt) {
                        OOM();
                        return retval;
                    }

                    rt->destination.addr = n->addr;
                    rt->link_type = OLSRLINK_UNKNOWN;
                } else {
                    olsr_route_del(rt);
                }

                rt->iface = e->iface;
                rt->gateway = e;
                rt->metric = (uint16_t)(e->metric + 1);
                rt->lq = n->lq; /* 0xff */
                rt->nlq = n->nlq; /* 0xff */
                rt->time_left = e->time_left; /* 256 */
                olsr_route_add(rt);
            }
        }
        /* olsr_dbg("Routes changed...\n"); */
    }

    return retval;
}


static void olsr_recv(uint8_t *buffer, uint32_t len)
{
    struct olsrmsg *msg;
    struct olsrhdr *oh = (struct olsrhdr *) buffer;
    struct olsr_route_entry *ancestor;
    uint32_t parsed = 0;
    uint16_t outsize = 0;
    uint8_t *datagram;

    if (len != short_be(oh->len)) {
        return;
    }

    /* RFC 3626, section 3.4, if a packet is too small, it is silently discarded */
    if (len < 16) {
        return;
    }

    parsed += (uint32_t)sizeof(struct olsrhdr);

    datagram = PICO_ZALLOC(DGRAM_MAX_SIZE);
    if (!datagram) {
        OOM();
        return;
    }

    outsize = (uint16_t) (outsize + (sizeof(struct olsrhdr)));
    /* Section 1: parsing received messages. */
    while (len > parsed) {
        struct olsr_route_entry *origin;
        msg = (struct olsrmsg *) (buffer + parsed);
        origin = get_route_by_address(Local_interfaces, msg->orig.addr);

        if(pico_ipv4_link_find(&msg->orig) != NULL) {
            /* olsr_dbg("rebound\n"); */
            parsed += short_be(msg->size);
            continue;
        }

        /* OLSR's TTL expired. */
        if (msg->ttl < 1u) {
            parsed += short_be(msg->size);
            continue;
        }

        if (!origin) {
            if (msg->hop == 0) {
                struct olsr_route_entry *e = PICO_ZALLOC(sizeof (struct olsr_route_entry));
                if (!e) {
                    parsed += short_be(msg->size);
                    OOM();
                    break;
                }

                e->destination.addr = msg->orig.addr;
                e->link_type = OLSRLINK_SYMMETRIC;
                e->time_left = (OLSR_HELLO_INTERVAL << 2);
                e->iface = Local_devices->dev;
                e->gateway = olsr_get_ethentry(e->iface);
                e->metric = 1;
                e->lq = 0xFF;
                e->nlq = 0xFF;
                olsr_route_add(e);
            }

            parsed += short_be(msg->size);
            continue;
        }

        /* We know this is a Master host and a neighbor */
        origin->link_type = OLSRLINK_MPR;
        origin->time_left = olsr2seconds(msg->vtime);
        switch(msg->type) {
        case OLSRMSG_HELLO:
            ancestor = olsr_get_ethentry(origin->iface);
            if ((origin->metric > 1) && ancestor) {
                olsr_route_del(origin);
                origin->gateway = ancestor;
                origin->metric = 1;
                olsr_route_add(origin);
            }

            recv_hello(buffer + (uint32_t)parsed + (uint32_t)sizeof(struct olsrmsg) + (uint32_t)sizeof(struct olsr_hmsg_hello),
                       (uint32_t) ((short_be(msg->size) - (sizeof(struct olsrmsg))) - (uint32_t)sizeof(struct olsr_hmsg_hello)),
                       origin, msg->hop);
            msg->ttl = 0;
            break;
        case OLSRMSG_MID:
            if ((origin->seq != 0) && (!fresher(short_be(msg->seq), origin->seq))) {
                msg->ttl = 0;
            } else {
                recv_mid(buffer + parsed + sizeof(struct olsrmsg), (uint32_t)(short_be(msg->size) - (sizeof(struct olsrmsg))), origin);
                /* olsr_dbg("MID forwarded from origin %08x (seq: %u)\n", long_be(msg->orig.addr), short_be(msg->seq)); */
                origin->seq = short_be(msg->seq);
            }

            break;
        case OLSRMSG_TC:
            if(!pico_ipv4_link_find(&origin->destination)) {
                reconsider_topology(buffer + parsed + sizeof(struct olsrmsg), (uint32_t)(short_be(msg->size) - (sizeof(struct olsrmsg))), origin);
                if ((origin->seq != 0) && (!fresher(short_be(msg->seq), origin->seq))) {
                    msg->ttl = 0;
                } else {
                    /* olsr_dbg("TC forwarded from origin %08x (seq: %u)\n", long_be(msg->orig.addr), short_be(msg->seq)); */
                    origin->seq = short_be(msg->seq);
                }
            }

            break;
        default:
            PICO_FREE(datagram);
            return;
        }

        if (msg->ttl > 1) {
            msg->hop++;
            msg->ttl--;
            memcpy(datagram + outsize, msg, short_be(msg->size));
            outsize = (uint16_t)(outsize + short_be(msg->size));
        }

        parsed += short_be(msg->size);
    }
    /* Section 2: forwarding parsed messages that got past the filter. */
    if ((outsize > sizeof(struct olsrhdr))) {
        /* Finalize FWD packet */
        olsr_scheduled_output(OLSR_MAXJITTER, datagram, outsize, NULL);
    } else {
        /* Nothing to forward. */
        PICO_FREE(datagram);
    }
}

static void wakeup(uint16_t ev, struct pico_socket *s)
{
    unsigned char *recvbuf;
    int r = 0;
    struct pico_ip4 ANY = {
        0
    };
    uint16_t port = OLSR_PORT;
    recvbuf = PICO_ZALLOC(DGRAM_MAX_SIZE);
    if (!recvbuf) {
        OOM();
        return;
    }

    if (ev & PICO_SOCK_EV_RD) {
        r = pico_socket_recv(s, recvbuf, DGRAM_MAX_SIZE);
        if (r > 0)
            olsr_recv(recvbuf, (uint32_t)r);
    }

    if (ev == PICO_SOCK_EV_ERR) {
        pico_socket_close(udpsock);
        udpsock = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &wakeup);
        if (udpsock)
            pico_socket_bind(udpsock, &ANY, &port);
    }

    PICO_FREE(recvbuf);
}

static void olsr_hello_tick(pico_time when, void *unused)
{
    struct olsr_dev_entry *d;
    (void)when;
    (void)unused;
    olsr_garbage_collector(Local_interfaces);
    refresh_routes();
    d = Local_devices;
    while(d) {
        olsr_make_dgram(d->dev, 0);
        d = d->next;
    }
    if (!pico_timer_add(OLSR_HELLO_INTERVAL, &olsr_hello_tick, NULL)) {
        olsr_dbg("OLSR: Failed to start hello_tick timer\n");
        /* TODO no more ticks now */
    }
}

static void olsr_tc_tick(pico_time when, void *unused)
{
    struct olsr_dev_entry *d;
    (void)when;
    (void)unused;
    d = Local_devices;
    while(d) {
        olsr_make_dgram(d->dev, 1);
        d = d->next;
    }
    if (!pico_timer_add(OLSR_TC_INTERVAL, &olsr_tc_tick, NULL)) {
        olsr_dbg("OLSR: Failed to start tc_tick timer\n");
        /* TODO no more ticks now */
    }
}


/* Public interface */

void pico_olsr_init(void)
{
    uint32_t hello_timer = 0;
    struct pico_ip4 ANY = {
        0
    };
    uint16_t port = OLSR_PORT;
    olsr_dbg("OLSR initialized.\n");
    if (!udpsock) {
        udpsock = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &wakeup);
        if (udpsock)
            pico_socket_bind(udpsock, &ANY, &port);
    }

    hello_timer = pico_timer_add(pico_rand() % 100, &olsr_hello_tick, NULL);
    if (!hello_timer) {
        olsr_dbg("OLSR: Failed to start hello_tick timer\n");
        return;
    }
    if (!pico_timer_add(pico_rand() % 900, &olsr_tc_tick, NULL)) {
        olsr_dbg("OLSR: Failed to start tc_tick timer\n");
        pico_timer_cancel(hello_timer);
    }
}


int OOM(void)
{
    volatile int c = 3600;
    c++;
    c++;
    c++;
    return -1;
}

int pico_olsr_add(struct pico_device *dev)
{
    struct pico_ipv4_link *lnk = NULL;
    struct olsr_dev_entry *od;


    if (!dev) {
        pico_err = PICO_ERR_EINVAL;
        return -1;
    }

    /* olsr_dbg("OLSR: Adding device %s\n", dev->name); */
    od = PICO_ZALLOC(sizeof(struct olsr_dev_entry));
    if (!od) {
        pico_err = PICO_ERR_ENOMEM;
        /* OOM(); */
        return -1;
    }

    od->dev = dev;
    od->next = Local_devices;
    Local_devices = od;

    do {
        char ipaddr[20];
        lnk = pico_ipv4_link_by_dev_next(dev, lnk);
        if (lnk) {
            struct olsr_route_entry *e = PICO_ZALLOC(sizeof(struct olsr_route_entry));
            /* olsr_dbg("OLSR: Found IP address %08x\n", long_be(lnk->address.addr)); */
            pico_ipv4_to_string(ipaddr, (lnk->address.addr));
            /* olsr_dbg("OLSR: Found IP address %s\n", ipaddr); */
            if (!e) {
                olsr_dbg("olsr allocating route\n");
                pico_err = PICO_ERR_ENOMEM;
                return -1;
            }

            e->destination.addr = lnk->address.addr;
            e->link_type = OLSRLINK_SYMMETRIC;
            e->time_left = (OLSR_HELLO_INTERVAL << 2);
            e->gateway = NULL;
            e->children = NULL;
            e->iface = dev;
            e->metric = 0;
            e->lq = 0xFF;
            e->nlq = 0xFF;
            e->next = Local_interfaces;
            Local_interfaces = e;

        }
    } while(lnk);

    return 0;
}

#endif
