Free (GPLv2) TCP/IP stack developed by TASS Belgium

Dependents:   lpc1768-picotcp-demo ZeroMQ_PicoTCP_Publisher_demo TCPSocket_HelloWorld_PicoTCP Pico_TCP_UDP_Test ... more

PicoTCP. Copyright (c) 2013 TASS Belgium NV.

Released under the GNU General Public License, version 2.

Different licensing models may exist, at the sole discretion of the Copyright holders.

Official homepage: http://www.picotcp.com

Bug tracker: https://github.com/tass-belgium/picotcp/issues

Development steps:

  • initial integration with mbed RTOS
  • generic mbed Ethernet driver
  • high performance NXP LPC1768 specific Ethernet driver
  • Multi-threading support for mbed RTOS
  • Berkeley sockets and integration with the New Socket API
  • Fork of the apps running on top of the New Socket API
  • Scheduling optimizations
  • Debugging/benchmarking/testing

Demo application (measuring TCP sender performance):

Import programlpc1768-picotcp-demo

A PicoTCP demo app testing the ethernet throughput on the lpc1768 mbed board.

modules/pico_igmp.c

Committer:
tass
Date:
2016-01-28
Revision:
155:a70f34550c34
Parent:
154:6c0e92a80c4a

File content as of revision 155:a70f34550c34:

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

   RFC 1112, 2236, 3376, 3569, 3678, 4607

   Authors: Kristof Roelants (IGMPv3), Simon Maes, Brecht Van Cauwenberghe
 *********************************************************************/

#include "pico_stack.h"
#include "pico_ipv4.h"
#include "pico_igmp.h"
#include "pico_config.h"
#include "pico_eth.h"
#include "pico_addressing.h"
#include "pico_frame.h"
#include "pico_tree.h"
#include "pico_device.h"
#include "pico_socket.h"

#if defined(PICO_SUPPORT_IGMP) && defined(PICO_SUPPORT_MCAST) 

#define igmp_dbg(...) do {} while(0)
/* #define igmp_dbg dbg */

/* membership states */
#define IGMP_STATE_NON_MEMBER             (0x0)
#define IGMP_STATE_DELAYING_MEMBER        (0x1)
#define IGMP_STATE_IDLE_MEMBER            (0x2)

/* events */
#define IGMP_EVENT_DELETE_GROUP           (0x0)
#define IGMP_EVENT_CREATE_GROUP           (0x1)
#define IGMP_EVENT_UPDATE_GROUP           (0x2)
#define IGMP_EVENT_QUERY_RECV             (0x3)
#define IGMP_EVENT_REPORT_RECV            (0x4)
#define IGMP_EVENT_TIMER_EXPIRED          (0x5)

/* message types */
#define IGMP_TYPE_MEM_QUERY               (0x11)
#define IGMP_TYPE_MEM_REPORT_V1           (0x12)
#define IGMP_TYPE_MEM_REPORT_V2           (0x16)
#define IGMP_TYPE_LEAVE_GROUP             (0x17)
#define IGMP_TYPE_MEM_REPORT_V3           (0x22)

/* group record types */
#define IGMP_MODE_IS_INCLUDE              (1)
#define IGMP_MODE_IS_EXCLUDE              (2)
#define IGMP_CHANGE_TO_INCLUDE_MODE       (3)
#define IGMP_CHANGE_TO_EXCLUDE_MODE       (4)
#define IGMP_ALLOW_NEW_SOURCES            (5)
#define IGMP_BLOCK_OLD_SOURCES            (6)

/* host flag */
#define IGMP_HOST_LAST                    (0x1)
#define IGMP_HOST_NOT_LAST                (0x0)

/* list of timers, counters and their default values */
#define IGMP_ROBUSTNESS                   (2u)
#define IGMP_QUERY_INTERVAL               (125) /* secs */
#define IGMP_QUERY_RESPONSE_INTERVAL      (10u) /* secs */
#define IGMP_STARTUP_QUERY_INTERVAL       (IGMPV3_QUERY_INTERVAL / 4)
#define IGMP_STARTUP_QUERY_COUNT          (IGMPV3_ROBUSTNESS)
#define IGMP_LAST_MEMBER_QUERY_INTERVAL   (1) /* secs */
#define IGMP_LAST_MEMBER_QUERY_COUNT      (IGMPV3_ROBUSTNESS)
#define IGMP_UNSOLICITED_REPORT_INTERVAL  (1) /* secs */
#define IGMP_DEFAULT_MAX_RESPONSE_TIME    (100)

/* custom timers types */
#define IGMP_TIMER_GROUP_REPORT           (1)
#define IGMP_TIMER_V1_QUERIER             (2)
#define IGMP_TIMER_V2_QUERIER             (3)

/* IGMP groups */
#define IGMP_ALL_HOST_GROUP               long_be(0xE0000001) /* 224.0.0.1 */
#define IGMP_ALL_ROUTER_GROUP             long_be(0xE0000002) /* 224.0.0.2 */
#define IGMPV3_ALL_ROUTER_GROUP           long_be(0xE0000016) /* 224.0.0.22 */

/* misc */
#define IGMP_TIMER_STOPPED                (1)
#define IP_OPTION_ROUTER_ALERT_LEN        (4u)
#define IGMP_MAX_GROUPS                   (32) /* max 255 */

PACKED_STRUCT_DEF igmp_message {
    uint8_t type;
    uint8_t max_resp_time;
    uint16_t crc;
    uint32_t mcast_group;
};

PACKED_STRUCT_DEF igmpv3_query {
    uint8_t type;
    uint8_t max_resp_time;
    uint16_t crc;
    uint32_t mcast_group;
    uint8_t rsq;
    uint8_t qqic;
    uint16_t sources;
};

