/*
 * lip_ipv4_acd.c
 *  Файл содержит процедуру для проверки единственности IPv4-адреса в сети
 *     в соответствии с rfc5227
 *     используется для определения конфиликтов в link-local адресах (модуль lip_ip_link_local)
 *     и для проверки полученного от сервера адреса DHCPv4-клиентом (модуль lip_dhcpc)
 *
 *  Внешний модуль запрашивает проверку адреса с помощью lip_ipv4_acd_probe,
 *     указывая помимо адреса callback с помощью которого ACD будет уведомлять
 *     модуль о событиях, связанных с запрашиваемым адресом.
 *     Модуль ACD посылает LIP_IPV4_ACD_PROBE_NUM проб - запросов на заданный адрес.
 *     Если через интервал LIP_IPV4_ACD_ANNOUNCE_WAIT не произошло конфликтов, то
 *     адрес считается установленным и вызывается callback с признаком подтверждения адреса.
 *     Так же ACD шлет LIP_IPV4_ACD_ANNOUNCE_NUM анонсов адреса.
 *  После проверки адреса, он не удаляется из ACD, так как модуль будет вести пассивное
 *     отслеживание конфликтов по этому адресу и вызывет callback при возникновении
 *     конфликта. При этом при первом конфликтном пакете модуль пытается защитить
 *     свой адрес (повторным анонсом), и только при повторном за заданный интервал
 *     конфликтном пакете считается, что произошел конфликт
 *     (вариант (b) из пункта 2.4 (page 12-13)).
 *  Адрес удаляется из списка отслеживаемых либо при конфликте (после вызова callback'а),
 *     либо при явном вызове другим модулем lip_ipv4_acd_leave
 *
 *  Модуль использует: lip_ip, lip_arp, lip_rand
 *
 *  Created on: 25.07.2011
 *      Author: Borisov
 */

#include "lip_ipv4_acd.h"

#ifdef LIP_USE_IPV4_ACD
#include "lip/misc/lip_rand_cfg_defs.h"
#if !LIP_RAND_ENABLE
#error "IPv4 Address Conflict Detection requires random generator. define LIP_CFG_RAND_ENABLE in lip_config.h"
#endif

#include "lip/net/ipv4/lip_ip_private.h"
#include "lip/misc/lip_rand.h"
#include "lip/link/lip_arp.h"
#include "lip/lip_private.h"
#include "ltimer.h"


/************************************************************************
 *  временные параметры, которые использует протокол (времена в мс)
 *  определены в соответствии с rfc5227 пункт 1.1 (page 5)
 *  могут переопределяться соответвующими константами из  lip_conf.h
 ***********************************************************************/

/* начальная задержка перед посылкой первой проверки адреса между min и max */
#ifndef LIP_IPV4_ACD_PROBE_WAIT_MIN
    #define LIP_IPV4_ACD_PROBE_WAIT_MIN             150
#endif
#ifndef LIP_IPV4_ACD_PROBE_WAIT_MAX
    #define LIP_IPV4_ACD_PROBE_WAIT_MAX             1000
#endif
/* количество проверок адреса */
#ifndef LIP_IPV4_ACD_PROBE_NUM
    #define LIP_IPV4_ACD_PROBE_NUM                  3
#endif
/* интервал между проверками адреса между min и max */
#ifndef LIP_IPV4_ACD_PROBE_INTERVAL_MIN
    #define LIP_IPV4_ACD_PROBE_INTERVAL_MIN         1000
#endif
#ifndef LIP_IPV4_ACD_PROBE_INTERVAL_MAX
    #define LIP_IPV4_ACD_PROBE_INTERVAL_MAX         2000
#endif
/* интервал между проверками адреса с флагом FAST между min и max */
#ifndef LIP_IPV4_ACD_PROBE_FAST_INTERVAL_MIN
    #define LIP_IPV4_ACD_PROBE_FAST_INTERVAL_MIN    400
#endif
#ifndef LIP_IPV4_ACD_PROBE_FAST_INTERVAL_MAX
    #define LIP_IPV4_ACD_PROBE_FAST_INTERVAL_MAX    500
