Free (GPLv2) TCP/IP stack developed by TASS Belgium
Fork of PicoTCP by
Diff: modules/pico_igmp.c
- 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, ¶ms)) { + pico_igmp_process_event(¶ms); + } + } 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(¶ms); +} + +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(¶ms->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(¶ms->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(¶ms->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(¶ms); + 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; + } +} +