PACKED_STRUCT_DEF igmpv3_group_record {
    uint8_t type;
    uint8_t aux;
    uint16_t sources;
    uint32_t mcast_group;
};

PACKED_STRUCT_DEF igmpv3_report {
    uint8_t type;
    uint8_t res0;
    uint16_t crc;
    uint16_t res1;
    uint16_t groups;
};

struct igmp_parameters {
    uint8_t event;
    uint8_t state;
    uint8_t last_host;
    uint8_t filter_mode;
    uint8_t max_resp_time;
    struct pico_ip4 mcast_link;
    struct pico_ip4 mcast_group;
    struct pico_tree *MCASTFilter;
    struct pico_frame *f;
};

struct igmp_timer {
    uint8_t type;
    uint8_t stopped;
    pico_time start;
    pico_time delay;
    struct pico_ip4 mcast_link;
    struct pico_ip4 mcast_group;
    struct pico_frame *f;
    void (*callback)(struct igmp_timer *t);
};

/* queues */
static struct pico_queue igmp_in = {
    0
};
static struct pico_queue igmp_out = {
    0
};

/* finite state machine caller */
static int pico_igmp_process_event(struct igmp_parameters *p);

/* state callback prototype */
typedef int (*callback)(struct igmp_parameters *);

static inline int igmpt_type_compare(struct igmp_timer *a,  struct igmp_timer *b)
{
    if (a->type < b->type)
        return -1;

    if (a->type > b->type)
        return 1;

    return 0;
}


static inline int igmpt_group_compare(struct igmp_timer *a,  struct igmp_timer *b)
{
    return pico_ipv4_compare(&a->mcast_group, &b->mcast_group);
}

static inline int igmpt_link_compare(struct igmp_timer *a,  struct igmp_timer *b)
{
    return pico_ipv4_compare(&a->mcast_link, &b->mcast_link);
}

/* redblack trees */
static int igmp_timer_cmp(void *ka, void *kb)
{
    struct igmp_timer *a = ka, *b = kb;
    int cmp = igmpt_type_compare(a, b);
    if (cmp)
        return cmp;

    cmp = igmpt_group_compare(a, b);
    if (cmp)
        return cmp;

    return igmpt_link_compare(a, b);

}
PICO_TREE_DECLARE(IGMPTimers, igmp_timer_cmp);

static inline int igmpparm_group_compare(struct igmp_parameters *a,  struct igmp_parameters *b)
{
    return pico_ipv4_compare(&a->mcast_group, &b->mcast_group);
}

static inline int igmpparm_link_compare(struct igmp_parameters *a,  struct igmp_parameters *b)
{
    return pico_ipv4_compare(&a->mcast_link, &b->mcast_link);
}

static int igmp_parameters_cmp(void *ka, void *kb)
{
    struct igmp_parameters *a = ka, *b = kb;
    int cmp = igmpparm_group_compare(a, b);
    if (cmp)
        return cmp;

    return igmpparm_link_compare(a, b);
}
PICO_TREE_DECLARE(IGMPParameters, igmp_parameters_cmp);

static int igmp_sources_cmp(void *ka, void *kb)
{
    struct pico_ip4 *a = ka, *b = kb;
    return pico_ipv4_compare(a, b);
}
PICO_TREE_DECLARE(IGMPAllow, igmp_sources_cmp);
PICO_TREE_DECLARE(IGMPBlock, igmp_sources_cmp);

static struct igmp_parameters *pico_igmp_find_parameter(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group)
{
    struct igmp_parameters test = {
        0
    };
    if (!mcast_link || !mcast_group)
        return NULL;

    test.mcast_link.addr = mcast_link->addr;
    test.mcast_group.addr = mcast_group->addr;
    return pico_tree_findKey(&IGMPParameters, &test);
}

static int pico_igmp_delete_parameter(struct igmp_parameters *p)
{
    if (pico_tree_delete(&IGMPParameters, p))
        PICO_FREE(p);
    else
        return -1;

    return 0;
}

static void pico_igmp_timer_expired(pico_time now, void *arg)
{
    struct igmp_timer *t = NULL, *timer = NULL, test = {
        0
    };

    IGNORE_PARAMETER(now);
    t = (struct igmp_timer *)arg;
    test.type = t->type;
    test.mcast_link = t->mcast_link;
    test.mcast_group = t->mcast_group;
    igmp_dbg("IGMP: timer expired for %08X link %08X type %u, delay %lu\n", t->mcast_group.addr, t->mcast_link.addr, t->type, t->delay);
    timer = pico_tree_findKey(&IGMPTimers, &test);
    if (!timer) {
        return;
    }

    if (timer->stopped == IGMP_TIMER_STOPPED) {
        PICO_FREE(t);
        return;
    }

    if (timer->start + timer->delay < PICO_TIME_MS()) {
        pico_tree_delete(&IGMPTimers, timer);
        if (timer->callback)
            timer->callback(timer);

        PICO_FREE(timer);
    } else {
        igmp_dbg("IGMP: restart timer for %08X, delay %lu, new delay %lu\n", t->mcast_group.addr, t->delay,  (timer->start + timer->delay) - PICO_TIME_MS());
        pico_timer_add((timer->start + timer->delay) - PICO_TIME_MS(), &pico_igmp_timer_expired, timer);
    }

    return;
}

static int pico_igmp_timer_reset(struct igmp_timer *t)
{
    struct igmp_timer *timer = NULL, test = {
        0
    };

    igmp_dbg("IGMP: reset timer for %08X, delay %lu\n", t->mcast_group.addr, t->delay);
    test.type = t->type;
    test.mcast_link = t->mcast_link;
    test.mcast_group = t->mcast_group;
    timer = pico_tree_findKey(&IGMPTimers, &test);
    if (!timer)
        return -1;

    *timer = *t;
    timer->start = PICO_TIME_MS();
    return 0;
}