#endif

/* задержка после последней проверки перед тем как адрес считаем действительным
 * и можем аннонсировать */
#ifndef LIP_IPV4_ACD_ANNOUNCE_WAIT
    #define LIP_IPV4_ACD_ANNOUNCE_WAIT              2000
#endif
/* Количество посылок анонса адреса */
#ifndef LIP_IPV4_ACD_ANNOUNCE_NUM
    #define LIP_IPV4_ACD_ANNOUNCE_NUM               2
#endif
/* интервал между анонсированиями адреса */
#ifndef LIP_IPV4_ACD_ANNOUNCE_INTERVAL
    #define LIP_IPV4_ACD_ANNOUNCE_INTERVAL          2000
#endif
/* интервал после обнаружения конфиликта и посылки защитного аннонса в течении
 * которого повторный конфиликный пакет приведет к решению о завершении использования адреса */
#ifndef LIP_IPV4_ACD_DEFEND_INTERVAL
    #define LIP_IPV4_ACD_DEFEND_INTERVAL            10000
#endif




/*********  определяем требуемое количество одновременно отслеживаемых адресов ****/
#ifdef LIP_USE_DHCPC
    #define LIP_IPV4_ACD_DHCP_CNT       1
#else
    #define LIP_IPV4_ACD_DHCP_CNT       0
#endif

#ifdef LIP_USE_LINK_LOCAL_ADDR
    #define LIP_IPV4_ACD_LINKLOCAL_CNT  1
#else
    #define LIP_IPV4_ACD_LINKLOCAL_CNT  0
#endif

#ifdef LIP_IPV4_USE_ADDR_ANNOUNCE
    #define LIP_IPV4_ACD_IP_STATIC_CNT  1
#else
    #define LIP_IPV4_ACD_IP_STATIC_CNT  0
#endif


#ifndef LIP_IPV4_ACD_USER_ADDR_CNT
    #define LIP_IPV4_ACD_USER_ADDR_CNT  0
#endif



#define LIP_IPV4_ACD_ADDR_CNT (LIP_IPV4_ACD_DHCP_CNT \
    + LIP_IPV4_ACD_LINKLOCAL_CNT\
    + LIP_IPV4_ACD_IP_STATIC_CNT\
    + LIP_IPV4_ACD_USER_ADDR_CNT)





/* признак, что адрес проверен и осуществляем его анонсирование */
#define LIP_IPV4_ACD_FLAGS_ANNOUNCED    0x1
/* признак, что пришел конфликтный ARP, но мы попытались защитить свой адрес */
#define LIP_IPV4_ACD_FLAGS_DEFENDING    0x2



/* структура с состоянием проверяемого адреса  */
typedef struct {
    uint8_t addr[LIP_IPV4_ADDR_SIZE]; /* проверяемый IPv4 адрес */
    uint8_t cfg_flags; /* флаги, переданные в probe */
    uint8_t flags; /* флаги - определяют состояние */
    uint8_t snd_cnt; /* количество посылок PROBE или ANNONCE (в зависимости от состояния) */
    t_lip_ipv4_acd_cb cb; /* callback на возникновение конфликта или завершение проверки */
    t_ltimer tmr; /* таймер для определения моментов передач пакетов */
    t_ltimer defend_tmr; /* таймер на защитный интервал (отдельный, так как
                        *  может использоваться в параллель с tmr) */
} t_lip_ipv4_acd_state;


/* текущее кол-во отслеживаемых адресов */
static uint8_t f_addr_cnt;
/* информация по отслеживаемым адресам */
static t_lip_ipv4_acd_state f_addrs[LIP_IPV4_ACD_ADDR_CNT];


static void f_drop_record(int i) {
    if (i != (f_addr_cnt - 1)) {
        /* нашли адрес - удаляем сдвигом адресов идущих после */
        memmove(&f_addrs[i], &f_addrs[i + 1], (f_addr_cnt - i - 1) * sizeof(f_addrs[0]));
    }
    --f_addr_cnt;
}

