/*
 * lip_igmp.c
 *  Файл содержит реализацию протокола IGMPv3 в соответвии с rfc3376.
 *  Так же стек может работать в режиме совместимости
 *     с IGMPv1 (rfc1112) и с IGMPv2 (rfc2236)
 *
 *  Особенности реализации:
 *      Стек сохраняет информацию по разрешенным адресам для каждой
 *      пары сокет-мультикаст адрес в статической памяти размера
 *      LIP_IGMP_SOCKET_STATE_MEMORY_SIZE.
 *      Изменение этой информации осуществляется с помощью lip_igmp_listen.
 *      При изменении анализируется изменение информации по mcast-адресам
 *      (отдельно не хранится для экономии памяти) и отчеты об обновлении
 *      сохраняются в память размера LIP_IGMP_RETRANS_STATE_MEMORY_SIZE.
 *      Если для изменения в одной из этих областей памяти не будет достаточно места,
 *      то функция вернет ошибку и никаких изменений не произойдет.
 *
 *      При изменении адреса в планируется объявление о новом состоянии
 *      по новому адресу и запрет по старому. Для возможности такой
 *      послки LIP_IGMP_RETRANS_STATE_MEMORY_SIZE
 *      должен быть достаточного размера (>= LIP_IGMP_SOCKET_STATE_MEMORY_SIZE)
 *
 *      Количество повторных передач считается по адресам, а не по записям
 *      (в rfc не совсем очевидно) - то есть при добавлении a1, а затем a2
 *      при 2-х повторах может быть передано a1, затем a1,a2, затем только a2
 *
 *      Для посылки ответов, запись с параметрами ответов и временем
 *      отправления сохраняется в f_response_state размером
 *      LIP_IGMP_RESPONSE_STATE_MEMORY_SIZE. Этой памяти должно быть
 *      достаточно для отправки ответов на все IGMP прослушивающиеся адреса
 *
 *      В режиме совместимости с IGMPv1, IGMPv2 используются только память
 *      f_response_state, так как есть только один вид отчета, который сохраняется
 *      так же как IGMPv3 Group-Specific Query. Какой тип отчета будет послан зависит
 *      от режима совместимости на момент посылки. *
 *
 *  todo:
 *    Нужно ли слать v3 отчеты о изменении состояния - свич похоже запоминает адреса
 *         из IGMv3 и если в режиме совместимости с v2 изменить адреса, то может не узнать,
 *         что они сменились?
 *    Отбрасывание ответов на запросы для непрослушиваемого адреса и не используемых
 *              адресов источников на стадии планирования (сейчас это делается на стадии посылки)
 *    Проверить ответы на запросы IGMPv3
 *    Выход из групп по закрытию
 *    Рекация на смену адреса
 *  Created on: 17.03.2011
 *      Author: Borisov
 */

#include "lip/misc/lip_rand_cfg_defs.h"

#ifdef LIP_USE_IGMP

#if !LIP_RAND_ENABLE
    #error "igmp protocol requires random generator. define LIP_CFG_RAND_ENABLE in lip_config.h"
#endif
#if !LIP_RAND_UINT32_RANGE_SUPPORT
    #warning "uint32_t random range disabled. define LIP_CFG_RAND_UINT32_RANGE_SUPPORT in lip_config.h"
#endif
#include "lip/lip_log.h"
#include "lip/net/ipv4/lip_igmp.h"
#include "lip/net/ipv4/lip_ipv4.h"
#include "lcspec.h"
#include "ltimer.h"
#include "string.h"
#include "lip/misc/lip_rand.h"
#include "lip/ll/lip_ll.h"
#include "lip/link/lip_eth.h"

/*****************************************************************
 * параметры IGMP - значения по умолчанию из rfc3376 пункт 8
 * могут быть переопределены в lip_conf.h
 *****************************************************************/
//количество повторных передач отчета о изменении состояния (пункт 8.1)
#ifndef LIP_IGMP_ROBUSTNESS_VARIABLE
    #define LIP_IGMP_ROBUSTNESS_VARIABLE  2
#endif
//максимальный интервал между повторными отчетами о изменении состояния (мс)
#ifndef LIP_IGMP_UNSOLICITED_REPORT_INTERVAL
    #define LIP_IGMP_UNSOLICITED_REPORT_INTERVAL  1000
#endif

//интервал между общими запросами по-умолчанию
#ifndef LIP_IGMP_DEFAULT_QUERY_INTERVAL
    #define LIP_IGMP_DEFAULT_QUERY_INTERVAL 125000
#endif

//минимальное время, в течении которого находимся в режиме совместимости
#ifndef LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MIN
    #define LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MIN  125000
#endif

//максимальное время, в течении которого находимся в режиме совместимости
#ifndef LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MAX
    #define LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MAX   (48*60*1000)
#endif

#ifndef LIP_IGMP_MIN_REPORT_TIMEOUT
    #define LIP_IGMP_MIN_REPORT_TIMEOUT 20
#endif



//параметры IP для посылаемых IGMP-пакетов
#define LIP_IGMP_TTL        1
#define LIP_IGMP_TOS        LIP_IP_PRECEDENCE_INTERNETWORK_CONTROL

//типы сообщений IGMP
#define LIP_IGMP_MSG_TYPE_QUERY             0x11
#define LIP_IGMP_MSG_TYPE_V3_REPORT         0x22
#define LIP_IGMP_MSG_TYPE_V1_REPORT         0x12
#define LIP_IGMP_MSG_TYPE_V2_REPORT         0x16
#define LIP_IGMP_MSG_TYPE_V2_LEAVE_GROUP    0x17

//типы записи о mcast группе
#define LIP_IGMP_REC_TYPE_IS_INCLUDE        0x1
#define LIP_IGMP_REC_TYPE_IS_EXCLUDE        0x2
#define LIP_IGMP_REC_TYPE_CHANGE_TO_INCLUDE 0x3
#define LIP_IGMP_REC_TYPE_CHANGE_TO_EXCLUDE 0x4
#define LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC     0x5
#define LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC     0x6

//размер сообщения с запросом для IGMPv1 и IGMPv2
#define LIP_IGMP_V1V2_QUERY_SIZE  8
#define LIP_IGMP_V1V2_REPORT_SIZE  8
//минимальный размер сообщения с запросом для IGMPv3
#define LIP_IGMP_V3_QUERY_MIN_SIZE 12

/**************8 флаги, которые могут быть в переменной f_flag ******************/
//запущен ли таймер совместимости с IGMPv1
#define LIP_IGMP_FLAG_V1_TIMER_RUN             0x1
//запущен ли таймер совместимости с IGMPv2
#define LIP_IGMP_FLAG_V2_TIMER_RUN             0x2
//признак, что время приема последнего запроса действительно
#define LIP_IGMP_FLAG_LAST_QUERY_TIME_VALID    0x4

#include "lcspec_pack_start.h"
//формат сообщения-запроса для IGMPv3
struct st_lip_igmp_query {
    uint8_t type; //тип сообщения равен LIP_IGMP_MSGTYPE_QUERY
    uint8_t max_resp_code;
    uint16_t checksum;
    uint8_t group_addr[LIP_IPV4_ADDR_SIZE]; //адрес группы
    uint8_t flags; //флаги S, QRV
    uint8_t QQIC;
    uint16_t src_num;
    uint8_t src_addrs[0][LIP_IPV4_ADDR_SIZE];
} LATTRIBUTE_PACKED;
typedef struct st_lip_igmp_query t_lip_igmp_query;


//формат информации о одной mcast группе
struct st_lip_igmp_group_rec {
    uint8_t type;
    uint8_t aux_len;
    uint16_t src_cnt;
    uint8_t mcast_addr[LIP_IPV4_ADDR_SIZE];
    uint8_t src_addrs[0][LIP_IPV4_ADDR_SIZE];
} LATTRIBUTE_PACKED;
typedef struct st_lip_igmp_group_rec t_lip_igmp_group_rec;

//формат отчета о прослушиваемых адресах для IGMPv3
struct st_lip_igmp_v3_report
{
    uint8_t type; //тип сообщения равен LIP_IGMP_MSGTYPE_V3_REPORT
    uint8_t reserv1;
    uint16_t checksum;
    uint16_t reserv2;
    uint16_t group_num; //кол-во групп
    t_lip_igmp_group_rec group_recs[0]; //группы адресов
} LATTRIBUTE_PACKED;
typedef struct st_lip_igmp_v3_report t_lip_igmp_v3_report;

//формат отчета для IGMPv1 и IGMPv2
struct st_lip_igmp_v1_2_report
{
    uint8_t type;
    uint8_t reserv;
    uint16_t checksum;
    uint8_t group_addr[LIP_IPV4_ADDR_SIZE];
} LATTRIBUTE_PACKED;
typedef struct st_lip_igmp_v1_2_report t_lip_igmp_v1_2_report;

#include "lcspec_pack_restore.h"

struct st_lip_igmp_src_retrans {
    uint8_t addr[LIP_IPV4_ADDR_SIZE];
    uint8_t retr_ctn;
};
typedef struct st_lip_igmp_src_retrans t_lip_igmp_src_retrans;

struct st_lip_igmp_retrans_record {
    t_ltimer retr_tmr;
    uint8_t mcast_addr[LIP_IPV4_ADDR_SIZE];    
    uint16_t src_cnt;    
    uint8_t type;
    uint8_t retr_ctn;
    t_lip_igmp_src_retrans src[0];
};
typedef struct st_lip_igmp_retrans_record t_lip_igmp_retrans_record;



/*************************************************************
 * информация о запланированном ответе на запрос по IGMP
 *************************************************************/
typedef struct
{
    uint8_t mcast_addr[LIP_IPV4_ADDR_SIZE]; //mcast адрес, для которого поставлен запрос
    t_ltimer tmr; //таймер, определяющий время, на которое поставлен ответ
    uint16_t retr_cnt; //кол-во повторных передач перед выкидыванием
    uint16_t src_cnt; //кол-во адресов источников
    uint8_t src[0][LIP_IPV4_ADDR_SIZE]; //список адресов источников
} t_lip_igmp_response_rec;


//тип для хранения mcast группы - адрес + фльтр источников;
typedef struct
{
    uint8_t addr[LIP_IPV4_ADDR_SIZE]; //mcast адрес
    uint8_t flags; //резерв
    uint8_t filter_mode; //режим фльтра (LIP_IGMP_FILTERMODE_INCLUDE/EXCLUDE)
    uint16_t src_cnt; //кол-во фльтруемых адресов источников
    const uint8_t (*src)[LIP_IPV4_ADDR_SIZE]; //фльтруемые адреса источников
} t_new_rec_info;

//структура, описывающая состояние прослушиваемых адресов по сокету
typedef struct
{
    t_lip_igmp_sock sock; //сокет
    uint8_t addr[LIP_IPV4_ADDR_SIZE]; //mcast адрес
    uint8_t flags; //резерв
    uint8_t filter_mode; //режим фльтра (LIP_IGMP_FILTERMODE_INCLUDE/EXCLUDE)
    uint16_t src_cnt; //кол-во фльтруемых адресов источников
    uint8_t src_addrs[0][LIP_IPV4_ADDR_SIZE]; //фльтруемые адреса источников
} t_igmp_sock_state;


//память под состояние сокетов
static uint8_t f_sock_states[LIP_IGMP_SOCKET_STATE_MEMORY_SIZE];
//память под запланированые отчеты об изменении состояния
static uint8_t f_retrans_state[LIP_IGMP_RETRANS_STATE_MEMORY_SIZE];
//память под запланированные ответы на запросы
static uint8_t f_response_state[LIP_IGMP_RESPONSE_STATE_MEMORY_SIZE];
//информация ip-уровня, используемая для посылки отчетов
static t_lip_ip_info f_ip_info;
//указатель на начало свободного места в f_retrans_state (сразу за последним отчетом)
static t_lip_igmp_retrans_record *f_last_retrans_group;
//указатель на начало свбобдного места в f_response_state (сразу за последним ответом)
static t_lip_igmp_response_rec* f_last_response;


//mcast адрес, соответствующий всем хостам в локальной сети
static const uint8_t f_mcast_all_host[] = {224, 0, 0, 1};
//mcast адрес, соответствущий всем маршрутизаторам сети
static const uint8_t f_mcast_all_routers[] = {224, 0, 0, 2};
//mcast адрес, по которому передаются отчеты в версии 3
static const uint8_t f_igmpv3_router_mcast[] = {224, 0, 0, 22};
//временные переменные, используемые при разрешении и запрещении адресов
static t_lip_igmp_retrans_record *f_allow_rec, *f_block_rec;
//временная переменная для рассчета mac-адреса по mcast ip-адресу
static uint8_t f_mac[LIP_MAC_ADDR_SIZE];
//флаги, общие для протокола - LIP_IGMP_FLAG_XXX
static uint8_t f_flags;
//таймер режма совместимости с IGMPv1
static t_ltimer f_v1_tmr;
static t_ltimer f_v2_tmr;
//последний интервал между двумя запросами
static t_lclock_ticks f_last_general_query_received;