static int pico_igmp_timer_start(struct igmp_timer *t)
{
    struct igmp_timer *timer = NULL, test = {
        0
    };

    igmp_dbg("IGMP: start timer for %08X link %08X type %u, delay %lu\n", t->mcast_group.addr, t->mcast_link.addr, t->type, t->delay);
    test.type = t->type;
    test.mcast_link = t->mcast_link;
    test.mcast_group = t->mcast_group;
    timer = pico_tree_findKey(&IGMPTimers, &test);
    if (timer)
        return pico_igmp_timer_reset(t);

    timer = PICO_ZALLOC(sizeof(struct igmp_timer));
    if (!timer) {
        pico_err = PICO_ERR_ENOMEM;
        return -1;
    }

    *timer = *t;
    timer->start = PICO_TIME_MS();

    pico_tree_insert(&IGMPTimers, timer);
    pico_timer_add(timer->delay, &pico_igmp_timer_expired, timer);
    return 0;
}

static int pico_igmp_timer_stop(struct igmp_timer *t)
{
    struct igmp_timer *timer = NULL, test = {
        0
    };

    test.type = t->type;
    test.mcast_link = t->mcast_link;
    test.mcast_group = t->mcast_group;
    timer = pico_tree_findKey(&IGMPTimers, &test);
    if (!timer)
        return 0;

    igmp_dbg("IGMP: stop timer for %08X, delay %lu\n", timer->mcast_group.addr, timer->delay);
    timer->stopped = IGMP_TIMER_STOPPED;
    return 0;
}

static int pico_igmp_timer_is_running(struct igmp_timer *t)
{
    struct igmp_timer *timer = NULL, test = {
        0
    };

    test.type = t->type;
    test.mcast_link = t->mcast_link;
    test.mcast_group = t->mcast_group;
    timer = pico_tree_findKey(&IGMPTimers, &test);
    if (timer)
        return 1;

    return 0;
}

static struct igmp_timer *pico_igmp_find_timer(uint8_t type, struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group)
{
    struct igmp_timer test = {
        0
    };

    test.type = type;
    test.mcast_link = *mcast_link;
    test.mcast_group = *mcast_group;
    return pico_tree_findKey(&IGMPTimers, &test);
}

static void pico_igmp_report_expired(struct igmp_timer *t)
{
    struct igmp_parameters *p = NULL;

    p = pico_igmp_find_parameter(&t->mcast_link, &t->mcast_group);
    if (!p)
        return;

    p->event = IGMP_EVENT_TIMER_EXPIRED;
    pico_igmp_process_event(p);
}

static void pico_igmp_v2querier_expired(struct igmp_timer *t)
{
    struct pico_ipv4_link *link = NULL;
    struct pico_tree_node *index = NULL, *_tmp = NULL;

    link = pico_ipv4_link_by_dev(t->f->dev);
    if (!link)
        return;

    /* When changing compatibility mode, cancel all pending response
     * and retransmission timers.
     */
    pico_tree_foreach_safe(index, &IGMPTimers, _tmp)
    {
        ((struct igmp_timer *)index->keyValue)->stopped = IGMP_TIMER_STOPPED;
        pico_tree_delete(&IGMPTimers, index->keyValue);
    }
    igmp_dbg("IGMP: switch to compatibility mode IGMPv3\n");
    link->mcast_compatibility = PICO_IGMPV3;
    return;
}

static int pico_igmp_is_checksum_valid(struct pico_frame *f)
{
    struct pico_ipv4_hdr *hdr = NULL;
    uint8_t ihl = 24, datalen = 0;

    hdr = (struct pico_ipv4_hdr *)f->net_hdr;
    ihl = (uint8_t)((hdr->vhl & 0x0F) * 4); /* IHL is in 32bit words */
    datalen = (uint8_t)(short_be(hdr->len) - ihl);

    if (short_be(pico_checksum(f->transport_hdr, datalen)) == 0)
        return 1;

    igmp_dbg("IGMP: invalid checksum\n");
    return 0;
}

/* RFC 3376 $7.1 */
static int pico_igmp_compatibility_mode(struct pico_frame *f)
{
    struct pico_ipv4_hdr *hdr = NULL;
    struct pico_ipv4_link *link = NULL;
    struct pico_tree_node *index = NULL, *_tmp = NULL;
    struct igmp_timer t = {
        0
    };
    uint8_t ihl = 24, datalen = 0;

    link = pico_ipv4_link_by_dev(f->dev);
    if (!link)
        return -1;

    hdr = (struct pico_ipv4_hdr *) f->net_hdr;
    ihl = (uint8_t)((hdr->vhl & 0x0F) * 4); /* IHL is in 32bit words */
    datalen = (uint8_t)(short_be(hdr->len) - ihl);
    igmp_dbg("IGMP: IHL = %u, LEN = %u, OCTETS = %u\n", ihl, short_be(hdr->len), datalen);

    if (datalen >= 12) {
        /* IGMPv3 query */
        t.type = IGMP_TIMER_V2_QUERIER;
        if (pico_igmp_timer_is_running(&t)) { /* IGMPv2 querier present timer still running */
            igmp_dbg("Timer is already running\n");
            return -1;
        } else {
            link->mcast_compatibility = PICO_IGMPV3;
            igmp_dbg("IGMP Compatibility: v3\n");
            return 0;
        }
    } else if (datalen == 8) {
        struct igmp_message *query = (struct igmp_message *)f->transport_hdr;
        if (query->max_resp_time != 0) {
            /* IGMPv2 query */
            /* When changing compatibility mode, cancel all pending response
             * and retransmission timers.
             */
            pico_tree_foreach_safe(index, &IGMPTimers, _tmp)
            {
                ((struct igmp_timer *)index->keyValue)->stopped = IGMP_TIMER_STOPPED;
                pico_tree_delete(&IGMPTimers, index->keyValue);
            }
            igmp_dbg("IGMP: switch to compatibility mode IGMPv2\n");
            link->mcast_compatibility = PICO_IGMPV2;
            t.type = IGMP_TIMER_V2_QUERIER;
            t.delay = ((IGMP_ROBUSTNESS * link->mcast_last_query_interval) + IGMP_QUERY_RESPONSE_INTERVAL) * 1000;
            t.f = f;
            t.callback = pico_igmp_v2querier_expired;
            /* only one of this type of timer may exist! */
            pico_igmp_timer_start(&t);
        } else {
            /* IGMPv1 query, not supported */
            return -1;
        }
    } else {
        /* invalid query, silently ignored */
        return -1;
    }

    return 0;
}