static void f_start_addr(t_lip_ipv4_acd_state *addr_st, unsigned init_tout) {
    addr_st->flags = 0;
    addr_st->snd_cnt = 0;

    if (addr_st->cfg_flags & LIP_IPV4_ADC_FLAGS_SKIP_PROBE) {
        addr_st->flags |= LIP_IPV4_ACD_FLAGS_ANNOUNCED;
    }


    /* если init_tout задан равным нулю - ждем случайное
     * время от LIP_IPV4_ACD_PROBE_WAIT_MIN до LIP_IPV4_ACD_PROBE_WAIT_MAX
     * перед первой пробой  */
    if (init_tout == 0) {
        init_tout = LIP_IPV4_ACD_PROBE_WAIT_MIN
                + lip_rand_range(LIP_IPV4_ACD_PROBE_WAIT_MAX - LIP_IPV4_ACD_PROBE_WAIT_MIN);
    }
    ltimer_set_ms(&addr_st->tmr, init_tout);
}

/************************************************
 * Инициализация модуля
 ************************************************/
void lip_ipv4_acd_init(void) {
    f_addr_cnt = 0;
}

void lip_ipv4_acd_reconnect() {
    for (int i = f_addr_cnt - 1; i >= 0; --i) {
        t_lip_ipv4_acd_state *addr_st = &f_addrs[i];
        if (addr_st->cfg_flags & LIP_IPV4_ADC_FLAGS_PERSIST) {
            f_start_addr(addr_st, 0);
        } else {
            f_drop_record(i);
        }
    }
}


/***************************************************************************************
 * Запуск процедуры проверки адреса
 * Параметры
 *    addr       - проверяемый IPv4 адрес
 *    cb         - callback на обнаружение конфликта и на завершение проверки
 *    init_tout  - таймаут на посылку первой пробы. Если 0 - то выбирается
 *                 автоматически из rfc
 *                 (не 0 может использоваться при повторном выборе адреса,
 *                  если до этого был конфликт)
 * Возвращаемое значение:
 *    код ошибки
 **************************************************************************************/
t_lip_errs lip_ipv4_acd_add(const uint8_t *addr, t_lip_ipv4_acd_cb cb, uint32_t init_tout, uint8_t flags) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    t_lip_ipv4_acd_state *addr_st = NULL;
    int i;
    /* смотрим - не используем ли мы уже этот адрес
     */
    for (i = 0; (i < f_addr_cnt) && (addr_st == NULL) && (res == LIP_ERR_SUCCESS); ++i) {
        t_lip_ipv4_acd_state *check_addr_st = &f_addrs[i];
        if (lip_ipv4_addr_equ(addr, check_addr_st->addr)) {
            /* если адрес используется с тем же cb, то просто запускаем новую проверку */
            if ((check_addr_st->cb == NULL) || (cb == check_addr_st->cb)) {
                addr_st = check_addr_st;
            } else {
                res = LIP_ERR_ACD_ADDR_IN_USE;
                lip_printf(LIP_LOG_MODULE_IPV4_ACD, LIP_LOG_LVL_ERROR_HIGH, res,
                        "lip ipv4 acd: requested ipv4 address already in acd list\n");
            }
        }
    }

    if ((res == LIP_ERR_SUCCESS) && (addr_st == NULL)) {
        if (!(f_addr_cnt < LIP_IPV4_ACD_ADDR_CNT)) {
            res = LIP_ERR_ACD_UNSUF_MEMORY;
            lip_printf(LIP_LOG_MODULE_IPV4_ACD, LIP_LOG_LVL_ERROR_HIGH, res,
                   "lip ipv4 acd: insufficient space to save acd state for ip\n");
        } else {
            addr_st = &f_addrs[f_addr_cnt];
            ++f_addr_cnt;
        }
    }

    if (res == LIP_ERR_SUCCESS) {
        lip_ipv4_addr_cpy(addr_st->addr, addr);
        addr_st->cb = cb;
        addr_st->cfg_flags = flags;
        f_start_addr(addr_st, init_tout);

    }
    return res;
}