static t_lip_errs f_schedule_group_resp(const uint8_t* mcast_addr,
        const uint8_t src_addrs[][LIP_IPV4_ADDR_SIZE],
        uint16_t src_cnt,
        t_lclock_ticks max_tout,
        uint16_t retr_cnt);

LINLINE static t_lip_igmp_retrans_record* f_next_retrans_record(t_lip_igmp_retrans_record* record)
{
    /*return (uint8_t*)&record->src[record->src_cnt]
           > &f_retrans_state[LIP_IGMP_RETRANS_STATE_MEMORY_SIZE] - sizeof(t_lip_igmp_retrans_record) ?
           NULL : (t_lip_igmp_retrans_record*)&record->src[record->src_cnt];*/
    #warning "lip_igmp: addr must be aligned to record alignment"
    return (t_lip_igmp_retrans_record*)&record->src[record->src_cnt];
}

LINLINE static t_lip_igmp_response_rec* f_next_response_record(t_lip_igmp_response_rec* record)
{
    return (t_lip_igmp_response_rec*)&record->src[record->src_cnt];
}


LINLINE static t_igmp_sock_state* f_next_sock_state(const t_igmp_sock_state* sock_state)
{
    return (uint8_t*)sock_state->src_addrs[sock_state->src_cnt]
           > &f_sock_states[LIP_IGMP_SOCKET_STATE_MEMORY_SIZE] - sizeof(t_lip_igmp_sock) ?
           0 : (t_igmp_sock_state*)sock_state->src_addrs[sock_state->src_cnt];
}


#define MOVE_RECORD(start_free, start_valid, move_size, rec, next_rec) \
    do { \
        if (start_free != start_valid) \
        { \
            if (move_size != 0) \
                memmove(start_free, start_valid, move_size); \
            rec = (t_lip_igmp_retrans_record*)((uint8_t*)rec - (start_valid-start_free)); \
            next_rec = (t_lip_igmp_retrans_record*)((uint8_t*)next_rec - (start_valid-start_free)); \
            f_last_retrans_group = (t_lip_igmp_retrans_record*)\
                ((uint8_t*)f_last_retrans_group - (start_valid-start_free)); \
        } \
        move_size = 0; \
    } while (0);

#define MOVE_RESPONSE(start_free, start_valid, move_size, rec, next_rec) \
    do { \
        if (start_free != start_valid) \
        { \
            if (move_size != 0) \
                memmove(start_free, start_valid, move_size); \
            rec = (t_lip_igmp_response_rec*)((uint8_t*)rec - (start_valid-start_free)); \
            next_rec = (t_lip_igmp_response_rec*)((uint8_t*)next_rec - (start_valid-start_free)); \
            f_last_response = (t_lip_igmp_response_rec*)\
                ((uint8_t*)f_last_response - (start_valid-start_free)); \
        } \
        move_size = 0; \
    } while (0);





static void f_flush_report(t_lip_igmp_v3_report* rep, int size) {
    rep->checksum = lip_chksum(0, (uint8_t*)rep, size);
    rep->checksum = HTON16(rep->checksum);
    lip_ip_flush(size);
}


/*********************************************************************
 * Функция удаляет все записи повторных передач, которые
 * были уже переданы LIP_IGMP_ROBUSTNESS_VARIABLE раз,
 * так же выкидываются адреса источников, которые были переданы
 * LIP_IGMP_ROBUSTNESS_VARIABLE раз
 *********************************************************************/
static void f_drop_retrans_records(void)
{
    enum {e_SAVE_RECORD, e_DROP_RECORD, e_DROP_SRC} drop_rec;
    uint16_t prev_drop = 1, move_size=0;
    uint8_t *start_free = f_retrans_state, *start_valid = f_retrans_state;
    t_lip_igmp_retrans_record *rec, *next_rec = 0;
    //удаляем все повторные передачи с тем же адресом
    for (rec = (t_lip_igmp_retrans_record*)f_retrans_state;
            rec != f_last_retrans_group; rec = next_rec)
    {
        next_rec = f_next_retrans_record(rec);

        drop_rec = e_SAVE_RECORD;
        //если запись больше не нужно передавать, то ее можно выкинуть
        if (rec->retr_ctn==0)
        {
            drop_rec = e_DROP_RECORD;
        }
        else
        {
            int addr, valid_addr;
            //проверяем, какие адреса еще нужны для передачи
            for (addr = valid_addr= 0; addr < rec->src_cnt; addr++)
            {
                //адрес еще нужен
                if (rec->src[addr].retr_ctn)
                {
                    //если до этого были выброшенные адреса - смещаем
                    if (addr!=valid_addr)
                    {
                        memcpy(&rec->src[valid_addr], &rec->src[addr],
                                sizeof(t_lip_igmp_src_retrans));
                    }
                    valid_addr++;
                }
            }
            if (valid_addr != rec->src_cnt)
            {
                if (valid_addr == 0)
                {
                    drop_rec = e_DROP_RECORD;
                }
                else
                {
                    //todo анализ типа сообщения. если TO_EXC, выкинуть, если TO_INC - заменить на ALLOW
                    drop_rec = e_DROP_SRC;
                    rec->src_cnt = valid_addr;
                }
            }
        }

        //если выкидываем всю запись
        if (drop_rec == e_DROP_RECORD)
        {
            if (prev_drop)
            {
                //увеличиваем просто размер выкидываемой части
                start_valid = (uint8_t*)next_rec;
            }
            else
            {
                //выкидываем в прошлый раз отмеченную недействительную часть
                MOVE_RECORD(start_free, start_valid, move_size, rec, next_rec);
                start_free = (uint8_t*)rec;
                start_valid = (uint8_t*)next_rec;
            }
            prev_drop = 1;
        }
        else if (drop_rec == e_DROP_SRC)
        {
            /* если выкидывается только часть - как комбинация подрад идущий сперва
             * valid, потом drop - сдвигаем помеченную часть, а конец записи помечаем на
             * drop */
            move_size += (uint8_t*)&rec->src[rec->src_cnt] - (uint8_t*)rec;
            MOVE_RECORD(start_free, start_valid, move_size, rec, next_rec);

            prev_drop = 1;
            start_free = (uint8_t*)&rec->src[rec->src_cnt];
            start_valid = (uint8_t*)next_rec;
        }
        else
        {
            //если действительная часть - увиличиваем просто move_size
            prev_drop = 0;
            move_size += (uint8_t*)next_rec - (uint8_t*)rec;
        }
    }

    MOVE_RECORD(start_free, start_valid, move_size, rec, next_rec);
}

/****************************************************************
 * выкидываем ответы на запросы, помеченные флагом drop
 ****************************************************************/
static void f_drop_response_records(void)
{
    t_lip_igmp_response_rec *rec, *next_rec;
    uint8_t *start_free = f_response_state, *start_valid = f_response_state;
    uint16_t move_size=0;
    int prev_drop = 1;
    for (rec = (t_lip_igmp_response_rec*)f_response_state;
             rec != f_last_response;  rec = next_rec)
    {
        next_rec = f_next_response_record(rec);

        if (rec->retr_cnt == 0)
        {
            if (prev_drop)
            {
                //увеличиваем просто размер выкидываемой части
                start_valid = (uint8_t*)next_rec;
            }
            else
            {
                //выкидываем в прошлый раз отмеченную недействительную часть
                MOVE_RESPONSE(start_free, start_valid, move_size, rec, next_rec);
                start_free = (uint8_t*)rec;
                start_valid = (uint8_t*)next_rec;
            }
            prev_drop = 1;
        }
        else
        {
            //если действительная часть - увиличиваем просто move_size
            prev_drop = 0;
            move_size += (uint8_t*)next_rec - (uint8_t*)rec;
        }
    }
    MOVE_RESPONSE(start_free, start_valid, move_size, rec, next_rec);
}


#define FREE_MEM_FOR_NEW_SRC() ((uint8_t*)&f_last_retrans_group->src[f_last_retrans_group->src_cnt + 1] <= \
                                         &f_retrans_state[LIP_IGMP_RETRANS_STATE_MEMORY_SIZE])
/**********************************************************************
 * добавление записи отчета об изменении состояния в конец списка.
 * Используется для добавляения TO_EXCL, TO_INCL
 **********************************************************************/