static struct igmp_parameters *pico_igmp_analyse_packet(struct pico_frame *f)
{
    struct igmp_message *message = NULL;
    struct igmp_parameters *p = NULL;
    struct pico_ipv4_link *link = NULL;
    struct pico_ip4 mcast_group = {
        0
    };

    link = pico_ipv4_link_by_dev(f->dev);
    if (!link)
        return NULL;

    /* IGMPv2 and IGMPv3 have a similar structure for the first 8 bytes */
    message = (struct igmp_message *)f->transport_hdr;
    mcast_group.addr = message->mcast_group;
    p = pico_igmp_find_parameter(&link->address, &mcast_group);
    if (!p && mcast_group.addr == 0) { /* general query */
        p = PICO_ZALLOC(sizeof(struct igmp_parameters));
        if (!p)
            return NULL;

        p->state = IGMP_STATE_NON_MEMBER;
        p->mcast_link.addr = link->address.addr;
        p->mcast_group.addr = mcast_group.addr;
        pico_tree_insert(&IGMPParameters, p);
    } else if (!p) {
        return NULL;
    }

    switch (message->type) {
    case IGMP_TYPE_MEM_QUERY:
        p->event = IGMP_EVENT_QUERY_RECV;
        break;
    case IGMP_TYPE_MEM_REPORT_V1:
        p->event = IGMP_EVENT_REPORT_RECV;
        break;
    case IGMP_TYPE_MEM_REPORT_V2:
        p->event = IGMP_EVENT_REPORT_RECV;
        break;
    case IGMP_TYPE_MEM_REPORT_V3:
        p->event = IGMP_EVENT_REPORT_RECV;
        break;
    default:
        return NULL;
    }
    p->max_resp_time = message->max_resp_time; /* if IGMPv3 report this will be 0 (res0 field) */
    p->f = f;

    return p;
}

static int pico_igmp_process_in(struct pico_protocol *self, struct pico_frame *f)
{
    struct igmp_parameters *p = NULL;
    IGNORE_PARAMETER(self);

    if (!pico_igmp_is_checksum_valid(f))
        goto out;

    if (pico_igmp_compatibility_mode(f) < 0)
        goto out;

    p = pico_igmp_analyse_packet(f);
    if (!p)
        goto out;

    return pico_igmp_process_event(p);

out:
    pico_frame_discard(f);
    return 0;
}

static int pico_igmp_process_out(struct pico_protocol *self, struct pico_frame *f)
{
    /* packets are directly transferred to the IP layer by calling pico_ipv4_frame_push */
    IGNORE_PARAMETER(self);
    IGNORE_PARAMETER(f);
    return 0;
}

/* Interface: protocol definition */
struct pico_protocol pico_proto_igmp = {
    .name = "igmp",
    .proto_number = PICO_PROTO_IGMP,
    .layer = PICO_LAYER_TRANSPORT,
    .process_in = pico_igmp_process_in,
    .process_out = pico_igmp_process_out,
    .q_in = &igmp_in,
    .q_out = &igmp_out,
};

int pico_igmp_state_change(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t filter_mode, struct pico_tree *_MCASTFilter, uint8_t state)
{
    struct igmp_parameters *p = NULL;

    if (mcast_group->addr == IGMP_ALL_HOST_GROUP)
        return 0;

    p = pico_igmp_find_parameter(mcast_link, mcast_group);
    if (!p && state == PICO_IGMP_STATE_CREATE) {
        p = PICO_ZALLOC(sizeof(struct igmp_parameters));
        if (!p) {
            pico_err = PICO_ERR_ENOMEM;
            return -1;
        }

        if (!mcast_link || !mcast_group) {
            pico_err = PICO_ERR_EINVAL;
            return -1;
        }

        p->state = IGMP_STATE_NON_MEMBER;
        p->mcast_link = *mcast_link;
        p->mcast_group = *mcast_group;
        pico_tree_insert(&IGMPParameters, p);
    } else if (!p) {
        pico_err = PICO_ERR_EINVAL;
        return -1;
    }

    switch (state) {
    case PICO_IGMP_STATE_CREATE:
        p->event = IGMP_EVENT_CREATE_GROUP;
        break;

    case PICO_IGMP_STATE_UPDATE:
        p->event = IGMP_EVENT_UPDATE_GROUP;
        break;

    case PICO_IGMP_STATE_DELETE:
        p->event = IGMP_EVENT_DELETE_GROUP;
        break;

    default:
        return -1;
    }
    p->filter_mode = filter_mode;
    p->MCASTFilter = _MCASTFilter;

    return pico_igmp_process_event(p);
}