/*****************************************************************************
 * Завершаем использование указанного адреса
 *   Прекаращаем его проверку (если ведется) и прекращаем отслеживать
 *   конфликты (просто исключаем его из списка)
 ***************************************************************************/
t_lip_errs lip_ipv4_acd_leave(const uint8_t *addr) {
    t_lip_errs res = LIP_ERR_SUCCESS;

    int i;
    /* ищем указанные адреса */
    for (i = f_addr_cnt - 1; i >= 0; --i) {
        t_lip_ipv4_acd_state *addr_st = &f_addrs[i];
        if (lip_ipv4_addr_equ(addr, addr_st->addr)) {
            f_drop_record(i);
        }
    }
    return res;
}

/******************************************************************************
 * Выполнение фоновых задач модуля - отслеживаются моменты времени для
 * передач проб и анонсов адреса, отслеживается истечение defend interval
 ******************************************************************************/
t_lip_errs lip_ipv4_acd_pull(void) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    int i;
    for (i = 0; i < f_addr_cnt; ++i) {
        t_lip_ipv4_acd_state *addr_st = &f_addrs[i];
        /**************************************************************
         * находимся еще в состоянии проверки пакета => отслеживаем
         * момент, чтобы послать новую пробу
         *************************************************************/
        if (!(addr_st->flags & LIP_IPV4_ACD_FLAGS_ANNOUNCED)) {
            if (ltimer_expired(&addr_st->tmr)) {
                if (addr_st->snd_cnt >= LIP_IPV4_ACD_PROBE_NUM) {
                    /* по завершению ANNOUNCENT_WAIT после последней пробы
                     * переходим в состояние с утвержденным адресом
                     */
                    addr_st->flags = LIP_IPV4_ACD_FLAGS_ANNOUNCED;
                    addr_st->snd_cnt = 0;
                } else if ((lip_arp_send_probe(addr_st->addr) == LIP_ERR_SUCCESS) &&
                            (++addr_st->snd_cnt >= LIP_IPV4_ACD_PROBE_NUM)) {
                    /* завершили нужно кол-во проб - ждем ANNOUNCENT_WAIT перед
                     * первым анонсом выбранного адреса */
                    ltimer_set_ms(&addr_st->tmr, LIP_IPV4_ACD_ANNOUNCE_WAIT);
                } else {
                    /* планируем следующую пробу в интервале от MIN до MAX  */
                    ltimer_set_ms(&addr_st->tmr,
                                  ((addr_st->cfg_flags & LIP_IPV4_ADC_FLAGS_FAST_PROBE) ?
                                       LIP_IPV4_ACD_PROBE_FAST_INTERVAL_MIN : LIP_IPV4_ACD_PROBE_INTERVAL_MIN)  +
                                 lip_rand_range((addr_st->cfg_flags & LIP_IPV4_ADC_FLAGS_FAST_PROBE) ?
                                                    (LIP_IPV4_ACD_PROBE_FAST_INTERVAL_MAX - LIP_IPV4_ACD_PROBE_FAST_INTERVAL_MIN) :
                                                    (LIP_IPV4_ACD_PROBE_INTERVAL_MAX - LIP_IPV4_ACD_PROBE_INTERVAL_MIN)));
                }
            }
        } else {
            /* шлем анонс адреса, если еще нужно */
            if ((addr_st->snd_cnt < LIP_IPV4_ACD_ANNOUNCE_NUM) && ltimer_expired(&addr_st->tmr)) {
                if (lip_arp_send_announce(addr_st->addr) == LIP_ERR_SUCCESS) {
                    /* по первому успешному анонсу считаем, что адрес выбран успешно -
                     * оповещаем запрашивающего об этом */
                    if ((addr_st->snd_cnt == 0) && addr_st->cb) {
                        addr_st->cb(addr_st->addr, LIP_IPV4_ACD_EVENT_ADDR_ESTABLISHED);
                    }
                    ++addr_st->snd_cnt;
                }
                ltimer_set(&addr_st->tmr, LTIMER_MS_TO_CLOCK_TICKS(LIP_IPV4_ACD_ANNOUNCE_INTERVAL));
            }

            /* если пытались защитить свой адрес и прошел с этого момента DEFEND_INTERVAL
             * адрес оставляем - все ок  */
            if ((addr_st->flags & LIP_IPV4_ACD_FLAGS_DEFENDING) && ltimer_expired(&addr_st->defend_tmr)) {
                addr_st->flags &= ~LIP_IPV4_ACD_FLAGS_DEFENDING;
            }
        }
    }
    return res;
}