static t_lip_errs f_add_change_record(uint8_t type,
            const t_igmp_sock_state* old_state,
            const t_new_rec_info* new_rec)
{
    t_lip_errs err = LIP_ERR_SUCCESS;
    t_igmp_sock_state* state;
    //если есть место на добавление новой записи
    if ((uint8_t*)&f_last_retrans_group->src[0] <=
            &f_retrans_state[LIP_IGMP_RETRANS_STATE_MEMORY_SIZE])
    {
        uint16_t i;
        lip_ipv4_addr_cpy(f_last_retrans_group->mcast_addr, new_rec->addr);
        f_last_retrans_group->type = type;
        f_last_retrans_group->retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
        ltimer_set(&f_last_retrans_group->retr_tmr, 0);
        if (type == LIP_IGMP_REC_TYPE_CHANGE_TO_INCLUDE)
        {
            //если тип include, то все записи из new_rec добавляются
            if ((uint8_t*)&f_last_retrans_group->src[new_rec->src_cnt] <=
                        &f_retrans_state[LIP_IGMP_RETRANS_STATE_MEMORY_SIZE])
            {
                f_last_retrans_group->src_cnt = new_rec->src_cnt;
                for (i=0; i < new_rec->src_cnt; i++)
                {
                    f_last_retrans_group->src[i].retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
                    lip_ipv4_addr_cpy(f_last_retrans_group->src[i].addr,
                            new_rec->src[i]);
                }
            }
            else
                err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;

            //так же добавляем все записи из старых include
            //проверяем на вхождение адреса в неизменяемое состояние E/I
            for (state = (t_igmp_sock_state*)f_sock_states;
                    (state !=0) && (state->sock != LIP_IGMP_INVALID_SOCK) && !err;
                     state = f_next_sock_state(state))
            {
               //проверяем только записи с совпадающим mcast-адресом
               if ((state!=old_state) && lip_ipv4_addr_equ(new_rec->addr, state->addr))
               {
                   for (i = 0; (i < state->src_cnt) && !err; i++)
                   {
                       uint8_t fnd = 0;
                       uint16_t src;
                       for (src = 0; (src < f_last_retrans_group->src_cnt) && !fnd; src++)
                       {
                           if (lip_ipv4_addr_equ(f_last_retrans_group->src[src].addr, state->src_addrs[i]))
                           {
                               fnd = 1;
                           }
                       }
                       if (!fnd)
                       {
                           if (FREE_MEM_FOR_NEW_SRC())
                           {
                               lip_ipv4_addr_cpy(f_last_retrans_group->
                                       src[f_last_retrans_group->src_cnt].addr,
                                       state->src_addrs[i]);
                               f_last_retrans_group->
                                   src[f_last_retrans_group->src_cnt++].
                                   retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
                           }
                           else
                               err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
                       }
                   }
               }
            }
        }
        else
        {
            uint16_t src;
            f_last_retrans_group->src_cnt = 0;
            for (src=0; (src < new_rec->src_cnt) && !err; src++)
            {
                uint8_t fnd_excl = 1, fnd_incl = 0;
                //проверяем на вхождение адреса в неизменяемое состояние E/I
                for (state = (t_igmp_sock_state*)f_sock_states;
                        (state !=0) && (state->sock != LIP_IGMP_INVALID_SOCK)
                                && !fnd_incl && fnd_excl && !err;
                         state = f_next_sock_state(state))
                {
                   //проверяем только записи с совпадающим mcast-адресом
                   if ((state!=old_state) && lip_ipv4_addr_equ(new_rec->addr, state->addr))
                   {
                       if (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
                           fnd_excl = 0;

                       for (i = 0; (i < state->src_cnt); i++)
                       {
                           if (lip_ipv4_addr_equ(new_rec->src[src], state->src_addrs[i]))
                           {
                               if (state->filter_mode == LIP_IGMP_FILTERMODE_INCLUDE)
                                   fnd_incl = 1;
                               else
                                   fnd_excl = 1;
                           }
                       }
                   }
                }

                if (!fnd_incl && fnd_excl)
                {
                    if (FREE_MEM_FOR_NEW_SRC())
                    {
                        lip_ipv4_addr_cpy(f_last_retrans_group->
                           src[f_last_retrans_group->src_cnt].addr,
                           new_rec->src[src]);
                        f_last_retrans_group->
                           src[f_last_retrans_group->src_cnt++].
                           retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
                    }
                    else
                        err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
                }
            }
        }

        //если добавили успешно, увеличиваем f_last_retrans_group, что свидетельствует
        //о добавлении новой записи
        if (!err)
            f_last_retrans_group = f_next_retrans_record(f_last_retrans_group);
    }
    else
    {
        err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
    }
    return err;
}

/***************************************************************************
 * Функция отправляет отчеты о изменении состояния прослушки, для которых
 * вышел таймаут и планирует их повторную передачу или выкидывает, если уже
 * передали нужное количество раз
 **************************************************************************/
static void f_send_reports(void)
{
    int max_size = 0, size = 0;
    t_lip_igmp_retrans_record* rec, *filter_ch_rec = NULL;
    t_lip_igmp_v3_report *rep = NULL;
    t_ltimer retr_tmr;
    t_lclock_ticks cur_time = lclock_get_ticks();


    for (rec = (t_lip_igmp_retrans_record *)f_retrans_state;
            (rec != f_last_retrans_group) && (max_size>=0);
            rec = f_next_retrans_record(rec))
    {
        /*запоминаем первую встетившуюся запись, изменяющую тип фильтра,
         * чтобы проверить потом, разрешено ли посылать allow/block */
        if (((rec->type == LIP_IGMP_REC_TYPE_CHANGE_TO_EXCLUDE)
                || (rec->type == LIP_IGMP_REC_TYPE_CHANGE_TO_INCLUDE))
                && (filter_ch_rec==NULL))
        {
            filter_ch_rec = rec;
        }


        //ищем отчеты, для которых уже вышло время ожидания
        if (ltimer_expired_at(&rec->retr_tmr, cur_time))
        {
            /* если в плане на отправку еще стоит запись, изменяющая тип фильтра
             * для данного multicast адреса, то посылать allow/block нельзя до завершения
             * передач этих записей   */
            if ((filter_ch_rec!=NULL) &&
                    ((rec->type == LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC)
                            || (rec->type == LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC)))
            {
                t_lip_igmp_retrans_record* search_rec;
                uint8_t fnd;
                for (search_rec = filter_ch_rec, fnd=0;
                        (search_rec != rec) && !fnd;
                        search_rec = f_next_retrans_record(search_rec))
                {
                    if (((search_rec->type == LIP_IGMP_REC_TYPE_CHANGE_TO_EXCLUDE)
                            || (search_rec->type == LIP_IGMP_REC_TYPE_CHANGE_TO_INCLUDE))
                            && lip_ipv4_addr_equ(rec->mcast_addr, search_rec->mcast_addr))
                    {
                        //откладывае посылку, если не можем послать сейчас
                        ltimer_set(&rec->retr_tmr, LTIMER_MS_TO_CLOCK_TICKS(
                                       LIP_IGMP_UNSOLICITED_REPORT_INTERVAL));
                    }
                }
            }
        }

        if (ltimer_expired_at(&rec->retr_tmr, cur_time))
        {
            //если еще не выделили буфер на передачу - выделяем
            if (rep == NULL)
            {
                f_ip_info.sa = NULL;
                f_ip_info.da = (uint8_t*)f_igmpv3_router_mcast;
                rep = (t_lip_igmp_v3_report*)lip_ip_prepare(&f_ip_info, &max_size);
                if (rep != NULL)
                {
                    rep->checksum = 0;
                    rep->group_num = 0;
                    rep->reserv1 = rep->reserv2 = 0;
                    rep->type = LIP_IGMP_MSG_TYPE_V3_REPORT;
                    size = sizeof(t_lip_igmp_v3_report);
                    //выбираем новый таймаут на следующую повторную передачу отчетов
                    ltimer_set(&retr_tmr, LTIMER_MS_TO_CLOCK_TICKS(lip_rand_range(
                            LIP_IGMP_UNSOLICITED_REPORT_INTERVAL - LIP_IGMP_MIN_REPORT_TIMEOUT)
                             + LIP_IGMP_MIN_REPORT_TIMEOUT));
                }
            }

            //добавляем отчет, либо если он первый, либо если есть место
            if ((rep!=NULL) && (((rep->group_num == 0)
                    && ((max_size - size) >= (int)(sizeof(t_lip_igmp_group_rec) + LIP_IPV4_ADDR_SIZE)))
                    || ((max_size - size) >= (int)(sizeof(t_lip_igmp_group_rec)
                            + LIP_IPV4_ADDR_SIZE*rec->src_cnt))))
            {
                int addr = 0;
                t_lip_igmp_group_rec *group = (t_lip_igmp_group_rec*)((uint8_t*)rep + size);
                group->type = rec->type;
                group->aux_len = 0;
                lip_ipv4_addr_cpy(group->mcast_addr, rec->mcast_addr);
                if ((max_size - size) >= (int)(sizeof(t_lip_igmp_group_rec)
                            + LIP_IPV4_ADDR_SIZE*rec->src_cnt))
                {
                    group->src_cnt = rec->src_cnt;
                    //если отсылаем всю запись, то уменьшаем кол-во посылок
                    if (rec->retr_ctn)
                        rec->retr_ctn--;
                }
                else
                {
                    //если все источники не помещаются - посылаем только первые
                    group->src_cnt = (max_size
                            - size - sizeof(t_lip_igmp_group_rec))/LIP_IPV4_ADDR_SIZE;
                }
                for (addr = 0; addr < group->src_cnt; addr++)
                {
                    //копируем адреса и уменьшем кол-во необходимых посылок
                    lip_ipv4_addr_cpy(group->src_addrs[addr], rec->src[addr].addr);
                    if (rec->src[addr].retr_ctn)
                        rec->src[addr].retr_ctn--;
                }
                size += sizeof(t_lip_igmp_group_rec)
                        + group->src_cnt * LIP_IPV4_ADDR_SIZE;
                group->src_cnt = HTON16(group->src_cnt);
                rep->group_num++;
                rec->retr_tmr = retr_tmr;
            }
        }
    }

    if (size > 0) {
        rep->group_num = HTON16(rep->group_num);
        f_flush_report(rep, size);
        f_drop_retrans_records();
    }
}


/***********************************************************************************
 * заполнение записи группы из отсылаемое пакета по параметрам отчета в записи rec
 * max_src_cnt - указывает на сколько адресов есть места
 ***********************************************************************************/
static int f_check_respond_addrs(const t_lip_igmp_response_rec* rec,
        t_lip_igmp_group_rec* group,
        const uint8_t drop_list[][LIP_IPV4_ADDR_SIZE],
        uint16_t drop_list_size,
        uint16_t max_src_cnt
        )
{
    const t_igmp_sock_state* state, *first_excl=NULL;
    uint16_t incl_cnt=0, excl_cnt=0;
    uint16_t i,j;
    int err = 0;


    group->src_cnt = 0;
    group->aux_len = 0;
    group->src_cnt = 0;
    group->type = LIP_IGMP_REC_TYPE_IS_INCLUDE;
    lip_ipv4_addr_cpy(group->mcast_addr, rec->mcast_addr);

    //рассчитываем кол-во записей типа INCLUDE и EXCLUDE для заданного адреса
    for (state = (const t_igmp_sock_state*)f_sock_states;
            (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK);
            state = f_next_sock_state(state))
    {
        if (lip_ipv4_addr_equ(state->addr, rec->mcast_addr))
        {
            if (state->filter_mode == LIP_IGMP_FILTERMODE_INCLUDE)
            {
                incl_cnt++;
            }
            else
            {
                excl_cnt++;
                if (first_excl==NULL)
                    first_excl = state;
            }
        }
    }

    //если есть список - проверяем из него
    if ((incl_cnt + excl_cnt) > 0)
    {
       //src_cnt == 0 => передаем все текущее состояние
       if (rec->src_cnt==0)
       {
           //если нет записей exclude, то шлем объединение всех include адресов
           if (excl_cnt == 0)
           {
               group->type = LIP_IGMP_REC_TYPE_IS_INCLUDE;

               for (state = (const t_igmp_sock_state*)f_sock_states;
                       (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && !err;
                       state = f_next_sock_state(state))
               {
                   if (lip_ipv4_addr_equ(state->addr, rec->mcast_addr))
                   {
                       for (i=0; (i < state->src_cnt) && !err; i++)
                       {
                           int fnd = 0;
                           //смотрим - добавили ли мы уже этот адресс или нет
                           for (j=0; (j < group->src_cnt) && !fnd; j++)
                           {
                               if (lip_ipv4_addr_equ(group->src_addrs[j], state->src_addrs[i]))
                               {
                                   fnd = 1;
                               }
                           }
                           for (j=0; (j < drop_list_size) && !fnd; j++)
                           {
                               if (lip_ipv4_addr_equ(drop_list[j], state->src_addrs[i]))
                               {
                                   fnd = 1;
                               }
                           }
                           //если новый пакет - добавляем
                           if (!fnd)
                           {
                               //сохраняем, если есть место
                               if (group->src_cnt < max_src_cnt)
                               {
                                   lip_ipv4_addr_cpy(group->src_addrs[group->src_cnt], state->src_addrs[i]);
                                   group->src_cnt++;
                               }
                               else
                               {
                                   err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
                               }
                           }
                       }
                   }
               }
           } //if (excl_cnt == 0)
           else
           {
               group->type = LIP_IGMP_REC_TYPE_IS_EXCLUDE;
               //если есть exclude => шлем пересечение всех exclude без include
               for (i=0; (i < first_excl->src_cnt) && !err; i++)
               {
                   int fnd_incl = 0, fnd_excl = 1;
                   for (state = (const t_igmp_sock_state*)f_sock_states;
                         (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK)
                                 && !err && fnd_excl && !fnd_incl;
                         state = f_next_sock_state(state))
                   {
                       if (lip_ipv4_addr_equ(state->addr, rec->mcast_addr))
                       {
                           if (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
                               fnd_excl = 0;
                           for (j=0; j < state->src_cnt; j++)
                           {
                               if (lip_ipv4_addr_equ(state->src_addrs[j], first_excl->src_addrs[i]))
                               {
                                   if (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
                                       fnd_excl = 1;
                                   else
                                       fnd_incl = 0;
                               }
                           }
                       }
                   }

                   if (fnd_excl && !fnd_incl)
                   {
                       //сохраняем, если есть место
                        if (group->src_cnt < max_src_cnt)
                        {
                            lip_ipv4_addr_cpy(group->src_addrs[group->src_cnt], first_excl->src_addrs[i]);
                            group->src_cnt++;
                        }
                        else
                        {
                            err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
                        }
                   }
               }
           }
       }
       else //if (rec->src_cnt==0)
       {
           group->type = LIP_IGMP_REC_TYPE_IS_INCLUDE;
           //добавляем адрес если он есть в include, но нет в exclude
           for (i=0; (i < rec->src_cnt) && !err; i++)
           {
               int fnd_incl = 0, fnd_excl = 1;
               for (state = (const t_igmp_sock_state*)f_sock_states;
                       (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK)
                               && !err && fnd_excl && !fnd_incl;
                       state = f_next_sock_state(state))
               {
                   if (lip_ipv4_addr_equ(state->addr, rec->mcast_addr))
                   {
                       if (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
                           fnd_excl = 0;
                       for (j=0; j < state->src_cnt; j++)
                       {
                           if (lip_ipv4_addr_equ(state->src_addrs[j], rec->src[i]))
                           {
                               if (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
                                   fnd_excl = 1;
                               else
                                   fnd_incl = 0;
                           }
                       }
                   }
               }


               //включаем если есть в include или нет в пересечении exclude
               if (fnd_incl || !fnd_excl)
               {
                   //сохраняем, если есть место
                   if (group->src_cnt < max_src_cnt)
                   {
                       lip_ipv4_addr_cpy(group->src_addrs[group->src_cnt], rec->src[i]);
                       group->src_cnt++;
                   }
                   else
                   {
                       err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
                   }
               }
           }
       }
    }

    return err;
}


/************************************************************************
 * посылаем старый отчет IGMP, указанный как rep (размер в size)
 * и подготавливаем новый отчет, возвращая указатель на отчет и заполняя
 * size в смещение с которого можем класть первый отчет,
 * а max_size - макс. размер пакета
 *************************************************************************/
static t_lip_igmp_v3_report* f_prepare_resp(t_lip_igmp_v3_report* rep,
        int* size, int* max_size)
{
    //если был подготовлен предыдущий пакет - посылаем
    if ((rep!=NULL) && (*size > (int)sizeof(t_lip_igmp_v3_report)))  {
        f_flush_report(rep, *size);
    }

    //создаем новый пакет
    f_ip_info.sa = NULL;
    f_ip_info.da = (uint8_t*)f_igmpv3_router_mcast;
    rep = (t_lip_igmp_v3_report*)lip_ip_prepare(&f_ip_info, max_size);
    if (rep != NULL)
    {
        if (*max_size > (int)(sizeof(t_lip_igmp_v3_report)
                + sizeof(t_lip_igmp_group_rec) + LIP_IPV4_ADDR_SIZE))
        {
            rep->checksum = 0;
            rep->group_num = 0;
            rep->reserv1 = rep->reserv2 = 0;
            rep->type = LIP_IGMP_MSG_TYPE_V3_REPORT;
            *size = sizeof(t_lip_igmp_v3_report);
        }
        else
        {
            *max_size = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
        }
    }
    return rep;
}

/***************************************************************
 * Передача IGMPv1 или IGMPv2 отчета о прослушивании заданного
 * mcast-адреса (версия определяется по f_flags)
 ***************************************************************/
static int f_send_igmp_v1_2_report(const uint8_t* addr, uint8_t join)
{
    int max_size = 0;
    //в режиме совместимости с IGMPv1 есть только сообщения о присоединении к группе
    if (join || !(f_flags & LIP_IGMP_FLAG_V1_TIMER_RUN))
    {
        t_lip_igmp_v1_2_report *rep;

        f_ip_info.sa = NULL;
        /* в IGMPv1 и IGMPv2 отчеты о принадлежности к группе посылаются
         * на тот mcast адрес о принадлежности к которому и сообщают,
         * leave-отчеты посылаются мершрутизаторам на адрес 224.0.0.2 */
        f_ip_info.da = join ? addr : f_mcast_all_routers;
        rep = (t_lip_igmp_v1_2_report*)lip_ip_prepare(
                &f_ip_info, &max_size);
        if (rep!=NULL)
        {
            if (max_size < (int)sizeof(t_lip_igmp_v1_2_report))
                max_size = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
            else
            {
                if (join)
                {
                    rep->type = (f_flags & LIP_IGMP_FLAG_V1_TIMER_RUN) ?
                            LIP_IGMP_MSG_TYPE_V1_REPORT : LIP_IGMP_MSG_TYPE_V2_REPORT;
                }
                else
                {
                    rep->type = LIP_IGMP_MSG_TYPE_V2_LEAVE_GROUP;
                }
                rep->checksum = 0;
                rep->reserv = 0;
                lip_ipv4_addr_cpy(rep->group_addr, addr);
                rep->checksum = lip_chksum(0, (uint8_t*)rep,
                        sizeof(t_lip_igmp_v1_2_report));
                rep->checksum = HTON16(rep->checksum);
                max_size = lip_ip_flush(sizeof(t_lip_igmp_v1_2_report));
            }
        }
    }
    return max_size;
}



/*********************************************************************************
 * посылка запланированных ответов на запросы
 *********************************************************************************/
static void f_send_responses(void)
{
    t_lip_igmp_response_rec* rec;
    int err = 0;
    t_lip_igmp_v3_report *rep = NULL;
    int size = 0;
    int max_size = 0;

    //просматриваем все запланированные на передачу записи
    for (rec = (t_lip_igmp_response_rec*)f_response_state;
                  (rec != f_last_response) && (max_size >= 0);
                  rec = f_next_response_record(rec))
    {
        if (ltimer_expired(&rec->tmr))
        {
            if ((f_flags & (LIP_IGMP_FLAG_V1_TIMER_RUN |
                    LIP_IGMP_FLAG_V2_TIMER_RUN)))
            {
                /*********************************************************
                 * формируем отчет при совместимости с IGMPv1 или IGMPv2
                 *********************************************************/
                int fnd = 0;
                t_igmp_sock_state *state;
                //проверяем, слушаем ли указанный mcast-адрес
                for (state = (t_igmp_sock_state*)f_sock_states;
                        (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && !fnd;
                        state = f_next_sock_state(state))
                {
                    if (lip_ipv4_addr_equ(state->addr, rec->mcast_addr))
                        fnd = 1;
                }
                if (fnd)
                {
                    //если слушаем - шлем отчет
                    err = f_send_igmp_v1_2_report(rec->mcast_addr, 1);
                }
                //если обработали - ставим флаг, что запись можно выкинуть
                if (!err && (rec->retr_cnt>0))
                {
                    rec->retr_cnt--;
                    if (rec->retr_cnt > 0)
                    {
                        ltimer_set(&rec->tmr, LTIMER_MS_TO_CLOCK_TICKS(lip_rand_range(
                                LIP_IGMP_UNSOLICITED_REPORT_INTERVAL - LIP_IGMP_MIN_REPORT_TIMEOUT)
                                 + LIP_IGMP_MIN_REPORT_TIMEOUT));
                    }
                }
            }
            else
            {
                /*********************************************
                 * формируем ответы для версии IGMPv3
                 ********************************************/
                //если присутствуют записи - то формируем отчет
                t_lip_igmp_group_rec *group = 0;

                //подготвалвиваем ответ (если еще не подготовлен)
                if ((rep == NULL) || ((max_size - size)
                        < (int)(sizeof(t_lip_igmp_group_rec) + LIP_IPV4_ADDR_SIZE)))
                {
                    rep = f_prepare_resp(rep, &size, &max_size);
                }

                if (max_size > 0)
                {
                    group = (t_lip_igmp_group_rec*)((uint8_t*)rep + size);
                    err = f_check_respond_addrs(rec, group, NULL, 0,
                            (max_size - size - sizeof(t_lip_igmp_group_rec))/LIP_IPV4_ADDR_SIZE);
                    //если не хватило места
                    if (err == LIP_ERR_IGMP_INSUFFICIENT_MEMORY)
                    {
                        //если пытались добавить не в начало пакета - то выделяем новый пакет и
                        //пытаемся добавить сначала
                        if (size > (int)sizeof(t_lip_igmp_v3_report))
                        {
                            //если не начало запроса - выделяем новый
                            rep = f_prepare_resp(rep, &size, &max_size);
                            if (max_size > 0)
                            {
                                group = (t_lip_igmp_group_rec*)((uint8_t*)rep + size);
                                err = f_check_respond_addrs(rec, group, NULL, 0,
                                                (max_size - size - sizeof(t_lip_igmp_group_rec))/LIP_IPV4_ADDR_SIZE);
                            }
                        }

                        if ((err == LIP_ERR_IGMP_INSUFFICIENT_MEMORY) && (max_size > 0))
                        {
                            if (group->type == LIP_IGMP_REC_TYPE_IS_INCLUDE)
                            {
                                /* xxx если целого пакета недостаточно, то по идее надо послать окончание include
                                 * но так как мы состояние не храним и памяти у нас нет, то не откуда брать, какие
                                 * адреса мы передали, а какие нет
                                 * Лучшим вариантом видится посылка exclude 0 пакета, для включения всех адресов,
                                 * хотя это явно нарушает требование rfc3376, так что это ошибка
                                 */
                                lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_ERROR_HIGH, err,
                                    "lip igmp: insufficient place for source address in include response\n");
                                group->type = LIP_IGMP_REC_TYPE_IS_EXCLUDE;
                                group->src_cnt = 0;
                                err = 0;
                            }
                            else
                            {
                                //для exclude пакетов можем посылать и не все адреса
                                err = 0;
                            }
                        }
                    }
                }

                /* если успешно заполнили поля утверждаем запись изменяя текущий размер
                 * записи типа include с 0 количеством (пустые) адресов источников не отсылаются
                 * так как соответствуют отсуствию прослушки данного mcast-адреса
                 */
                if (!err && (max_size > 0) && ((group->src_cnt!=0)
                        || (group->type!=LIP_IGMP_REC_TYPE_IS_INCLUDE)))
                {
                    size+=sizeof(t_lip_igmp_group_rec) + group->src_cnt*LIP_IPV4_ADDR_SIZE;
                }

                //выкидываем запись за исключением случая, когда была ошибка подготовки пакета
                if ((max_size > 0) && (rec->retr_cnt > 0))
                {
                    rec->retr_cnt--;
                    if (rec->retr_cnt > 0)
                    {
                        ltimer_set(&rec->tmr, LTIMER_MS_TO_CLOCK_TICKS(lip_rand_range(
                                LIP_IGMP_UNSOLICITED_REPORT_INTERVAL - LIP_IGMP_MIN_REPORT_TIMEOUT)
                                + LIP_IGMP_MIN_REPORT_TIMEOUT));
                    }
                }
            }
        }
    }

    if ((rep!=NULL) && (size > (int)sizeof(t_lip_igmp_v3_report))) {
        f_flush_report(rep, size);
    }

    f_drop_response_records();
}

/****************************************************************************
 * Добавляем адрес scr_addr в запись (память уже выделена в f_alloc_retrans_ch)
 * Если адрес есть в противоположной записи, то в ней он помечается на удаление
 * Запись для добавления и исключения являются записями f_allow_rec и f_block_rec
 * в соответствии с типом type
 * (должен быть либо LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC, либо LIP_IGMP_REC_TYPE_BLOCK_NEW_SRC
 ****************************************************************************/
static void f_set_addr_excl(const uint8_t* src_addr, uint8_t type)
{
    t_lip_igmp_retrans_record *rec, *other_rec;
    if (type == LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC)
    {
        rec = f_allow_rec;
        other_rec = f_block_rec;
    }
    else
    {
        rec = f_block_rec;
        other_rec = f_allow_rec;
    }

    //добавляем адрес
    lip_ipv4_addr_cpy(rec->src[rec->src_cnt].addr, src_addr);
    rec->src[rec->src_cnt++].retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
    if (other_rec!=NULL)
    {
        //если передан other_rec, помечаем в нем совпавшие записи на удаление
        int i;
        for (i=0; i < other_rec->src_cnt; i++)
        {
            if (lip_ipv4_addr_equ(other_rec->src[i].addr, src_addr))
                other_rec->src[i].retr_ctn = 0;
        }
    }
}



#define CHECKMODE_OLD_SUB    0
#define CHECKMODE_OLD_MUL    1
#define CHECKMODE_NEW_SUB    0
#define CHECKMODE_NEW_MUL    2
/****************************************************************************************
 * Функция подсчитывает количество адресов источников, входящих во множество:
 * E*[NEW]*[OLD]/(I+[NEW]+[OLD]), где
 *  E - пересечение адресов источников exclude-записей с указаным mcast-адресом
 *     из неизменяемого состояния (f_sock_states без old_state)
 *  I - объединение адресов источников include-записей с указаным mcast-адресом
 *     из неизменяемого состояния
 *  OLD - список адресов источников удаляемой записи (из old_state)
 *  NEW - список адресов источников новой добавляемой записи (из new_rec)
 * NEW и OLD находятся в пересечении, если в mode указаны флаги CHECKMODE_OLD_MUL и
 *    CHECKMODE_NEW_MUL соответственно, иначе - в объединении вычитаемого множества
 * Если fill != 0, то для найденными адресами заполняются поля записей о изменении
 *    состояния с помощью f_set_addr_excl
 * type определяет,
 ****************************************************************************************/
static int f_check_addr(int mode, int fill,
        uint8_t type,
        const t_igmp_sock_state* old_state,
        const t_new_rec_info* new_rec)
{
    uint16_t fnd_cnt,src, search_size=0;
    const uint8_t (*search_list)[LIP_IPV4_ADDR_SIZE] = NULL;
    const t_igmp_sock_state* state;

    if ((mode & CHECKMODE_OLD_MUL) && (old_state!=NULL))
    {
        search_list = old_state->src_addrs;
        search_size = old_state->src_cnt;
    }
    else if (mode & CHECKMODE_NEW_MUL)
    {
        search_list = new_rec->src;
        search_size = new_rec->src_cnt;
    }
    else
    {
        //если обе записи - в исключаемых, то в качестве списка обходимых адресов
        //выбираем список первой записи типа exclude из текущего состояния
        for (state = (const t_igmp_sock_state*)f_sock_states;
             (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && (search_list == NULL);
                         state = f_next_sock_state(state))
        {
            if ((state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE) &&
                    (state!=old_state) && lip_ipv4_addr_equ(new_rec->addr, state->addr))

            {
                search_list = state->src_addrs;
                search_size = state->src_cnt;
            }
        }
    }

    for (src=0, fnd_cnt = 0; src < search_size; src++)
    {
        uint16_t fnd_excl = 1, fnd_incl = 0, i;

        //сперва проверяем удаляемую запись
        //если режим пересечения, то адрес обязательно должен присуствовать
        if (mode & CHECKMODE_OLD_MUL)
        {
            fnd_excl = 0;
            //нулевое состояние соответствует include 0 => в нем нет адресов, fnd_excl станет 0
            if (old_state!=NULL)
            {
                for (i=0; (i < old_state->src_cnt) && !fnd_excl; i++)
                {
                    if (lip_ipv4_addr_equ(search_list[src], old_state->src_addrs[i]))
                        fnd_excl = 1;
                }
            }
        }
        else //иначе - адрес не должен присутствовать
        {
            if (old_state!=NULL)
            {
                for (i=0; (i < old_state->src_cnt) && !fnd_incl; i++)
                {
                    if (lip_ipv4_addr_equ(search_list[src], old_state->src_addrs[i]))
                        fnd_incl = 1;
                }
            }
        }

        //тоже самое для новой записи
        if (fnd_excl && !fnd_incl)
        {
            if (mode & CHECKMODE_NEW_MUL)
            {
                fnd_excl = 0;
                //нулевое состояние соответствует include 0 => в нем нет адресов, fnd_excl станет 0
                for (i=0; (i < new_rec->src_cnt) && !fnd_excl; i++)
                {
                    if (lip_ipv4_addr_equ(search_list[src], new_rec->src[i]))
                        fnd_excl = 1;
                }
            }
            else //иначе - адрес не должен присутствовать
            {
                for (i=0; (i < new_rec->src_cnt) && !fnd_incl; i++)
                {
                    if (lip_ipv4_addr_equ(search_list[src], new_rec->src[i]))
                        fnd_incl = 1;
                }

            }
        }

        //проверяем на вхождение адреса в неизменяемое состояние E/I
        for (state = (t_igmp_sock_state*)f_sock_states;
                (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && !fnd_incl && fnd_excl;
                 state = f_next_sock_state(state))
        {
           //проверяем только записи с совпадающим mcast-адресом
           if ((state!=old_state) && lip_ipv4_addr_equ(new_rec->addr, state->addr))
           {
               if (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
                   fnd_excl = 0;

               for (i = 0; (i < state->src_cnt); i++)
               {
                   if (lip_ipv4_addr_equ(search_list[src], state->src_addrs[i]))
                   {
                       if (state->filter_mode == LIP_IGMP_FILTERMODE_INCLUDE)
                           fnd_incl = 1;
                       else
                           fnd_excl = 1;
                   }
               }
           }
        }

        if (!fnd_incl && fnd_excl)
        {
            fnd_cnt++;
            if (fill)
                f_set_addr_excl(search_list[src], type);
        }
    }
    return fnd_cnt;
}



LINLINE static t_lip_igmp_retrans_record* f_alloc_allow_block_rec(
        t_lip_igmp_retrans_record* ch_rec,
        t_lip_igmp_retrans_record** pother_rec,
        uint8_t type, const uint8_t* mcast_addr, uint16_t src_cnt)
{
    int move_size = 0;
    //если запись не существует - добавляем ее в конец
    if (ch_rec==NULL)
    {
        ch_rec = f_last_retrans_group;
        ch_rec->type = type;
        lip_ipv4_addr_cpy(ch_rec->mcast_addr, mcast_addr);
        ch_rec->retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
        ltimer_set(&ch_rec->retr_tmr, 0);
        ch_rec->src_cnt = src_cnt;
        f_last_retrans_group = f_next_retrans_record(ch_rec);
        ch_rec->src_cnt = 0;
        /*for (i=0; i < src_cnt; i++)
        {
            f_last_retrans_group->src[i].retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
            lip_ipv4_addr_set_invalid(f_last_retrans_group->src[i].addr);
        }*/
    }
    else
    {
        //иначе нам нужно добавить в нее адреса источников
        //смотрим, сколько действительных байт за этой записью
        move_size = (uint8_t*)f_last_retrans_group -
                (uint8_t*)f_next_retrans_record(ch_rec);

        if (move_size > 0)
        {
            //сдвигаем эти байты вправо
            memmove(&ch_rec->src[ch_rec->src_cnt + src_cnt],
                    &ch_rec->src[ch_rec->src_cnt], move_size);
            //сдвигаем указатель на другую запись, если он оказался в сдвигаемом интервале
            if ((uint8_t*)(*pother_rec) > (uint8_t*)&ch_rec->src[ch_rec->src_cnt])
            {
                *pother_rec = (t_lip_igmp_retrans_record*)((uint8_t*)(*pother_rec)
                        + src_cnt*sizeof(ch_rec->src[0]));
            }
        }

        f_last_retrans_group = (t_lip_igmp_retrans_record*)((uint8_t*)(f_last_retrans_group)
                            + src_cnt*sizeof(ch_rec->src[0]));
        /*for (i=ch_rec->src_cnt; i < ch_rec->src_cnt + src_cnt; i++)
        {
            f_last_retrans_group->src[i].retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
            lip_ipv4_addr_set_invalid(f_last_retrans_group->src[i].addr);
        }*/
        //ch_rec->src_cnt+=src_cnt;
        ch_rec->retr_ctn = LIP_IGMP_ROBUSTNESS_VARIABLE;
        ltimer_set(&ch_rec->retr_tmr, 0);
    }
    return ch_rec;
}


/***************************************************************************
 * изменяем массив с повторными передачами для добавляния указанного
 * количества разрешенных и запрещенных адресов
 * Если уже есть записи для данного mcast-адреса то выделяется место для
 * добавления в существующую запись новых адресов,
 * иначе создается новая запись в конце списка
 * Адреса заполняются недействительными значенями и устанавливаются потом
 * с помощью f_set_addr_excl()
 * Поле кол-во адресов остается старым и увеличивается при вызовах f_set_addr_excl()!
 ****************************************************************************/
static t_lip_errs f_alloc_retrans_ch(const uint8_t* mcast_addr, uint16_t allow_cnt, uint16_t block_cnt)
{
    int size;
    t_lip_errs err = LIP_ERR_SUCCESS;
    t_lip_igmp_retrans_record *rec;

    /* ищем запросы на повторную передачу с указанным mcast-адресом
     * типов LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC/BLOCK_OLD_SRC*/
    for (f_allow_rec = NULL, f_block_rec = NULL,
            rec = (t_lip_igmp_retrans_record *)f_retrans_state;
            rec != f_last_retrans_group;
            rec = f_next_retrans_record(rec))
    {
        if (lip_ipv4_addr_equ(mcast_addr, rec->mcast_addr))
        {
            if (rec->type == LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC)
            {
                f_allow_rec = rec;
            }
            else if (rec->type == LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC)
            {
                f_block_rec = rec;
            }
        }
    }

    //рассчитываем сколько нужно свободного места
    size = sizeof(t_lip_igmp_src_retrans)*(allow_cnt + block_cnt);
    if ((allow_cnt!=0) && (f_allow_rec==NULL))
    {
        size+= sizeof(t_lip_igmp_retrans_record);
    }
    if ((block_cnt!=0) && (f_block_rec==NULL))
    {
        size+=sizeof(t_lip_igmp_retrans_record);
    }
    //проверяем, достаточно ли памяти
    if (size <= (&f_retrans_state[LIP_IGMP_RETRANS_STATE_MEMORY_SIZE] -
            (uint8_t*)f_last_retrans_group))
    {
        if (allow_cnt!=0)
        {
            f_allow_rec = f_alloc_allow_block_rec(f_allow_rec, &f_block_rec,
                    LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC, mcast_addr, allow_cnt);
        }
        if (block_cnt!=0)
        {
            f_block_rec = f_alloc_allow_block_rec(f_block_rec, &f_allow_rec,
                    LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC, mcast_addr, block_cnt);
        }
    }
    else
    {
        err = LIP_ERR_IGMP_NONMCAST_ADDR;
    }
    return err;
}



static void f_get_allow_rec(uint8_t new_mode, const t_igmp_sock_state* old_state,
        const t_new_rec_info* new_rec, uint8_t fill,
        uint16_t* pallow_cnt, uint16_t *pblock_cnt)
{
    uint16_t allow_cnt = 0, block_cnt = 0;

    /**********************************************************************
     * Рассматриваем 5 вариантов изменения адреса, которые не приводят
     * к изменению режима фильтра, но приводять к разрешению/блокированию
     * адресов источников
     * Обозначения:
     * I1, E1 - адреса источников заменяемой записи (типа include или eclude соотв.)
     * I2, E2 - адреса источников новой добавляемой записи (типа include или eclude соотв.)
     * I - объединение всех адресов источников остальных записей типа include для mcast-адреса
     * E - пересечение всех адресов источников остальных записей типа exclude для mcast-адреса
     ***********************************************************************/

    if (new_mode == LIP_IGMP_FILTERMODE_INCLUDE)
    {
        /* если режим фильтрации INCLUDE до и после изменения - то все записи типа INCLUDE
         * I+I1 -> I + I2
         */
        /* новые разрешенные записи: A = I2/(I1+I) */
        allow_cnt = f_check_addr(CHECKMODE_NEW_MUL | CHECKMODE_OLD_SUB, fill,
                LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC, old_state, new_rec);


        /* запрещаемые записи: B = I1/(I2+I)*/
        if (old_state!=NULL)
        {
            block_cnt = f_check_addr(CHECKMODE_NEW_SUB | CHECKMODE_OLD_MUL, fill,
                    LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC, old_state, new_rec);
        }
    }
    /* остальные варианты соответствуют фильтру LIP_IGMP_FILTERMODE_EXCLUDE
     * и разным вариантам фльтра заменяемой и замененной записи */
    else if ((old_state!=NULL) && (old_state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE))
    {
        if (new_rec->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
        {
            /* вариант замены одной записи exclude на другую:
             * изменение исключаемых адресов: E*E1/I -> E*E2/I */
            /* A = E1*E/(E2+I) */
            allow_cnt = f_check_addr(CHECKMODE_NEW_SUB | CHECKMODE_OLD_MUL, fill,
                    LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC, old_state, new_rec);

            /*********************************************************************
             * запрещаем адреса, которые есть в новой, но не было ни в удаляемой,
             * ни в остальных
             * B = E2*E/(E1+I) */
            block_cnt = f_check_addr(CHECKMODE_NEW_MUL | CHECKMODE_OLD_SUB, fill,
                    LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC, old_state, new_rec);
        }
        else
        {
            /*  замена записи excluded на included.
             *  изменение списка: E*E1/I -> E/(I+I2)
             *  A = E*E1*I2/I */
            allow_cnt = f_check_addr(CHECKMODE_NEW_MUL | CHECKMODE_OLD_MUL, fill,
                    LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC, old_state, new_rec);
            /* B = E/(I+I2+E1) */
            block_cnt = f_check_addr(CHECKMODE_NEW_SUB | CHECKMODE_OLD_SUB, fill,
                                LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC, old_state, new_rec);
        }
    }  //else if ((old_state!=NULL) && (old_state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE))
    else
    {
        if (new_rec->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
        {
            /* замена I1 на E2
             * изменение списка: E/(I+I1) -> E*E2/I
             * A = E(I+I1+E2)      */
            allow_cnt = f_check_addr(CHECKMODE_NEW_SUB | CHECKMODE_OLD_SUB, fill,
                    LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC, old_state, new_rec);
            /* B = E2*E*I1/I */
            block_cnt = f_check_addr(CHECKMODE_NEW_MUL | CHECKMODE_OLD_MUL, fill,
                    LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC, old_state, new_rec);
        }
        else
        {
            /* замена I1 на I2
             * изменение списка: E/(I+I1) -> E/(I+I2)
             * A = E*I2/(I+I1) */
            allow_cnt = f_check_addr(CHECKMODE_NEW_MUL | CHECKMODE_OLD_SUB, fill,
                    LIP_IGMP_REC_TYPE_ALLOW_NEW_SRC, old_state, new_rec);
            /* B=E*I1/(I+I2) */
            block_cnt = f_check_addr(CHECKMODE_NEW_SUB | CHECKMODE_OLD_MUL, fill,
                    LIP_IGMP_REC_TYPE_BLOCK_OLD_SRC, old_state, new_rec);
        }
    }

    if (pallow_cnt!=NULL)
        *pallow_cnt = allow_cnt;
    if (pblock_cnt!=NULL)
        *pblock_cnt = block_cnt;
}





static t_lip_errs f_build_change_report(const t_igmp_sock_state* old_state,
        const t_new_rec_info* new_rec)
{
    t_lip_errs err = LIP_ERR_SUCCESS;
    int old_mode, new_mode;
    t_igmp_sock_state* state;
    //режим по-умолчанию INCLUDE с 0-ым списком ip-адресов
    old_mode = LIP_IGMP_FILTERMODE_INCLUDE;
    new_mode = new_rec->filter_mode;

    //определеяем режим фильтра для указанного интерфейса
    for (state = (t_igmp_sock_state*)f_sock_states;
            (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK);
            state = f_next_sock_state(state))
    {
        if (lip_ipv4_addr_equ(new_rec->addr, state->addr) &&
                (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE))
        {
            //наличие хотя бы одного exclude => состояние exclude
            old_mode = LIP_IGMP_FILTERMODE_EXCLUDE;
            if (state!=old_state)
                new_mode = LIP_IGMP_FILTERMODE_EXCLUDE;
        }
    }

    /* если сменился режим фильтра - то отменяем все предыдущие
     * отложенные посылки для данного адреса и ставим в очередь новую
     */
    if (old_mode!=new_mode)
    {
        uint16_t rem_cnt=0;
        t_lip_igmp_retrans_record *rec;
        //удаляем все повторные передачи с тем же адресом
        for (rec = (t_lip_igmp_retrans_record *)f_retrans_state;
                rec!=f_last_retrans_group; rec = f_next_retrans_record(rec))
        {
            if (lip_ipv4_addr_equ(new_rec->addr, rec->mcast_addr))
            {
                rec->retr_ctn = 0;
                rem_cnt++;
            }
        }
        if (rem_cnt)
            f_drop_retrans_records();

        err = f_add_change_record(new_mode == LIP_IGMP_FILTERMODE_INCLUDE ?
                LIP_IGMP_REC_TYPE_CHANGE_TO_INCLUDE : LIP_IGMP_REC_TYPE_CHANGE_TO_EXCLUDE,
                old_state, new_rec);
    }
    else
    {
        uint16_t allow_cnt = 0, block_cnt = 0;
        //считаем, сколько нужно ризрешить или заблокировать адресов
        f_get_allow_rec(new_mode, old_state, new_rec, 0, &allow_cnt, &block_cnt);
        //если есть хотя бы один такой адрес
        if ((allow_cnt !=0) || (block_cnt!=0))
        {
            //выделяем память под них
            err = f_alloc_retrans_ch(new_rec->addr, allow_cnt, block_cnt);
            if (!err)
            {
                //если удачно выделили память - заполняем адреса источников
                f_get_allow_rec(new_mode, old_state, new_rec, 1, NULL, NULL);
                //выкидываем адреса, если были помечены на удаления
                f_drop_retrans_records();
            }
        }
    }

    return err;
}

/***********************************************************************************
 *  Изменение состояния прослушивания multicast-адресов
 *  (основной серверс IGMPv3. Отличается от rfc3376 отсутствием интерфейса,
 *    т.к. стек поддерживает только один интерфейс)
 *  Параметры
 *     sock - сокет, для которого выполняется опирация
 *          (специфичное значение для верхнего уровня.
 *          например адрес слушающего callback-а для udp)
 *     addr - mcast адрес, для которого изменяется состояние прослушивания
 *     filter_mode - вариант фильтра - LIP_IGMP_FILTERMODE_INCLUDE/EXCLUDE
 *     src_addrs     - адреса источников, которые должны быть исключены или включены
 *     src_cnt  - кол-во вдресов в filters
 *  Возвращаемое значение:
 *     Код ошибки
 ************************************************************************************/
t_lip_errs lip_igmp_listen(t_lip_igmp_sock sock, const uint8_t* addr,
        uint8_t filter_mode, const uint8_t src_addrs[][LIP_IPV4_ADDR_SIZE], uint8_t src_cnt)
{
    t_lip_errs err = LIP_ERR_SUCCESS;
    if ((addr == NULL) || ((src_cnt > 0) && (src_addrs == NULL)))
    {
        err = LIP_ERR_PARAMETERS;
    }
    else if (!lip_ipv4_addr_is_mcast(addr))
    {
        err = LIP_ERR_IGMP_NONMCAST_ADDR;
        lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_ERROR_HIGH, err,
                "lip igmp: try to listen non-multicast address:\n",
                addr[0], addr[1], addr[2], addr[3]);
    }
    else
    {
        t_igmp_sock_state* fnd_state = NULL, *state;
        //сохраняем текущие параметры запроса
        t_new_rec_info new_rec;
        int fnd_addr_sock = 0;
        new_rec.filter_mode = filter_mode;
        new_rec.src_cnt = src_cnt;
        new_rec.src = src_addrs;
        lip_ipv4_addr_cpy(new_rec.addr, addr);


        //ищем заданную пару сокет-адрес
        for (state = (t_igmp_sock_state*)f_sock_states;
                (state!=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK); // (fnd_state==NULL) &&
                state = f_next_sock_state(state))
        {
            if (lip_ipv4_addr_equ(addr, state->addr))
            {
                /* подсчитываем, сколько было записей с данным mcast-адресом
                 * чтобы понять, изменилось ли состояние для ehternet-уровня
                 * по прослушке адреса  */
                fnd_addr_sock++;
                if (state->sock == sock)
                {
                    fnd_state = state;
                }
            }

        }

        //если не нашли данную пару - то новый запрос - добавляем
        if (fnd_state==NULL)
        {
            //состояние include 0 - не добавляем в список, т.к. соответствует
            //случаю, когда вообще этот сокет не слушаем
            if ((filter_mode != LIP_IGMP_FILTERMODE_INCLUDE) || (src_cnt!=0))
            {
                //проверяем, есть ли место в памяти, чтобы добавить состояние прослушки
                if (((uint8_t*)state - f_sock_states + sizeof(t_igmp_sock_state)
                        + LIP_IPV4_ADDR_SIZE*src_cnt) < LIP_IGMP_SOCKET_STATE_MEMORY_SIZE)
                {
                    /*формируем IGMPv3 отчет о изменении адресов,
                     *если не находимся в режиме совместимости */
                    if (!(f_flags & (LIP_IGMP_FLAG_V1_TIMER_RUN
                            | LIP_IGMP_FLAG_V2_TIMER_RUN)))
                    {
                        err = f_build_change_report(NULL, &new_rec);
                    }
                    if (!err)
                    {
                        //добавляем запись в конец
                        state->sock = sock;
                        state->filter_mode = filter_mode;
                        state->flags = 0;
                        lip_ipv4_addr_cpy(state->addr, addr);
                        state->src_cnt = src_cnt;
                        if ((src_cnt > 0) && ((const uint8_t (*)[LIP_IPV4_ADDR_SIZE])
                                state->src_addrs!=src_addrs))
                        {
                            memcpy(state->src_addrs, src_addrs, src_cnt*LIP_IPV4_ADDR_SIZE);
                        }

                        //сокет следующего состояния устанавливаем в 0, чтобы показать, что больше нет состояний
                        state = f_next_sock_state(state);
                        if (state!=NULL)
                            state->sock = LIP_IGMP_INVALID_SOCK;

                        /*если не было записей с данным адресом, говорим низкому уровню
                         * прослушивать этот соответствующий mac */
                        if (fnd_addr_sock==0)
                        {
                            lip_ipv4_get_mcast_mac(addr, f_mac);
                            lip_ll_join(f_mac);
                            //планируем на отправку IGMPv1 или v2 отчет
                            if ((f_flags & (LIP_IGMP_FLAG_V1_TIMER_RUN
                                           | LIP_IGMP_FLAG_V2_TIMER_RUN)))
                            {
                                f_schedule_group_resp(addr, NULL, 0, 0,
                                    LIP_IGMP_ROBUSTNESS_VARIABLE);
                            }
                        }
                    }
                }
                else
                {
                    err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
                }
            }
        }
        else
        {
            //проверяем, что есть место для сохранения нового состояния
            if ((src_cnt <= fnd_state->src_cnt)
                    || ((uint8_t*)state - f_sock_states +
                            LIP_IPV4_ADDR_SIZE*(src_cnt - fnd_state->src_cnt)
                            < LIP_IGMP_SOCKET_STATE_MEMORY_SIZE))
            {
                /*формируем IGMPv3 отчет о изменении адресов,
                 *если не находимся в режиме совместимости */
                if (!(f_flags & (LIP_IGMP_FLAG_V1_TIMER_RUN
                        | LIP_IGMP_FLAG_V2_TIMER_RUN)))
                {
                    err = f_build_change_report(fnd_state, &new_rec);
                }

                if (!err)
                {
                    //заменяем прошлое состояние прослушки сокета на новое
                    if ((src_cnt != fnd_state->src_cnt) ||
                            ((filter_mode == LIP_IGMP_FILTERMODE_INCLUDE) && (src_cnt!=0)))
                    {
                        //двигаем последующие состояния, если кол-во источников изменилось
                        t_igmp_sock_state* next_state = f_next_sock_state(fnd_state);
                        int move_size = 0;
                        int move_pos;
                        /*определяем размер передвигаемой памяти (разница между адерсом
                         * последнего состояния и следующего за изменяемым*/
                        if (next_state != NULL)
                        {
                            //если state == NULL, то ищем первый недействительный байт
                            if (state==NULL)
                            {
                                t_igmp_sock_state* prev_state = next_state;
                                for (state = next_state; state!=NULL; state = f_next_sock_state(state))
                                {
                                    prev_state = state;
                                }
                                state = (t_igmp_sock_state*)prev_state->src_addrs[prev_state->src_cnt];
                            }

                            move_size = (uint8_t*)state - (uint8_t*)next_state;
                        }

                        if ((filter_mode == LIP_IGMP_FILTERMODE_INCLUDE) && (src_cnt==0))
                        {
                            //если выкидываем всю запись - то сдвигаем на размер записи
                            move_pos = -(sizeof(t_igmp_sock_state)
                                    + fnd_state->src_cnt*LIP_IPV4_ADDR_SIZE);
                        }
                        else
                        {
                            //иначе сдвиг на размер разности в количестве источников
                            move_pos = (src_cnt - fnd_state->src_cnt) * LIP_IPV4_ADDR_SIZE;
                        }
                        if (move_size>0)
                        {
                            memmove((uint8_t*)next_state + move_pos, next_state, move_size);
                        }

                        //получаем указатель на состояние сразу за последним передвинутым
                        state = (t_igmp_sock_state*)((uint8_t*)state + move_pos);
                        //если есть место - устанавливаем недействительный сокет - признак конца
                        if ((&f_sock_states[LIP_IGMP_SOCKET_STATE_MEMORY_SIZE] - (uint8_t*)state)
                                >= (int)sizeof(t_igmp_sock_state))
                        {
                            state->sock = LIP_IGMP_INVALID_SOCK;
                        }
                    }

                    //если запись не удалена совсем, то заполняем ее поля новыми значениями
                    if ((filter_mode != LIP_IGMP_FILTERMODE_INCLUDE) || (src_cnt!=0))
                    {
                        fnd_state->filter_mode = filter_mode;
                        fnd_state->flags = 0;
                        fnd_state->src_cnt = src_cnt;
                        if ((src_cnt > 0) && ((const uint8_t (*)[LIP_IPV4_ADDR_SIZE])
                                fnd_state->src_addrs != src_addrs))
                        {
                            memcpy(fnd_state->src_addrs, src_addrs, src_cnt*LIP_IPV4_ADDR_SIZE);
                        }
                    }
                    else
                    {
                        /*если удалили запись, которая была последняя для этого адреса,
                         * говорим низкому уровню больше не прослушивать соответствующий mac */
                        if (fnd_addr_sock==1)
                        {
                            lip_ipv4_get_mcast_mac(addr, f_mac);
                            lip_ll_leave(f_mac);

                            /* только для IGMPv2 режима отправляем leave report
                             * todo - проверить, нужно ли отправлять
                             * Можем отправлять leave всегда, даже если не последними
                             * были в группе, хотя мб потом лучше сделать механизм
                             * отслеживания, для каких адресов есть еще слушащие хосты
                             */
                            if (!(f_flags & LIP_IGMP_FLAG_V1_TIMER_RUN) &&
                                    (f_flags & LIP_IGMP_FLAG_V2_TIMER_RUN))
                            {
                                t_lip_igmp_response_rec* rec;
                                //сперва так же удаляем отложенные отчеты с этим адресом
                                for (rec = (t_lip_igmp_response_rec*)f_response_state;
                                      (rec != f_last_response);
                                      rec = f_next_response_record(rec))
                                {
                                    if (lip_ipv4_addr_equ(rec->mcast_addr, addr))
                                    {
                                        rec->retr_cnt = 0;
                                    }
                                }
                                f_drop_response_records();

                                f_send_igmp_v1_2_report(addr, 0);
                            }

                        }
                    }
                }
            }
            else
            {
                err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
            }
        }
    }
    return err;
}

void lip_igmp_init(void)
{
    f_flags = 0;
    t_igmp_sock_state* state = (t_igmp_sock_state*)f_sock_states;
    //для указания, что нету прослушиваемых групп нет устанавливаем первый сокет в 0
    state->sock = LIP_IGMP_INVALID_SOCK;

    f_last_retrans_group = (t_lip_igmp_retrans_record*)f_retrans_state;
    f_last_response      = (t_lip_igmp_response_rec*)f_response_state;

    //всегда слушаем ip 224.0.0.1 (mcast для всех хостов)
    lip_ipv4_get_mcast_mac(f_mcast_all_host, f_mac);
    lip_ll_join(f_mac);

    //инициализация ip-info
    memset(&f_ip_info, 0, sizeof(f_ip_info));
    f_ip_info.addr_len = LIP_IPV4_ADDR_SIZE;
    f_ip_info.tos = LIP_IGMP_TOS;
    f_ip_info.ttl = LIP_IGMP_TTL;
    f_ip_info.flags = LIP_IP_FLAG_ROUTER_ALERT;
    f_ip_info.protocol = LIP_PROTOCOL_IGMP;
    f_ip_info.type = LIP_IPTYPE_IPv4;
}

void lip_igmp_pull(void)
{
    if (lip_ipv4_cur_addr_is_valid())
    {
        //в режиме совместимости посылаем только ответы, так как
        if (!(f_flags & (LIP_IGMP_FLAG_V1_TIMER_RUN
                | LIP_IGMP_FLAG_V2_TIMER_RUN)))
        {
            f_send_reports();
        }
        f_send_responses();
    }

    /* если находимся в режиме совместимости с IGMPv1 или IGMPv2
     * проверяем - не пора ли из него выйти
     */
    if ((f_flags & LIP_IGMP_FLAG_V1_TIMER_RUN) &&
            ltimer_expired(&f_v1_tmr))
    {
        f_flags &= ~LIP_IGMP_FLAG_V1_TIMER_RUN;
    }

    if ((f_flags & LIP_IGMP_FLAG_V2_TIMER_RUN) &&
            ltimer_expired(&f_v2_tmr))
    {
        f_flags &= ~LIP_IGMP_FLAG_V2_TIMER_RUN;
    }
}


#define RESPONSE_FREEMEM() (&f_response_state[LIP_IGMP_RESPONSE_STATE_MEMORY_SIZE] - (uint8_t*)f_last_response)




static t_lip_errs f_schedule_group_resp(const uint8_t* mcast_addr,
        const uint8_t src_addrs[][LIP_IPV4_ADDR_SIZE],
        uint16_t src_cnt,
        t_lclock_ticks max_tout,
        uint16_t retr_cnt)
{
    t_lip_errs err = LIP_ERR_SUCCESS;
    t_lip_igmp_response_rec *rec, *fnd_rec = NULL;
    t_lclock_ticks cur_time = lclock_get_ticks();
    //выбираем случайное время для планирования посылки ответа
    if (max_tout > LIP_IGMP_MIN_REPORT_TIMEOUT)
        max_tout = lip_rand_range(max_tout-LIP_IGMP_MIN_REPORT_TIMEOUT);
    max_tout += LIP_IGMP_MIN_REPORT_TIMEOUT;

    //ищем запланированный ответ для того же mcast-адреса
    for (rec = (t_lip_igmp_response_rec*)f_response_state;
               (rec != f_last_response) && (fnd_rec == NULL);
               rec = f_next_response_record(rec))
    {
       if (lip_ipv4_addr_equ(rec->mcast_addr, mcast_addr))
       {
           fnd_rec = rec;
       }
    }

    /*************************************************************************
     * 3. Если это Group[and-Source] Specific запрос, и для этого адреса
     * нет больше запроса, поставленного в очередь - ставим в очередь
     * новый запрос
     ************************************************************************/
    if (fnd_rec==NULL)
    {
        //если достаточна места - запоминаем весь запрос
        if (RESPONSE_FREEMEM() >= (int)(sizeof(t_lip_igmp_response_rec)
                + LIP_IPV4_ADDR_SIZE*src_cnt))
        {
            f_last_response->retr_cnt = retr_cnt;
            lip_ipv4_addr_cpy(f_last_response->mcast_addr, mcast_addr);
            f_last_response->src_cnt = src_cnt;
            ltimer_set_at(&f_last_response->tmr, max_tout, cur_time);
            if (src_cnt > 0)
                memcpy(f_last_response->src, src_addrs, LIP_IPV4_ADDR_SIZE*src_cnt);
            f_last_response = f_next_response_record(f_last_response);
        }
        else if (RESPONSE_FREEMEM() >= (int)sizeof(t_lip_igmp_response_rec))
        {
            //если есть место для запроса без адресов источников - то сохраняем его
            f_last_response->retr_cnt = retr_cnt;
            lip_ipv4_addr_cpy(f_last_response->mcast_addr, mcast_addr);
            f_last_response->src_cnt = 0;
            ltimer_set_at(&f_last_response->tmr, max_tout, cur_time);
            f_last_response = f_next_response_record(f_last_response);

            err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
            lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_WARNING_HIGH, err,
                    "lip igmp: insufficient memory for query src list. group-specific query scheduled\n");
        }
        else
        {
            //если места недостаточно - заменяем запрос на общий и сообщаем об ошибке
            err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
            lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_ERROR_HIGH, err,
                    "lip igmp: insufficient memory for save specific query. query dropped\n");
        }
    }
    else
    {
        /*************************************************************
         *  4. если список адресов для нового запроса пустой, а для
         *      отложенного - нет, удаляем список адресов
         *************************************************************/
        if ((src_cnt == 0) && (fnd_rec->src_cnt != 0))
        {
            t_lip_igmp_response_rec* next_rec = f_next_response_record(fnd_rec);
            uint32_t move_offs = fnd_rec->src_cnt*LIP_IPV4_ADDR_SIZE;
            fnd_rec->src_cnt = 0;
            if (next_rec != f_last_response)
            {
                memmove((uint8_t*)next_rec - move_offs, next_rec,
                        (uint8_t*)f_last_response - (uint8_t*)next_rec);
            }
            f_last_response = (t_lip_igmp_response_rec*)((uint8_t*)f_last_response
                    - move_offs);
        }
        /**************************************************************************
         * 5. если и в прошлой и в новой записи есть список адресов источников
         *    - то результирующий список - объединение адресов
         **************************************************************************/
        else if ((src_cnt != 0) && (fnd_rec->src_cnt!=0))
        {
            uint32_t i,j, new_addrs;
            /* смотрим, сколько новых источников в новом запросе */
            for (i=0, new_addrs = 0; i < src_cnt; i++)
            {
                int fnd = 0;
                for (j=0, fnd = 0; (j < fnd_rec->src_cnt) && !fnd; j++)
                {
                    if (lip_ipv4_addr_equ(fnd_rec->src[j], src_addrs[i]))
                    {
                        fnd = 1;
                    }
                }
                if (!fnd)
                    new_addrs++;
            }

            //смотрим, есть ли место для новых адресов
            if (RESPONSE_FREEMEM() >= (int)(LIP_IPV4_ADDR_SIZE*new_addrs))
            {
                //сдвигаем память, если есть новые адреса
                if (new_addrs > 0)
                {
                    t_lip_igmp_response_rec* next_rec = f_next_response_record(fnd_rec);
                    if (next_rec != f_last_response)
                    {
                        memmove((uint8_t*)next_rec + new_addrs*LIP_IPV4_ADDR_SIZE, next_rec,
                                (uint8_t*)f_last_response - (uint8_t*)next_rec);
                    }
                    f_last_response = (t_lip_igmp_response_rec*)((uint8_t*)f_last_response
                            + new_addrs*LIP_IPV4_ADDR_SIZE);

                    //заполняем новые адреса
                    for (i=0; (i < src_cnt) && (new_addrs!=0); i++)
                    {
                        int fnd = 0;
                        for (j=0, fnd = 0; (j < fnd_rec->src_cnt) && !fnd; j++)
                        {
                            if (lip_ipv4_addr_equ(fnd_rec->src[j], src_addrs[i]))
                            {
                                fnd = 1;
                            }
                        }
                        if (!fnd)
                        {
                            lip_ipv4_addr_cpy(fnd_rec->src[fnd_rec->src_cnt], src_addrs[i]);
                            fnd_rec->src_cnt++;
                            new_addrs--;
                        }
                    }
                }
            }
            else
            {
                t_lip_igmp_response_rec* next_rec = f_next_response_record(fnd_rec);
                uint32_t move_offs = fnd_rec->src_cnt*LIP_IPV4_ADDR_SIZE;
                fnd_rec->src_cnt = 0;
                if (next_rec != f_last_response)
                {
                    memmove((uint8_t*)next_rec - move_offs, next_rec,
                            (uint8_t*)f_last_response - (uint8_t*)next_rec);
                }
                f_last_response = (t_lip_igmp_response_rec*)((uint8_t*)f_last_response
                        - move_offs);
                //если места недостаточно - выкидываем все адреса источников
                err = LIP_ERR_IGMP_INSUFFICIENT_MEMORY;
                lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_ERROR_LOW, err,
                        "lip igmp: insufficient memory for save src addrs from query. group specific response scheduled\n");

            }
        } //if ((hdr->src_num != 0) && (fnd_rec->src_cnt!=0))
        /******************************************************************************
         * 4b. в случае, если fnd_rec->src_cnt == 0, новые записи не добавляются
         * ничего менять не нужно
         * ****************************************************************************/

        //время передачи устанавливаем минимальное, между новой и старой записями
        if (ltimer_expiration_from(&fnd_rec->tmr, cur_time) > max_tout)
        {
            ltimer_set_at(&fnd_rec->tmr, max_tout, cur_time);
        }
    }
    return err;
}




/***********************************************************************************
 *  Обработка входящего IGMP-пакета. Вызывается ядром стека, после обработки в ip
 ***********************************************************************************/
t_lip_errs lip_igmp_process_packet(const t_lip_ip_rx_packet *ip_packet) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (ip_packet->pl.size < 1) {
        err = LIP_ERR_RXBUF_TOO_SMALL;
    } else {
        const t_lip_igmp_query* hdr = (const t_lip_igmp_query*)ip_packet->pl.data;
        uint16_t chk_sum;

        //поверяем контрольную сумму пакета
        chk_sum = lip_chksum(0, ip_packet->pl.data, ip_packet->pl.size);
        if (chk_sum!=0xFFFF) {
            err = LIP_ERR_IGMP_CHECKSUM;
            lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_WARNING_MEDIUM, err,
                    "lip igmp: incomming igmp packet checksum error\n");
        }

        if (err == LIP_ERR_SUCCESS) {
            //обрабатываем только запросы, остальное выкидываем
            if (hdr->type == LIP_IGMP_MSG_TYPE_QUERY) {
                t_lclock_ticks tout;
                //определяем версию запроса (rfc3376 пункт 7 page 35)
                if (ip_packet->pl.size == LIP_IGMP_V1V2_QUERY_SIZE) {
                    t_lclock_ticks query_int = ((f_flags & LIP_IGMP_FLAG_LAST_QUERY_TIME_VALID) ?
                          (lclock_get_ticks()-f_last_general_query_received) :
                          LTIMER_MS_TO_CLOCK_TICKS(LIP_IGMP_DEFAULT_QUERY_INTERVAL))
                          * LIP_IGMP_ROBUSTNESS_VARIABLE;
#ifdef LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MIN
                    if (query_int < LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MIN) {
                        query_int = LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MIN;
                    }
#endif
#ifdef LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MAX
                    if (query_int > LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MAX) {
                        query_int = LIP_IGMP_OLDER_VER_QUERIER_PRESENT_TOUT_MAX;
                    }
#endif
                    if (hdr->max_resp_code == 0) {
                        //IGMPv1
                        if (!(f_flags & LIP_IGMP_FLAG_V1_TIMER_RUN)) {
                            /* если не находились в режиме совместимости с IGMPv1
                             * - переходим в него - отменяем все предыдущие
                             * запланированные отчеты
                             */
                            f_last_response = (t_lip_igmp_response_rec*)f_response_state;
                            f_last_retrans_group = (t_lip_igmp_retrans_record*)f_retrans_state;
                            f_flags |= LIP_IGMP_FLAG_V1_TIMER_RUN;
                        }
                        //в IGMPv1 максимальное время ответа всегда 10 сек.
                        tout = 10*LCLOCK_TICKS_PER_SECOND;
                        //перезапускаем таймер на совместимость с v1
                        ltimer_set(&f_v1_tmr, query_int+tout);
                    } else {
                        //IGMPv2
                        if (!(f_flags & (LIP_IGMP_FLAG_V1_TIMER_RUN
                                | LIP_IGMP_FLAG_V2_TIMER_RUN))) {
                            /**********************************************************
                             * переходим в режим совместимости IGMPv2, если уже
                             * не находимся в нем или в режиме совместимости с IGMPv1
                             **********************************************************/
                            f_last_response = (t_lip_igmp_response_rec*)f_response_state;
                            f_last_retrans_group = (t_lip_igmp_retrans_record*)f_retrans_state;
                        }
                        /* в IGMPv2 максимальное время ответа берется из
                         * max_resp_code, где оно заданов в 0.1 сек
                         * (без варианта экспоненциальной зависимости) */
                        f_flags |= LIP_IGMP_FLAG_V2_TIMER_RUN;
                        tout = hdr->max_resp_code*LCLOCK_TICKS_PER_SECOND/10;
                        ltimer_set(&f_v2_tmr, query_int+tout);
                    }


                    //если общий запрос
                    if (!lip_ipv4_addr_is_valid(hdr->group_addr)) {
                        t_igmp_sock_state *state;
                        /* планируем на отправку все адреса, которые слушаем */
                        for (state = (t_igmp_sock_state*)f_sock_states;
                                (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && !err;
                                 state = f_next_sock_state(state)) {
                            int fnd = 0;
                            //проверяем, что этот адрес новый
                            if (state != (t_igmp_sock_state*)f_sock_states) {
                                t_igmp_sock_state *old_state;
                                for (old_state = (t_igmp_sock_state*)f_sock_states;
                                    (old_state !=state) && !fnd;
                                    old_state = f_next_sock_state(old_state)) {
                                    if (lip_ipv4_addr_equ(old_state->addr, state->addr))
                                        fnd = 1;
                                }
                            }

                            if (!fnd) {
                                err = f_schedule_group_resp(state->addr, NULL, 0, tout, 1);
                            }
                        }
                        lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                "lip igmp: IGMPv1/IGMPv2 general query received\n");
                        //сохраняем время последнего запроса
                        f_last_general_query_received = lclock_get_ticks();
                    } else {
                        /* если приняли group-specific запрос - то проверяем, слушаем ли адрес
                         * и, если да - планируем запрос на передачу */
                        int fnd = 0;
                        t_igmp_sock_state *state;
                        for (state = (t_igmp_sock_state*)f_sock_states;
                                (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && !fnd;
                                state = f_next_sock_state(state)) {
                            if (lip_ipv4_addr_equ(state->addr, hdr->group_addr))
                                fnd = 1;
                        }

                        if (fnd) {
                            err = f_schedule_group_resp(hdr->group_addr, NULL, 0,  tout, 1);
                        }

                        lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                  "lip igmp: IGMPv1/IGMPv2 query received for group %d.%d.%d.%d\n",
                                  hdr->group_addr[0], hdr->group_addr[1], hdr->group_addr[2], hdr->group_addr[3]);
                    }

                } else if (ip_packet->pl.size >= LIP_IGMP_V3_QUERY_MIN_SIZE) {

                    /* рассчитываем интервал, через который планируем передать ответ */
                    //если код < 128 - то это просто время в 100 мс единицах
                    if (hdr->max_resp_code < 128) {
                        tout = hdr->max_resp_code;
                    } else {
                        //иначе - экспон. формула
                        tout = ((hdr->max_resp_code &0xF) | 0x10)
                                << (((hdr->max_resp_code&0x70)>>4) + 3);
                    }
                    tout=tout*LCLOCK_TICKS_PER_SECOND/10;


                    /***********************************************************
                     * обрабатываем запрос в соответствии с RFC3376 page 22
                     **********************************************************/
                    if ((hdr->src_num == 0) && !lip_ipv4_addr_is_valid(hdr->group_addr)) {
                        t_igmp_sock_state *state;
                        /* так как на стр. 23 в пункте 1 написано, что общий запрос рекомендуется
                         * посылать не целиком, а распределенным в интервале (0-max_resp_time),
                         * то общие запросы мы заменяем на набор group-specific запросов для каждого
                         * адреса интерфейса. таким образом 1 и 2 пункт на стр. 22 исчезают
                         */

                        //получаем список mcast адресов и для каждого шлем запрос
                        for (state = (t_igmp_sock_state*)f_sock_states;
                                (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && !err;
                                 state = f_next_sock_state(state)) {
                            int fnd = 0;
                            //проверяем, что этот адрес новый
                            if (state != (t_igmp_sock_state*)f_sock_states) {
                                t_igmp_sock_state *old_state;
                                for (old_state = (t_igmp_sock_state*)f_sock_states;
                                    (old_state !=state) && !fnd;
                                    old_state = f_next_sock_state(old_state)) {
                                    if (lip_ipv4_addr_equ(old_state->addr, state->addr))
                                        fnd = 1;
                                }
                            }

                            if (!fnd)  {
                                err = f_schedule_group_resp(state->addr, (const uint8_t (*)[LIP_IPV4_ADDR_SIZE])state->src_addrs,
                                                        state->src_cnt, tout, 1);
                            }
                        }

                        lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                      "lip igmp: IGMPv3  general query received\n");

                        //сохраняем время последнего запроса
                        f_last_general_query_received = lclock_get_ticks();


                    } else if (lip_ipv4_addr_is_valid(hdr->group_addr)) {
                        lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                     "lip igmp: IGMPv3 query received for group %d.%d.%d.%d, src cnt = %d\n",
                                     hdr->group_addr[0], hdr->group_addr[1], hdr->group_addr[2], hdr->group_addr[3],
                                     hdr->src_num);

                        err = f_schedule_group_resp(hdr->group_addr,
                                (const uint8_t (*)[LIP_IPV4_ADDR_SIZE])hdr->src_addrs,
                                hdr->src_num, tout, 1);
                    } //if (lip_ipv4_addr_valid(hdr->group_addr))
                } //if (size >= LIP_IGMP_V3_QUERY_MIN_SIZE)
            } //if (hdr->type == LIP_IGMP_MSG_TYPE_QUERY)
            /**********************************************************************
             * если находимся в режиме совместимости с IGMPv1 или IGMPv2 и слышим
             * отчет для версии 1 или 2, то, если есть запланированный отчет для
             * этого же адреса - то удаляем его
             *********************************************************************/
            else if ((f_flags & (LIP_IGMP_FLAG_V1_TIMER_RUN
                    | LIP_IGMP_FLAG_V2_TIMER_RUN)) &&
                    ((hdr->type == LIP_IGMP_MSG_TYPE_V1_REPORT) ||
                            (hdr->type == LIP_IGMP_MSG_TYPE_V2_REPORT))
                            && (ip_packet->pl.size >= LIP_IGMP_V1V2_REPORT_SIZE)
                            ) {
                t_lip_igmp_response_rec* rec;
                const t_lip_igmp_v1_2_report* rep = (const t_lip_igmp_v1_2_report*)hdr;
                /* IGMPv1, v2 отчеты всегда посылаются по тому же адресу
                 * о принадлежности к которому они объявляют. Если это не так -
                 * не обрабатываем (в соответствии с rfc)
                 */
                if (lip_ipv4_addr_equ(rep->group_addr, ip_packet->ip_info.da)) {
                    for (rec = (t_lip_igmp_response_rec*)f_response_state;
                          (rec != f_last_response);
                          rec = f_next_response_record(rec)) {
                        if (lip_ipv4_addr_equ(rec->mcast_addr, rep->group_addr)) {
                            rec->retr_cnt = 0;
                            lip_printf(LIP_LOG_MODULE_IGMP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                    "lip igmp: suppressed IGMPv1/IGMPv2 response for address %d.%d.%d.%d\n",
                                    rec->mcast_addr[0], rec->mcast_addr[1], rec->mcast_addr[2], rec->mcast_addr[3]);
                        }
                    }
                    f_drop_response_records();
                }
            }
        } //if (!err)
    }
    return err;
}


/*********************************************************************
 * Проверка, разрешено ли принимать пакеты с заданного адреса
 * от указанного адреса источника
 ******************************************************************/
int lip_igmp_check_mcast_addr(const uint8_t* mcast_addr, const uint8_t* src_addr)
{
    uint8_t fnd = 0;
    uint16_t i;
    t_igmp_sock_state* state;

    /*пакеты на адрес, соответствующий всем локальным хостам принимаем всегда */
    if (lip_ipv4_addr_equ(mcast_addr, f_mcast_all_host))
        fnd = 1;

    /*адрес разрешено принимать, если есть состояние с заданным mcast_addr,
     * что указанный src_addr есть или в списке include или нет в списке exclude
     */

    for (state = (t_igmp_sock_state*)f_sock_states;
            (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && !fnd;
             state = f_next_sock_state(state))
    {
        if (lip_ipv4_addr_equ(mcast_addr, state->addr))
        {
            if (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
                fnd = 1;
            for (i = 0; i < state->src_cnt; i++)
            {
                if (lip_ipv4_addr_equ(state->src_addrs[i], src_addr))
                {
                    fnd = (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE) ? 0 : 1;
                }
            }
        }
    }
    return fnd ? LIP_ERR_SUCCESS : LIP_ERR_IGMP_SRC_NOT_FOUND;
}





/* повторное объявление состояний прослушиваемых сокетов */
static t_lip_errs f_readvertise_sock_states(void)
{
    t_lip_errs res = LIP_ERR_SUCCESS;
    t_igmp_sock_state *state = (t_igmp_sock_state*)f_sock_states, *next_state;
    t_lip_igmp_sock next_sock = LIP_IGMP_INVALID_SOCK, cur_sock;

    /* очищаем очередь на передачу отчетов */
    f_last_retrans_group = (t_lip_igmp_retrans_record*)f_retrans_state;
    f_last_response      = (t_lip_igmp_response_rec*)f_response_state;

    /* повторно добавляем на прослушку существующие состояния сокетов */
    for (state = (t_igmp_sock_state*)f_sock_states, cur_sock = state->sock;
                  (state!=NULL) && (cur_sock != LIP_IGMP_INVALID_SOCK)
                          && (res == LIP_ERR_SUCCESS);
                  state = next_state, cur_sock = next_sock)
    {
        /* получаем указатель на следующее состояние и сохраняем
         * его сокет, так как вызов lissen обнулит его */
        next_state = f_next_sock_state(state);
        if (next_state)
        {
            next_sock = next_state->sock;
        }
        else
        {
            next_sock = LIP_IGMP_INVALID_SOCK;
        }
        /* обнуляем текущий сокет, чтобы lissen посчитал его недейстительным
         * и поставил запрос именно на его место */
        state->sock = LIP_IGMP_INVALID_SOCK;
        res = lip_igmp_listen(cur_sock, state->addr, state->filter_mode,
                (const uint8_t(*)[LIP_IPV4_ADDR_SIZE])state->src_addrs, state->src_cnt);
    }
    return res;
}





/*********************************************************************
 * Проверка, разрешено ли принимать пакеты с заданного адреса
 * для заданного сокета от указанного адреса источника
 ******************************************************************/
t_lip_errs lip_igmp_check_sock(t_lip_igmp_sock sock, const uint8_t* mcast_addr, const uint8_t* src_addr)
{
    uint8_t fnd = 0, fnd_rec = 0;
    uint16_t i;
    t_igmp_sock_state* state;
    /*************************************************************************
     * ищем конкретную запись - с указанной парой mcast_addr и sock
     *************************************************************************/
    for (state = (t_igmp_sock_state*)f_sock_states;
            (state !=NULL) && (state->sock != LIP_IGMP_INVALID_SOCK) && !fnd_rec;
             state = f_next_sock_state(state))
    {
        if ((state->sock == sock) && lip_ipv4_addr_equ(mcast_addr, state->addr))
        {
            /* разрешаем, если не входит в exclude или входит в include список */
            fnd_rec = 1;
            if (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE)
                fnd = 1;
            for (i = 0; i < state->src_cnt; i++)
            {
                if (lip_ipv4_addr_equ(state->src_addrs[i], src_addr))
                {
                    fnd = (state->filter_mode == LIP_IGMP_FILTERMODE_EXCLUDE) ? 0 : 1;
                }
            }
        }
    }
    return fnd ? LIP_ERR_SUCCESS : LIP_ERR_IGMP_SRC_NOT_FOUND;
}


/**************************************************************************************************
 * функция вызывается при подключении к новому link'у.
 * сбрасываем режим совместимости и повторно переобъявляем группы
 **************************************************************************************************/
t_lip_errs lip_igmp_reconnect(void)
{
    /* сбрасываем режим совместимости - т.к. мб уже в другой сети */
    f_flags = 0;
    return f_readvertise_sock_states();
}



/**************************************************************************************************
 * функция вызывается при изменении активного адреса
 * повторно переобъявляем группы при действительном ip-адресе
 **************************************************************************************************/
t_lip_errs lip_igmp_addr_changed(void)
{
    return lip_ipv4_cur_addr_is_valid() ? f_readvertise_sock_states() : LIP_ERR_SUCCESS;
}



#endif