static int pico_igmp_send_report(struct igmp_parameters *p, struct pico_frame *f)
{
    struct pico_ip4 dst = {
        0
    };
    struct pico_ip4 mcast_group = {
        0
    };
    struct pico_ipv4_link *link = NULL;

    link = pico_ipv4_link_get(&p->mcast_link);
    if (!link)
        return -1;

    mcast_group.addr = p->mcast_group.addr;
    switch (link->mcast_compatibility) {
    case PICO_IGMPV2:
        if (p->event == IGMP_EVENT_DELETE_GROUP)
            dst.addr = IGMP_ALL_ROUTER_GROUP;
        else
            dst.addr = mcast_group.addr;

        break;

    case PICO_IGMPV3:
        dst.addr = IGMPV3_ALL_ROUTER_GROUP;
        break;

    default:
        pico_err = PICO_ERR_EPROTONOSUPPORT;
        return -1;
    }

    igmp_dbg("IGMP: send membership report on group %08X to %08X\n", mcast_group.addr, dst.addr);
    pico_ipv4_frame_push(f, &dst, PICO_PROTO_IGMP);
    return 0;
}

static int8_t pico_igmp_generate_report(struct igmp_parameters *p)
{
    struct pico_ipv4_link *link = NULL;
    int i = 0;

    link = pico_ipv4_link_get(&p->mcast_link);
    if (!link) {
        pico_err = PICO_ERR_EINVAL;
        return -1;
    }

    switch (link->mcast_compatibility) {
    case PICO_IGMPV1:
        pico_err = PICO_ERR_EPROTONOSUPPORT;
        return -1;

    case PICO_IGMPV2:
    {
        struct igmp_message *report = NULL;
        uint8_t report_type = IGMP_TYPE_MEM_REPORT_V2;
        if (p->event == IGMP_EVENT_DELETE_GROUP)
            report_type = IGMP_TYPE_LEAVE_GROUP;

        p->f = pico_proto_ipv4.alloc(&pico_proto_ipv4, IP_OPTION_ROUTER_ALERT_LEN + sizeof(struct igmp_message));
        p->f->net_len = (uint16_t)(p->f->net_len + IP_OPTION_ROUTER_ALERT_LEN);
        p->f->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN;
        p->f->transport_len = (uint16_t)(p->f->transport_len - IP_OPTION_ROUTER_ALERT_LEN);
        p->f->dev = pico_ipv4_link_find(&p->mcast_link);
        /* p->f->len is correctly set by alloc */

        report = (struct igmp_message *)p->f->transport_hdr;
        report->type = report_type;
        report->max_resp_time = IGMP_DEFAULT_MAX_RESPONSE_TIME;
        report->mcast_group = p->mcast_group.addr;

        report->crc = 0;
        report->crc = short_be(pico_checksum(report, sizeof(struct igmp_message)));
        break;
    }
    case PICO_IGMPV3:
    {
        struct igmpv3_report *report = NULL;
        struct igmpv3_group_record *record = NULL;
        struct pico_mcast_group *g = NULL, test = {
            0
        };
        struct pico_tree_node *index = NULL, *_tmp = NULL;
        struct pico_tree *IGMPFilter = NULL;
        struct pico_ip4 *source = NULL;
        uint8_t record_type = 0;
        uint8_t sources = 0;
        uint16_t len = 0;

        test.mcast_addr = p->mcast_group;
        g = pico_tree_findKey(link->MCASTGroups, &test);
        if (!g) {
            pico_err = PICO_ERR_EINVAL;
            return -1;
        }

        if (p->event == IGMP_EVENT_DELETE_GROUP) { /* "non-existent" state of filter mode INCLUDE and empty source list */
            p->filter_mode = PICO_IP_MULTICAST_INCLUDE;
            p->MCASTFilter = NULL;
        }

        if (p->event == IGMP_EVENT_QUERY_RECV) {
            goto igmp3_report;
        }


        /* cleanup filters */
        pico_tree_foreach_safe(index, &IGMPAllow, _tmp)
        {
            pico_tree_delete(&IGMPAllow, index->keyValue);
        }
        pico_tree_foreach_safe(index, &IGMPBlock, _tmp)
        {
            pico_tree_delete(&IGMPBlock, index->keyValue);
        }

        switch (g->filter_mode) {

        case PICO_IP_MULTICAST_INCLUDE:
            switch (p->filter_mode) {
            case PICO_IP_MULTICAST_INCLUDE:
                if (p->event == IGMP_EVENT_DELETE_GROUP) { /* all ADD_SOURCE_MEMBERSHIP had an equivalent DROP_SOURCE_MEMBERSHIP */
                    /* TO_IN (B) */
                    record_type = IGMP_CHANGE_TO_INCLUDE_MODE;
                    IGMPFilter = &IGMPAllow;
                    if (p->MCASTFilter) {
                        pico_tree_foreach(index, p->MCASTFilter) /* B */
                        {
                            pico_tree_insert(&IGMPAllow, index->keyValue);
                            sources++;
                        }
                    } /* else { IGMPAllow stays empty } */

                    break;
                }

                /* ALLOW (B-A) */
                /* if event is CREATE A will be empty, thus only ALLOW (B-A) has sense */
                if (p->event == IGMP_EVENT_CREATE_GROUP) /* first ADD_SOURCE_MEMBERSHIP */
                    record_type = IGMP_CHANGE_TO_INCLUDE_MODE;
                else
                    record_type = IGMP_ALLOW_NEW_SOURCES;

                IGMPFilter = &IGMPAllow;
                pico_tree_foreach(index, p->MCASTFilter) /* B */
                {
                    pico_tree_insert(&IGMPAllow, index->keyValue);
                    sources++;
                }
                pico_tree_foreach(index, &g->MCASTSources) /* A */
                {
                    source = pico_tree_findKey(&IGMPAllow, index->keyValue);
                    if (source) {
                        pico_tree_delete(&IGMPAllow, source);
                        sources--;
                    }
                }
                if (!pico_tree_empty(&IGMPAllow)) /* record type is ALLOW */
                    break;

                /* BLOCK (A-B) */
                record_type = IGMP_BLOCK_OLD_SOURCES;
                IGMPFilter = &IGMPBlock;
                pico_tree_foreach(index, &g->MCASTSources) /* A */
                {
                    pico_tree_insert(&IGMPBlock, index->keyValue);
                    sources++;
                }
                pico_tree_foreach(index, p->MCASTFilter) /* B */
                {
                    source = pico_tree_findKey(&IGMPBlock, index->keyValue);
                    if (source) {
                        pico_tree_delete(&IGMPBlock, source);
                        sources--;
                    }
                }
                if (!pico_tree_empty(&IGMPBlock)) /* record type is BLOCK */
                    break;

                /* ALLOW (B-A) and BLOCK (A-B) are empty: do not send report (RFC 3376 $5.1) */
                p->f = NULL;
                return 0;

            case PICO_IP_MULTICAST_EXCLUDE:
                /* TO_EX (B) */
                record_type = IGMP_CHANGE_TO_EXCLUDE_MODE;
                IGMPFilter = &IGMPBlock;
                pico_tree_foreach(index, p->MCASTFilter) /* B */
                {
                    pico_tree_insert(&IGMPBlock, index->keyValue);
                    sources++;
                }
                break;

            default:
                pico_err = PICO_ERR_EINVAL;
                return -1;
            }
            break;

        case PICO_IP_MULTICAST_EXCLUDE:
            switch (p->filter_mode) {
            case PICO_IP_MULTICAST_INCLUDE:
                /* TO_IN (B) */
                record_type = IGMP_CHANGE_TO_INCLUDE_MODE;
                IGMPFilter = &IGMPAllow;
                if (p->MCASTFilter) {
                    pico_tree_foreach(index, p->MCASTFilter) /* B */
                    {
                        pico_tree_insert(&IGMPAllow, index->keyValue);
                        sources++;
                    }
                } /* else { IGMPAllow stays empty } */

                break;

            case PICO_IP_MULTICAST_EXCLUDE:
                /* BLOCK (B-A) */
                record_type = IGMP_BLOCK_OLD_SOURCES;
                IGMPFilter = &IGMPBlock;
                pico_tree_foreach(index, p->MCASTFilter)
                {
                    pico_tree_insert(&IGMPBlock, index->keyValue);
                    sources++;
                }
                pico_tree_foreach(index, &g->MCASTSources) /* A */
                {
                    source = pico_tree_findKey(&IGMPBlock, index->keyValue); /* B */
                    if (source) {
                        pico_tree_delete(&IGMPBlock, source);
                        sources--;
                    }
                }
                if (!pico_tree_empty(&IGMPBlock)) /* record type is BLOCK */
                    break;

                /* ALLOW (A-B) */
                record_type = IGMP_ALLOW_NEW_SOURCES;
                IGMPFilter = &IGMPAllow;
                pico_tree_foreach(index, &g->MCASTSources)
                {
                    pico_tree_insert(&IGMPAllow, index->keyValue);
                    sources++;
                }
                pico_tree_foreach(index, p->MCASTFilter) /* B */
                {
                    source = pico_tree_findKey(&IGMPAllow, index->keyValue); /* A */
                    if (source) {
                        pico_tree_delete(&IGMPAllow, source);
                        sources--;
                    }
                }
                if (!pico_tree_empty(&IGMPAllow)) /* record type is ALLOW */
                    break;

                /* BLOCK (B-A) and ALLOW (A-B) are empty: do not send report (RFC 3376 $5.1) */
                p->f = NULL;
                return 0;

            default:
                pico_err = PICO_ERR_EINVAL;
                return -1;
            }
            break;

        default:
            pico_err = PICO_ERR_EINVAL;
            return -1;
        }

igmp3_report:
        len = (uint16_t)(sizeof(struct igmpv3_report) + sizeof(struct igmpv3_group_record) + (sources * sizeof(struct pico_ip4)));
        p->f = pico_proto_ipv4.alloc(&pico_proto_ipv4, (uint16_t)(IP_OPTION_ROUTER_ALERT_LEN + len));
        p->f->net_len = (uint16_t)(p->f->net_len + IP_OPTION_ROUTER_ALERT_LEN);
        p->f->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN;
        p->f->transport_len = (uint16_t)(p->f->transport_len - IP_OPTION_ROUTER_ALERT_LEN);
        p->f->dev = pico_ipv4_link_find(&p->mcast_link);
        /* p->f->len is correctly set by alloc */

        report = (struct igmpv3_report *)p->f->transport_hdr;
        report->type = IGMP_TYPE_MEM_REPORT_V3;
        report->res0 = 0;
        report->crc = 0;
        report->res1 = 0;
        report->groups = short_be(1);

        record = (struct igmpv3_group_record *)(((uint8_t *)report) + sizeof(struct igmpv3_report));
        record->type = record_type;
        record->aux = 0;
        record->sources = short_be(sources);
        record->mcast_group = p->mcast_group.addr;
        if (IGMPFilter && !pico_tree_empty(IGMPFilter)) {
            uint32_t *source_addr = (uint32_t *)((uint8_t *)record + sizeof(struct igmpv3_group_record));
            i = 0;
            pico_tree_foreach(index, IGMPFilter)
            {
                source_addr[i] = ((struct pico_ip4 *)index->keyValue)->addr;
                i++;
            }
        }

        report->crc = short_be(pico_checksum(report, len));
        break;
    }

    default:
        pico_err = PICO_ERR_EINVAL;
        return -1;
    }
    return 0;
}