/***************************************************************************************
 *  callback-функция, выполняющая проверку пришедших ARP-пакетов на конфликты
 *  с текущим адресом (вызывается из arp модуля)
 **************************************************************************************/
void lip_ipv4_acd_arp_cb(const uint8_t *src_addr, const uint8_t *dst_addr, const uint8_t *src_mac) {
    int i;
    for (i = 0; i < f_addr_cnt; ++i) {
        t_lip_ipv4_acd_state *addr_st = &f_addrs[i];
        if (!(addr_st->flags & LIP_IPV4_ACD_FLAGS_ANNOUNCED)) {
            /* проверяем, произошел ли конфликт конфликт на стадии проверки адреса:
             * 1. пришел ARP с адресом-источником равным тому, что мы пытались получить
             * 2. пришел ARP-probe с другим mac-адресом */
            if (lip_ipv4_addr_equ(src_addr, addr_st->addr)
                    || (!lip_ipv4_addr_is_valid(src_addr)
                            && lip_ipv4_addr_equ(dst_addr, addr_st->addr)
                            && memcmp(src_mac, lip_eth_cur_mac(), LIP_MAC_ADDR_SIZE))) {
                /* конфликт - сообщаем запрашивающему и удаляем адрес */
                if (addr_st->cb) {
                    addr_st->cb(addr_st->addr, LIP_IPV4_ACD_EVENT_ADDR_CONFLICT);
                }
                lip_printf(LIP_LOG_MODULE_IPV4_ACD, LIP_LOG_LVL_WARNING_MEDIUM, LIP_ERR_ACD_ADDR_CONFLICT,
                        "lip ipv4 acd: conflict arp detected for addr %d.%d.%d.%d\n",
                        addr_st->addr[0], addr_st->addr[1], addr_st->addr[2], addr_st->addr[3]);

                lip_ipv4_acd_leave(addr_st->addr);
            }
        } else {
            /* Проверяем, произошел ли конфликт при установленном адресе  -
             * конфликт - если не наш пакет (не наш src_mac), но наш ip в src_addr*/
            if (memcmp(src_mac, lip_eth_cur_mac(), LIP_MAC_ADDR_SIZE)
                    && lip_ipv4_addr_equ(src_addr, addr_st->addr)) {
                /* если это первый конфликтный пакет, то пытаемся защитить адрес */
                if (!(addr_st->flags & LIP_IPV4_ACD_FLAGS_DEFENDING)) {
                    lip_arp_send_announce(addr_st->addr);
                    addr_st->flags |= LIP_IPV4_ACD_FLAGS_DEFENDING;
                    ltimer_set_ms(&addr_st->defend_tmr, LIP_IPV4_ACD_DEFEND_INTERVAL);
                } else {
                    /* конфликт - сообщаем запрашивающему и удаляем адрес */
                    if (addr_st->cb) {
                        addr_st->cb(addr_st->addr, LIP_IPV4_ACD_EVENT_ADDR_CONFLICT);
                    }
                    lip_printf(LIP_LOG_MODULE_IPV4_ACD, LIP_LOG_LVL_WARNING_MEDIUM, LIP_ERR_ACD_ADDR_CONFLICT,
                            "lip ipv4 acd: conflict arp detected for addr %d.%d.%d.%d\n",
                            addr_st->addr[0], addr_st->addr[1], addr_st->addr[2], addr_st->addr[3]);
                    lip_ipv4_acd_leave(addr_st->addr);
                }
            }
        }
    } //for (i=0; i < f_addr_cnt; i++)
}

#endif








