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.

Revision:
2:d12a891f2eca
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/pico_igmp.c	Fri May 24 12:53:24 2013 +0000
@@ -0,0 +1,1017 @@
+/*********************************************************************
+PicoTCP. Copyright (c) 2012 TASS Belgium NV. 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"
+
+/* 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)
+
+/* misc */
+#define TIMER_NOT_ACTIVE                 (0)
+#define IP_OPTION_ROUTER_ALERT_LEN       (4)
+#define IGMP_DEFAULT_MAX_RESPONSE_TIME   (100)
+#define IGMP_UNSOLICITED_REPORT_INTERVAL (100)
+#define IGMP_MAX_GROUPS                  (32) /* max 255 */
+#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 */
+
+struct __attribute__((packed)) igmpv2_message {
+  uint8_t type;
+  uint8_t max_resp_time;
+  uint16_t crc;
+  uint32_t mcast_group;
+};
+
+struct __attribute__((packed)) 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;
+  uint32_t source_addr[0];
+};
+
+struct __attribute__((packed)) igmpv3_group_record {
+  uint8_t type;
+  uint8_t aux;
+  uint16_t sources;
+  uint32_t mcast_group;
+  uint32_t source_addr[0];
+};
+
+struct __attribute__((packed)) igmpv3_report {
+  uint8_t type;
+  uint8_t res0;
+  uint16_t crc;
+  uint16_t res1;
+  uint16_t groups;
+  struct igmpv3_group_record record[0];
+};
+
+struct igmp_parameters {
+  uint8_t event;
+  uint8_t state;
+  uint8_t last_host;
+  uint8_t filter_mode;
+  uint8_t max_resp_time;
+  uint8_t mcast_router_version;
+  uint16_t delay;
+  unsigned long timer_start;
+  struct pico_ip4 mcast_link;
+  struct pico_ip4 mcast_group;
+  struct pico_tree *MCASTFilter;
+  struct pico_frame *f;
+};
+
+struct timer_callback_info {
+  unsigned long timer_start;
+  struct pico_frame *f;
+};
+
+static int igmp_parameters_cmp(void *ka,void *kb)
+{
+  struct igmp_parameters *a = ka, *b = kb;
+  if (a->mcast_group.addr < b->mcast_group.addr)
+    return -1;
+  if (a->mcast_group.addr > b->mcast_group.addr)
+    return 1;
+  return 0;
+}
+PICO_TREE_DECLARE(IGMPParameters, igmp_parameters_cmp);
+
+static int igmp_sources_cmp(void *ka, void *kb)
+{
+  struct pico_ip4 *a = ka, *b = kb;
+  if (a->addr < b->addr)
+    return -1;
+  if (a->addr > b->addr)
+    return 1;
+  return 0;
+}
+PICO_TREE_DECLARE(IGMPAllow, igmp_sources_cmp);
+PICO_TREE_DECLARE(IGMPBlock, igmp_sources_cmp);
+
+static struct igmp_parameters *pico_igmp_find_parameters(struct pico_ip4 *mcast_group)
+{
+  struct igmp_parameters test = {0};
+  test.mcast_group.addr = mcast_group->addr;
+  return pico_tree_findKey(&IGMPParameters,&test);
+}
+
+static int pico_igmp_delete_parameters(struct igmp_parameters *info)
+{
+  if(!info){
+    pico_err = PICO_ERR_EINVAL;
+    return -1;
+  }
+  else {
+    if(pico_tree_delete(&IGMPParameters, info)) {
+      pico_free(info);
+    } else {
+      pico_err = PICO_ERR_EEXIST;
+      return -1; /* do not free, error on removing element from tree */
+    }
+  }
+  return 0;
+}
+
+static int pico_igmp_process_event(struct igmp_parameters *params);
+static void generate_event_timer_expired(long unsigned int empty, void *info);
+
+#ifdef PICO_UNIT_TEST_IGMP
+#define igmp_dbg dbg
+static int pico_igmp_process_event(struct igmp_parameters *params);
+static int pico_igmp_analyse_packet(struct pico_frame *f, struct igmp_parameters *params);
+static int pico_igmp_process_in(struct pico_protocol *self, struct pico_frame *f);
+
+int test_pico_igmp_process_in(struct pico_protocol *self, struct pico_frame *f){
+  pico_igmp_process_in(self, f);
+  return 0;
+}
+int test_pico_igmp_set_membershipState(struct pico_ip4 *mcast_group ,uint8_t state){
+  struct igmp_parameters *info = pico_igmp_find_parameters(mcast_group);
+  info->state = state;
+  igmp_dbg("DEBUG_IGMP:STATE = %s\n", (info->state == 0 ? "Non-Member" : (info->state == 1 ? "Delaying MEMBER" : "Idle MEMBER"))); 
+  return 0;
+}
+uint8_t test_pico_igmp_get_membershipState(struct pico_ip4 *mcast_group){
+  struct igmp_parameters *info = pico_igmp_find_parameters(mcast_group);
+  igmp_dbg("DEBUG_IGMP:STATE = %s\n", (info->state == 0 ? "Non-Member" : (info->state == 1 ? "Delaying Member" : "Idle Member"))); 
+  return info->state;
+}
+int test_pico_igmp_process_event(struct igmp_parameters *params) {
+   pico_igmp_process_event(params);
+   return 0;
+}
+
+int test_pico_igmp_analyse_packet(struct pico_frame *f, struct igmp_parameters *params){
+  pico_igmp_analyse_packet(f, params);
+  return 0;
+}
+#else
+//#define igmp_dbg(...) do{}while(0)
+#define igmp_dbg dbg
+#endif
+
+/* queues */
+static struct pico_queue igmp_in = {};
+static struct pico_queue igmp_out = {};
+
+static int pico_igmp_analyse_packet(struct pico_frame *f, struct igmp_parameters *params)
+{
+  struct igmpv2_message *hdr = (struct igmpv2_message *) f->transport_hdr;
+  switch (hdr->type) {
+    case IGMP_TYPE_MEM_QUERY:
+       params->event = IGMP_EVENT_QUERY_RECV;
+       break;
+    case IGMP_TYPE_MEM_REPORT_V1:
+       params->event = IGMP_EVENT_REPORT_RECV;
+       break;
+    case IGMP_TYPE_MEM_REPORT_V2:
+       params->event = IGMP_EVENT_REPORT_RECV;
+       break;
+    default:
+       pico_frame_discard(f);
+       pico_err = PICO_ERR_EINVAL;
+       return -1;
+  }
+  params->mcast_group.addr = hdr->mcast_group;
+  params->max_resp_time = hdr->max_resp_time;
+  params->f = f;
+  return 0;
+}
+
+static int check_igmp_checksum(struct pico_frame *f)
+{
+  struct igmpv2_message *igmp_hdr = (struct igmpv2_message *) f->transport_hdr;
+  uint16_t header_checksum;
+
+  if (!igmp_hdr) {
+    pico_err = PICO_ERR_EINVAL;
+    return -1;
+  }
+  header_checksum = igmp_hdr->crc;
+  igmp_hdr->crc=0;
+
+  if (header_checksum == short_be(pico_checksum(igmp_hdr, sizeof(struct igmpv2_message)))) {
+    igmp_hdr->crc = header_checksum;
+    return 0;
+  } else {
+    igmp_hdr->crc = header_checksum;
+    pico_err = PICO_ERR_EFAULT;
+    return -1;
+  }
+}
+
+static int pico_igmp_checksum(struct igmp_parameters *params)
+{
+  struct igmpv2_message *igmp_hdr = (struct igmpv2_message *) params->f->transport_hdr;
+  if (!igmp_hdr) {
+    pico_err = PICO_ERR_EINVAL;
+    return -1;
+  }
+  igmp_hdr->crc = 0;
+  igmp_hdr->crc = short_be(pico_checksum(igmp_hdr, sizeof(struct igmpv2_message)));
+  return 0;
+}
+
+static int pico_igmp_process_in(struct pico_protocol *self, struct pico_frame *f)
+{
+  struct igmp_parameters params;
+ 
+  if (check_igmp_checksum(f) == 0) {
+    if (!pico_igmp_analyse_packet(f, &params)) {
+      pico_igmp_process_event(&params);
+    }
+  } else {
+    igmp_dbg("IGMP: failure on checksum\n");
+    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 */
+  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 *rbtparams = NULL, params = {0};
+  
+  if (mcast_group->addr == IGMP_ALL_HOST_GROUP)
+    return 0;
+
+  switch (state) {
+    case PICO_IGMP_STATE_CREATE:
+      params.event = IGMP_EVENT_CREATE_GROUP;
+      break;
+
+    case PICO_IGMP_STATE_UPDATE:
+      params.event = IGMP_EVENT_UPDATE_GROUP;
+      break;
+    
+    case PICO_IGMP_STATE_DELETE:
+      params.event = IGMP_EVENT_DELETE_GROUP;
+      break;
+
+    default:
+      return -1;
+  }
+
+  params.mcast_router_version = PICO_IGMPV2; /* init value, can change when generating frame */
+  params.mcast_group = *mcast_group;
+  params.mcast_link = *mcast_link;
+  params.filter_mode = filter_mode;
+  params.MCASTFilter = MCASTFilter;
+
+  rbtparams = pico_igmp_find_parameters(mcast_group);
+  if (rbtparams) {
+    rbtparams->event = params.event;
+    rbtparams->mcast_link = params.mcast_link;
+    rbtparams->filter_mode = params.filter_mode;
+    rbtparams->MCASTFilter = params.MCASTFilter;
+  }
+
+  return pico_igmp_process_event(&params);
+}
+
+static int start_timer(struct igmp_parameters *params,const uint16_t delay)
+{
+  struct igmp_parameters *info = pico_igmp_find_parameters(&(params->mcast_group));
+  struct timer_callback_info *timer_info= pico_zalloc(sizeof(struct timer_callback_info));
+
+  timer_info->timer_start = PICO_TIME_MS();
+  timer_info->f = params->f;
+  info->delay = delay;
+  info->timer_start = timer_info->timer_start;
+  pico_timer_add(delay, &generate_event_timer_expired, timer_info);
+  return 0;
+}
+
+static int stop_timer(struct pico_ip4 *mcast_group)
+{
+  struct igmp_parameters *info = pico_igmp_find_parameters(mcast_group);
+  if (!info)
+    return -1;
+  info->timer_start = TIMER_NOT_ACTIVE;
+  return 0;
+}
+
+static int reset_timer(struct igmp_parameters *params)
+{
+  uint8_t ret = 0;
+  uint16_t delay = pico_rand() % (params->max_resp_time*100); 
+
+  ret |= stop_timer(&(params->mcast_group));
+  ret |= start_timer(params, delay);
+  return ret;
+}
+
+static int send_membership_report(struct igmp_parameters *params, struct pico_frame *f)
+{
+  struct pico_ip4 dst = {0};
+  struct pico_ip4 mcast_group = {0};
+
+  mcast_group.addr = params->mcast_group.addr;
+  switch (params->mcast_router_version) {
+    case PICO_IGMPV2:
+    {
+      if (params->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);
+  stop_timer(&mcast_group);
+  return 0;
+}
+
+static int generate_igmp_report(struct igmp_parameters *params)
+{
+  struct pico_ipv4_link *link = NULL;
+  int i = 0;
+
+  link = pico_ipv4_link_get(&params->mcast_link);
+  if (!link) {
+    pico_err = PICO_ERR_EINVAL;
+    return -1;
+  }
+  params->mcast_router_version = link->mcast_router_version;
+
+  switch (params->mcast_router_version) {
+    case PICO_IGMPV1:
+      pico_err = PICO_ERR_EPROTONOSUPPORT;
+      return -1;
+      
+    case PICO_IGMPV2:
+    {
+      struct igmpv2_message *report = NULL;
+      uint8_t report_type = IGMP_TYPE_MEM_REPORT_V2;
+      if (params->event == IGMP_EVENT_DELETE_GROUP)
+        report_type = IGMP_TYPE_LEAVE_GROUP;
+
+      params->f = pico_proto_ipv4.alloc(&pico_proto_ipv4, IP_OPTION_ROUTER_ALERT_LEN + sizeof(struct igmpv2_message));
+      params->f->net_len += IP_OPTION_ROUTER_ALERT_LEN;
+      params->f->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN;
+      params->f->transport_len -= IP_OPTION_ROUTER_ALERT_LEN;
+      params->f->dev = pico_ipv4_link_find(&params->mcast_link);
+      /* params->f->len is correctly set by alloc */
+
+      report = (struct igmpv2_message *)params->f->transport_hdr;
+      report->type = report_type;
+      report->max_resp_time = IGMP_DEFAULT_MAX_RESPONSE_TIME;
+      report->mcast_group = params->mcast_group.addr;
+
+      pico_igmp_checksum(params);
+      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;
+      int len = 0;
+
+      test.mcast_addr = params->mcast_group;
+      g = pico_tree_findKey(link->MCASTGroups, &test);
+      if (!g) {
+        pico_err = PICO_ERR_EINVAL;
+        return -1;
+      }
+
+      if (params->event == IGMP_EVENT_DELETE_GROUP) { /* "non-existent" state of filter mode INCLUDE and empty source list */
+        params->filter_mode = PICO_IP_MULTICAST_INCLUDE;
+        params->MCASTFilter = NULL;
+      }
+
+      /* 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 (params->filter_mode) {
+            case PICO_IP_MULTICAST_INCLUDE:
+              if (params->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 (params->MCASTFilter) {
+                  pico_tree_foreach(index, params->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 (params->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, params->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, params->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) */
+              params->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, params->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 (params->filter_mode) {
+            case PICO_IP_MULTICAST_INCLUDE:
+              /* TO_IN (B) */
+              record_type = IGMP_CHANGE_TO_INCLUDE_MODE;
+              IGMPFilter = &IGMPAllow;
+              if (params->MCASTFilter) {
+                pico_tree_foreach(index, params->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, params->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, params->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) */
+              params->f = NULL;
+              return 0;
+
+            default:
+              pico_err = PICO_ERR_EINVAL;
+              return -1;
+          }
+          break;
+
+        default:
+          pico_err = PICO_ERR_EINVAL;
+          return -1;
+      }
+
+      len = sizeof(struct igmpv3_report) + sizeof(struct igmpv3_group_record) + (sources * sizeof(struct pico_ip4));
+      params->f = pico_proto_ipv4.alloc(&pico_proto_ipv4, IP_OPTION_ROUTER_ALERT_LEN + len);
+      params->f->net_len += IP_OPTION_ROUTER_ALERT_LEN;
+      params->f->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN;
+      params->f->transport_len -= IP_OPTION_ROUTER_ALERT_LEN;
+      params->f->dev = pico_ipv4_link_find(&params->mcast_link);
+      /* params->f->len is correctly set by alloc */
+
+      report = (struct igmpv3_report *)params->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 = &report->record[0];
+      record->type = record_type;
+      record->aux = 0;
+      record->sources = short_be(sources);
+      record->mcast_group = params->mcast_group.addr;
+      if (!pico_tree_empty(IGMPFilter)) {
+        i = 0;
+        pico_tree_foreach(index, IGMPFilter)
+        {
+          record->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;
+}
+
+/* XXX TO BE DELETED */
+static int create_igmp_frame(struct igmp_parameters *params, struct pico_frame **f, struct pico_ip4 src, struct pico_ip4 *mcast_group, uint8_t type)
+{
+  uint8_t ret = 0;
+  struct igmpv2_message *igmp_hdr = NULL;
+
+  *f = pico_proto_ipv4.alloc(&pico_proto_ipv4, IP_OPTION_ROUTER_ALERT_LEN + sizeof(struct igmpv2_message));
+  (*f)->net_len += IP_OPTION_ROUTER_ALERT_LEN;
+  (*f)->transport_hdr += IP_OPTION_ROUTER_ALERT_LEN;
+  (*f)->transport_len -= IP_OPTION_ROUTER_ALERT_LEN;
+  (*f)->len += IP_OPTION_ROUTER_ALERT_LEN;
+  (*f)->dev = pico_ipv4_link_find(&src);
+
+  igmp_hdr = (struct igmpv2_message *) (*f)->transport_hdr;
+  igmp_hdr->type = type;
+  igmp_hdr->max_resp_time = IGMP_DEFAULT_MAX_RESPONSE_TIME;
+  igmp_hdr->mcast_group = mcast_group->addr;
+
+  ret |= pico_igmp_checksum(params);
+  return ret;
+}
+
+static void generate_event_timer_expired(long unsigned int empty, void *data)
+{
+  struct timer_callback_info *info = (struct timer_callback_info *) data;
+  struct igmp_parameters params = {0};
+  struct pico_frame* f = (struct pico_frame*)info->f;
+  struct igmpv2_message *igmp_hdr = (struct igmpv2_message *) f->transport_hdr;
+
+  params.event = IGMP_EVENT_TIMER_EXPIRED;
+  params.mcast_group.addr = igmp_hdr->mcast_group;
+  params.timer_start = info->timer_start;
+  params.f = info->f;
+
+  pico_igmp_process_event(&params);
+  pico_free(info);  
+}
+
+/* state callback prototypes */
+typedef int (*callback)(struct igmp_parameters *);
+
+/* stop timer, send leave if flag set */
+static int stslifs(struct igmp_parameters *params)
+{
+  struct igmp_parameters *rbtparams = NULL;
+
+  igmp_dbg("IGMP: event = leave group | action = stop timer, send leave if flag set\n");
+
+  rbtparams = pico_igmp_find_parameters(&(params->mcast_group));
+  if (!rbtparams)
+    return -1;
+
+  if (stop_timer(&(rbtparams->mcast_group)) < 0)
+    return -1;
+
+  /* always send leave, even if not last host */
+  if (generate_igmp_report(rbtparams) < 0)
+    return -1;
+  if (!rbtparams->f)
+    return 0;
+  if (send_membership_report(rbtparams, rbtparams->f) < 0)
+    return -1;
+
+  /* delete from tree */
+  pico_igmp_delete_parameters(rbtparams);
+  igmp_dbg("IGMP: new state = non-member\n");
+  return 0;
+}
+
+/* send report, set flag, start timer */
+static int srsfst(struct igmp_parameters *params)
+{
+  struct igmp_parameters *rbtparams = NULL;
+  struct pico_frame *copy_frame = NULL;
+
+  igmp_dbg("IGMP: event = join group | action = send report, set flag, start timer\n");
+
+  rbtparams = pico_zalloc(sizeof(struct igmp_parameters));
+  if (!rbtparams) {
+    pico_err = PICO_ERR_ENOMEM;
+    return -1;
+  }
+  memcpy(rbtparams, params, sizeof(struct igmp_parameters));
+  rbtparams->state = IGMP_STATE_NON_MEMBER;
+  rbtparams->last_host = IGMP_HOST_LAST;
+  rbtparams->timer_start = TIMER_NOT_ACTIVE;
+  pico_tree_insert(&IGMPParameters, rbtparams);
+
+  if (generate_igmp_report(rbtparams) < 0)
+    return -1;
+  if (!rbtparams->f)
+    return 0;
+  copy_frame = pico_frame_copy(rbtparams->f);
+  if (!copy_frame) {
+    pico_err = PICO_ERR_ENOMEM;
+    return -1;
+  }
+  if (send_membership_report(rbtparams, copy_frame) < 0)
+    return -1;
+
+  rbtparams->delay = (pico_rand() % (IGMP_UNSOLICITED_REPORT_INTERVAL * 100)); 
+  if (start_timer(rbtparams, rbtparams->delay) < 0) /* XXX: change to one parameter? */
+    return -1;
+  rbtparams->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 *params)
+{
+  struct igmp_parameters *rbtparams = NULL;
+  struct pico_frame *copy_frame = NULL;
+
+  igmp_dbg("IGMP: event = update group | action = merge report, send report, reset timer (IGMPv3 only)\n");
+
+  rbtparams = pico_igmp_find_parameters(&(params->mcast_group));
+  if (!rbtparams)
+    return -1;
+
+  if (rbtparams->mcast_router_version != PICO_IGMPV3) {
+    igmp_dbg("IGMP: no IGMPv3 compatible router on network\n");
+    pico_err = PICO_ERR_ENOPROTOOPT;
+    return -1;
+  }
+
+  /* XXX: merge with pending report rfc 3376 p20 */
+
+  if (generate_igmp_report(rbtparams) < 0)
+    return -1;
+  if (!rbtparams->f)
+    return 0;
+  copy_frame = pico_frame_copy(rbtparams->f);
+  if (!copy_frame) {
+    pico_err = PICO_ERR_ENOMEM;
+    return -1;
+  }
+  if (send_membership_report(rbtparams, copy_frame) < 0)
+    return -1;
+
+  /* XXX: reset timer */
+
+  rbtparams->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 *params)
+{
+  struct igmp_parameters *rbtparams = NULL;
+  struct pico_frame *copy_frame = NULL;
+
+  igmp_dbg("IGMP: event = update group | action = send report, start timer (IGMPv3 only)\n");
+
+  rbtparams = pico_igmp_find_parameters(&(params->mcast_group));
+  if (!rbtparams)
+    return -1;
+
+  if (rbtparams->mcast_router_version != PICO_IGMPV3) {
+    igmp_dbg("IGMP: no IGMPv3 compatible router on network\n");
+    pico_err = PICO_ERR_ENOPROTOOPT;
+    return -1;
+  }
+
+  if (generate_igmp_report(rbtparams) < 0)
+    return -1;
+  if (!rbtparams->f)
+    return 0;
+  copy_frame = pico_frame_copy(rbtparams->f);
+  if (!copy_frame) {
+    pico_err = PICO_ERR_ENOMEM;
+    return -1;
+  }
+  if (send_membership_report(rbtparams, copy_frame) < 0)
+    return -1;
+
+  /* XXX: start timer */
+
+  rbtparams->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 *params)
+{
+  struct igmp_parameters *rbtparams = NULL;
+
+  igmp_dbg("IGMP: event = leave group | action = send leave if flag set\n");
+
+  rbtparams = pico_igmp_find_parameters(&(params->mcast_group));
+  if (!rbtparams)
+    return -1;
+
+  /* always send leave, even if not last host */
+  if (generate_igmp_report(rbtparams) < 0)
+    return -1;
+  if (!rbtparams->f)
+    return 0;
+  if (send_membership_report(rbtparams, rbtparams->f) < 0)
+    return -1;
+
+  /* delete from tree */
+  pico_igmp_delete_parameters(rbtparams);
+  igmp_dbg("IGMP: new state = non-member\n");
+  return 0;
+}
+
+/* start timer */
+static int st(struct igmp_parameters *params)
+{
+  uint8_t ret = 0;
+  struct igmp_parameters *info = pico_igmp_find_parameters(&(params->mcast_group));
+
+  igmp_dbg("IGMP: event = query received | action = start timer\n");
+
+  ret |= create_igmp_frame(params, &(params->f), info->mcast_link, &(params->mcast_group), IGMP_TYPE_MEM_REPORT_V2);
+  info->delay = (pico_rand() % (params->max_resp_time*100)); 
+  ret |= start_timer(params, info->delay);
+
+  if (0 == ret) {
+    info->state = IGMP_STATE_DELAYING_MEMBER;
+    igmp_dbg("IGMP: new state = delaying member\n");
+    return 0;
+  } else {
+    pico_err = PICO_ERR_ENOENT;
+    return -1;
+  }
+}
+
+/* stop timer, clear flag */
+static int stcl(struct igmp_parameters *params)
+{
+  uint8_t ret = 0;
+  struct igmp_parameters *info = pico_igmp_find_parameters(&(params->mcast_group));
+
+  igmp_dbg("IGMP: event = report received | action = stop timer, clear flag\n");
+
+  ret |= stop_timer(&(params->mcast_group));
+  info->last_host = IGMP_HOST_NOT_LAST;
+
+  if (0 == ret) {
+    info->state = IGMP_STATE_IDLE_MEMBER;
+    igmp_dbg("IGMP: new state = idle member\n");
+    return 0;
+  } else {
+    pico_err = PICO_ERR_ENOENT;
+    return -1;
+  }
+}
+
+/* send report, set flag */
+static int srsf(struct igmp_parameters *params)
+{
+  uint8_t ret = 0;
+  struct igmp_parameters *info = pico_igmp_find_parameters(&(params->mcast_group));
+
+  igmp_dbg("IGMP: event = timer expired | action = send report, set flag\n");
+
+  /* start time of parameter == start time of expired timer? */
+  if (info->timer_start == params->timer_start) {
+    ret |= send_membership_report(params, params->f);
+  } else {
+    pico_frame_discard(params->f);
+  }
+
+  if (0 == ret) {
+    info->state = IGMP_STATE_IDLE_MEMBER;
+    igmp_dbg("IGMP: new state = idle member\n"); 
+    return 0;
+  } else {
+    pico_err = PICO_ERR_ENOENT;
+    return -1;
+  }
+}
+
+/* reset timer if max response time < current timer */
+static int rtimrtct(struct igmp_parameters *params)
+{
+  uint8_t ret = 0;
+  struct igmp_parameters *info = pico_igmp_find_parameters(&(params->mcast_group));
+  unsigned long current_time_left = ((unsigned long)info->delay - (PICO_TIME_MS() - (unsigned long)info->timer_start));
+
+  igmp_dbg("IGMP: event = query received | action = reset timer if max response time < current timer\n");
+
+  if (((unsigned long)(params->max_resp_time * 100)) < current_time_left) {
+    ret |= create_igmp_frame(params, &(params->f), params->mcast_link, &(params->mcast_group), IGMP_TYPE_MEM_REPORT_V2);
+    ret |= reset_timer(params);
+  }
+
+  if (0 == ret) {
+    info->state = IGMP_STATE_DELAYING_MEMBER;
+    igmp_dbg("IGMP: new state = delaying member\n"); 
+    return 0;
+  } else {
+    pico_err = PICO_ERR_ENOENT;
+    return -1;
+  }
+}
+
+static int discard(struct igmp_parameters *params){
+  igmp_dbg("IGMP: ignore and discard frame\n");
+  pico_frame_discard(params->f);
+  return 0;
+}
+
+static int err_non(struct igmp_parameters *params){
+  igmp_dbg("IGMP ERROR: state = non-member, event = %u\n", params->event);
+  pico_err = PICO_ERR_ENOENT;
+  return -1;
+}
+
+/* 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      */ { err_non,       srsfst,       srsfst,       discard,         err_non,          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 *params)
+{
+  struct pico_tree_node *index;
+  uint8_t ret = 0;
+  struct igmp_parameters *info = pico_igmp_find_parameters(&(params->mcast_group));
+
+  igmp_dbg("IGMP: process event on group address %08X\n", params->mcast_group.addr);
+  if (NULL == info) {
+    if (params->event == IGMP_EVENT_QUERY_RECV) { /* general query (mcast_group field is zero) */
+      pico_tree_foreach(index,&IGMPParameters) {
+        info = index->keyValue;
+        params->mcast_link.addr = info->mcast_link.addr;
+        params->mcast_group.addr = info->mcast_group.addr;
+        igmp_dbg("IGMP: for each mcast_group = %08X | state = %u\n", params->mcast_group.addr, info->state);
+        ret |= host_membership_diagram_table[info->state][params->event](params);
+      }
+    } else { /* first time this group enters the state diagram */
+      igmp_dbg("IGMP: state = Non-Member\n");
+      ret |= host_membership_diagram_table[IGMP_STATE_NON_MEMBER][params->event](params);
+    }
+  } else {
+    igmp_dbg("IGMP: state = %u (0: non-member - 1: delaying member - 2: idle member)\n", info->state); 
+    ret |= host_membership_diagram_table[info->state][params->event](params);
+  }
+
+  if( 0 == ret) {
+    return 0;
+  } else {
+    igmp_dbg("IGMP ERROR: pico_igmp_process_event failed!\n");
+    return -1;
+  }
+}
+