/* stop timer, send leave if flag set */
static int stslifs(struct igmp_parameters *p)
{
    struct igmp_timer t = {
        0
    };

    igmp_dbg("IGMP: event = leave group | action = stop timer, send leave if flag set\n");

    t.type = IGMP_TIMER_GROUP_REPORT;
    t.mcast_link = p->mcast_link;
    t.mcast_group = p->mcast_group;
    if (pico_igmp_timer_stop(&t) < 0)
        return -1;

    /* always send leave, even if not last host */
    if (pico_igmp_send_report(p, p->f) < 0)
        return -1;

    pico_igmp_delete_parameter(p);
    igmp_dbg("IGMP: new state = non-member\n");
    return 0;
}

/* send report, set flag, start timer */
static int srsfst(struct igmp_parameters *p)
{
    struct igmp_timer t = {
        0
    };
    struct pico_frame *copy_frame = NULL;

    igmp_dbg("IGMP: event = join group | action = send report, set flag, start timer\n");

    p->last_host = IGMP_HOST_LAST;

    if (pico_igmp_generate_report(p) < 0)
        return -1;

    if (!p->f)
        return 0;

    copy_frame = pico_frame_copy(p->f);
    if (!copy_frame) {
        pico_err = PICO_ERR_ENOMEM;
        return -1;
    }

    if (pico_igmp_send_report(p, copy_frame) < 0)
        return -1;

    t.type = IGMP_TIMER_GROUP_REPORT;
    t.mcast_link = p->mcast_link;
    t.mcast_group = p->mcast_group;
    t.delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000));
    t.f = p->f;
    t.callback = pico_igmp_report_expired;
    pico_igmp_timer_start(&t);

    p->state = IGMP_STATE_DELAYING_MEMBER;
    igmp_dbg("IGMP: new state = delaying member\n");
    return 0;
}

/* merge report, send report, reset timer (IGMPv3 only) */
static int mrsrrt(struct igmp_parameters *p)
{
    struct igmp_timer *t = NULL;
    struct pico_frame *copy_frame = NULL;
    struct pico_ipv4_link *link = NULL;

    igmp_dbg("IGMP: event = update group | action = merge report, send report, reset timer (IGMPv3 only)\n");

    link = pico_ipv4_link_get(&p->mcast_link);
    if (!link)
        return -1;

    if (link->mcast_compatibility != PICO_IGMPV3) {
        igmp_dbg("IGMP: no IGMPv3 compatible router on network\n");
        return -1;
    }

    /* XXX: merge with pending report rfc 3376 $5.1 */

    copy_frame = pico_frame_copy(p->f);
    if (!copy_frame)
        return -1;

    if (pico_igmp_send_report(p, copy_frame) < 0)
        return -1;

    t = pico_igmp_find_timer(IGMP_TIMER_GROUP_REPORT, &p->mcast_link, &p->mcast_group);
    if (!t)
        return -1;

    t->delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000));
    pico_igmp_timer_reset(t);

    p->state = IGMP_STATE_DELAYING_MEMBER;
    igmp_dbg("IGMP: new state = delaying member\n");
    return 0;
}

/* send report, start timer (IGMPv3 only) */
static int srst(struct igmp_parameters *p)
{
    struct igmp_timer t = {
        0
    };
    struct pico_frame *copy_frame = NULL;
    struct pico_ipv4_link *link = NULL;

    igmp_dbg("IGMP: event = update group | action = send report, start timer (IGMPv3 only)\n");

    link = pico_ipv4_link_get(&p->mcast_link);
    if (!link)
        return -1;

    if (link->mcast_compatibility != PICO_IGMPV3) {
        igmp_dbg("IGMP: no IGMPv3 compatible router on network\n");
        return -1;
    }

    if (pico_igmp_generate_report(p) < 0)
        return -1;

    if (!p->f)
        return 0;

    copy_frame = pico_frame_copy(p->f);
    if (!copy_frame)
        return -1;

    if (pico_igmp_send_report(p, copy_frame) < 0)
        return -1;

    t.type = IGMP_TIMER_GROUP_REPORT;
    t.mcast_link = p->mcast_link;
    t.mcast_group = p->mcast_group;
    t.delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 10000));
    t.f = p->f;
    t.callback = pico_igmp_report_expired;
    pico_igmp_timer_start(&t);

    p->state = IGMP_STATE_DELAYING_MEMBER;
    igmp_dbg("IGMP: new state = delaying member\n");
    return 0;
}

/* send leave if flag set */
static int slifs(struct igmp_parameters *p)
{
    igmp_dbg("IGMP: event = leave group | action = send leave if flag set\n");

    /* always send leave, even if not last host */
    if (pico_igmp_send_report(p, p->f) < 0)
        return -1;

    pico_igmp_delete_parameter(p);
    igmp_dbg("IGMP: new state = non-member\n");
    return 0;
}

/* start timer */
static int st(struct igmp_parameters *p)
{
    struct igmp_timer t = {
        0
    };

    igmp_dbg("IGMP: event = query received | action = start timer\n");

    if (pico_igmp_generate_report(p) < 0) {
        igmp_dbg("Failed to generate report\n");
        return -1;
    }

    if (!p->f) {
        igmp_dbg("No pending frame\n");
        return -1;
    }

    t.type = IGMP_TIMER_GROUP_REPORT;
    t.mcast_link = p->mcast_link;
    t.mcast_group = p->mcast_group;
    t.delay = (pico_rand() % ((1u + p->max_resp_time) * 100u));
    t.f = p->f;
    t.callback = pico_igmp_report_expired;
    pico_igmp_timer_start(&t);

    p->state = IGMP_STATE_DELAYING_MEMBER;
    igmp_dbg("IGMP: new state = delaying member\n");
    return 0;
}

/* stop timer, clear flag */
static int stcl(struct igmp_parameters *p)
{
    struct igmp_timer t = {
        0
    };

    igmp_dbg("IGMP: event = report received | action = stop timer, clear flag\n");

    t.type = IGMP_TIMER_GROUP_REPORT;
    t.mcast_link = p->mcast_link;
    t.mcast_group = p->mcast_group;
    if (pico_igmp_timer_stop(&t) < 0)
        return -1;

    p->last_host = IGMP_HOST_NOT_LAST;
    p->state = IGMP_STATE_IDLE_MEMBER;
    igmp_dbg("IGMP: new state = idle member\n");
    return 0;
}

/* send report, set flag */
static int srsf(struct igmp_parameters *p)
{
    igmp_dbg("IGMP: event = timer expired | action = send report, set flag\n");

    if (pico_igmp_send_report(p, p->f) < 0)
        return -1;

    p->state = IGMP_STATE_IDLE_MEMBER;
    igmp_dbg("IGMP: new state = idle member\n");
    return 0;
}

/* reset timer if max response time < current timer */
static int rtimrtct(struct igmp_parameters *p)
{
    struct igmp_timer *t = NULL;
    uint32_t time_to_run = 0;

    igmp_dbg("IGMP: event = query received | action = reset timer if max response time < current timer\n");

    t = pico_igmp_find_timer(IGMP_TIMER_GROUP_REPORT, &p->mcast_link, &p->mcast_group);
    if (!t)
        return -1;

    time_to_run = (uint32_t)(t->start + t->delay - PICO_TIME_MS());
    if ((p->max_resp_time * 100u) < time_to_run) { /* max_resp_time in units of 1/10 seconds */
        t->delay = pico_rand() % ((1u + p->max_resp_time) * 100u);
        pico_igmp_timer_reset(t);
    }

    p->state = IGMP_STATE_DELAYING_MEMBER;
    igmp_dbg("IGMP: new state = delaying member\n");
    return 0;
}

static int discard(struct igmp_parameters *p)
{
    igmp_dbg("IGMP: ignore and discard frame\n");
    pico_frame_discard(p->f);
    return 0;
}

/* finite state machine table */
const callback host_membership_diagram_table[3][6] =
{ /* event                    |Delete Group  |Create Group |Update Group |Query Received  |Report Received  |Timer Expired */
/* state Non-Member      */
    { discard,       srsfst,       srsfst,       discard,         discard,          discard },
/* state Delaying Member */ { stslifs,       mrsrrt,       mrsrrt,       rtimrtct,        stcl,             srsf    },
/* state Idle Member     */ { slifs,         srst,         srst,         st,              discard,          discard }
};

static int pico_igmp_process_event(struct igmp_parameters *p)
{
    struct pico_tree_node *index = NULL;
    struct igmp_parameters *_p = NULL;

    igmp_dbg("IGMP: process event on group address %08X\n", p->mcast_group.addr);
    if (p->event == IGMP_EVENT_QUERY_RECV && p->mcast_group.addr == 0) { /* general query */
        pico_tree_foreach(index, &IGMPParameters) {
            _p = index->keyValue;
            _p->max_resp_time = p->max_resp_time;
            _p->event = IGMP_EVENT_QUERY_RECV;
            igmp_dbg("IGMP: for each mcast_group = %08X | state = %u\n", _p->mcast_group.addr, _p->state);
            host_membership_diagram_table[_p->state][_p->event](_p);
        }
    } else {
        igmp_dbg("IGMP: state = %u (0: non-member - 1: delaying member - 2: idle member)\n", p->state);
        host_membership_diagram_table[p->state][p->event](p);
    }

    return 0;
}

#else
static struct pico_queue igmp_in = {
    0
};
static struct pico_queue igmp_out = {
    0
};

static int pico_igmp_process_in(struct pico_protocol *self, struct pico_frame *f) {
    IGNORE_PARAMETER(self);
    IGNORE_PARAMETER(f);
    pico_err = PICO_ERR_EPROTONOSUPPORT;
    return -1;
}

static int pico_igmp_process_out(struct pico_protocol *self, struct pico_frame *f) {
    IGNORE_PARAMETER(self);
    IGNORE_PARAMETER(f);
    return -1;
}

/* Interface: protocol definition */
struct pico_protocol pico_proto_igmp = {
    .name = "igmp",
    .proto_number = PICO_PROTO_IGMP,
    .layer = PICO_LAYER_TRANSPORT,
    .process_in = pico_igmp_process_in,
    .process_out = pico_igmp_process_out,
    .q_in = &igmp_in,
    .q_out = &igmp_out,
};

int pico_igmp_state_change(struct pico_ip4 *mcast_link, struct pico_ip4 *mcast_group, uint8_t filter_mode, struct pico_tree *_MCASTFilter, uint8_t state) {
    IGNORE_PARAMETER(mcast_link);
    IGNORE_PARAMETER(mcast_group);
    IGNORE_PARAMETER(filter_mode);
    IGNORE_PARAMETER(_MCASTFilter);
    IGNORE_PARAMETER(state);
    pico_err = PICO_ERR_EPROTONOSUPPORT;
    return -1;
}
#endif