/*****************************************************************************************
 * ip_link_local.c
 *  Файл содержит функции для работы с link local адресами для IPv4 (rfc3927)
 *  (адреса из диапазона 169.254.x.x)
 *  Процедура получения link-local ip-адреса начинается, если:
 *     -  link-local разрешен lip_ip_link_local_enable(1)
 *     -  не установлен статический ip-адрес
 *     -  не разрешен dhcp или dhcp сервер не найден
 *  link-local адрес может существовать параллельно с обычным (routable) адресом
 *     (статическим или полученным через dhcp), в случае, если после получения
 *     link-local адреса был сконфигурирован routable адрес, хост сохраняет link-local
 *     адрес для текущих соединений (и может принимать новые)
 *  Если link-local адрес изменен из-за конфилкта, то разрываются все tcp-соединения
 *     его использующие
 *  link-local адреса используются только для локальных соединений.
 *     Но могут взаимодействовать с обычными адресам
 *
 *  Для определения конфликтов используется отельный модуль - lip_ipv4_acd
 *
 *  Использует: lip_rand, lip_ipv4_acd, lip_ip, lip_core
 *
 *  Created on: 11.06.2011
 *      Author: Melkor
 ****************************************************************************************/

#include "lip_config.h"
#ifdef LIP_USE_LINK_LOCAL_ADDR
#include "lip/misc/lip_rand_cfg_defs.h"

#if !LIP_RAND_ENABLE
    #error "IPv4 link local requires random generator. define LIP_CFG_RAND_ENABLE in lip_config.h"
#endif

#include "lip/lip_private.h"
#include "lip/net/ipv4/lip_ip_private.h"
#include "lip/net/ipv4/lip_ip_link_local_private.h"
#include "lip/net/ipv4/lip_ipv4_acd.h"
#include "lip/misc/lip_rand.h"
#ifdef LIP_USE_DHCPC
    #include "lip/app/dhcp/lip_dhcpc_private.h"
#endif

#include "ltimer.h"
#include "lcspec.h"

#include <string.h>

/* максимальное кол-во конфликтов адресов,
   после которого идет ограничение на интервал между попытками */
#ifndef LIP_IP_LINK_LOCAL_MAX_CONFLICTS
    #define LIP_IP_LINK_LOCAL_MAX_CONFLICTS   10
#endif
/* интервал между попытками получения адреса, после LIP_IPV4_ACD_MAX_CONFLICTS конфликтов */
#ifndef LIP_IP_LINK_LOCAL_RATE_LIMIT_INTERVAL
    #define LIP_IP_LINK_LOCAL_RATE_LIMIT_INTERVAL   60000
#endif
/* время ожидания для следующей попытками получить IP-адресс после нуспешной */
#ifndef LIP_IP_LINK_LOCAL_PROBE_WAIT
    #define LIP_IP_LINK_LOCAL_PROBE_WAIT   10000
#endif
#define MAX_CONFLICT_CNT 0xFF


static enum {e_STATE_OFF, //возможность получения link-local адреса отключена
             e_STATE_WAIT_START, //ожидание запуска (пока не link_up или идет получение по DHCP)
             e_STATE_PROBE,  //проверка адреса (что не используется другими)
             e_STATE_DONE//адрес выбран, анонсируем его
             } f_state;


/* текущий кандидат в локальные адреса  */
static uint8_t f_addr[LIP_IPV4_ADDR_SIZE] = {0,0,0,0};
static uint32_t f_rand; /* случайное число, используемое для генерации адреса */
static uint8_t f_conflict_cnt; //количество конфликтов при попытке выбора ip-адреса


/***********************************************************
 * получение нового кандидата в локальные адреса
 * используется f_rand для инициализации случайных чисел
 * результат сохраняется в f_addr
 * *********************************************************/
static void f_calc_next_addr(void) {
    uint16_t ip;
    f_rand = lip_rand_next(f_rand);
    //адрес должен быть в интервале от 169.254.1.0 до 169.254.254.255
    ip = f_rand * (65535 - 512) / LIP_RAND_MAX + 256;
    f_addr[0] = LIP_IP_LINK_LOCAL_ADDR1;
    f_addr[1] = LIP_IP_LINK_LOCAL_ADDR2;
    f_addr[2] = (ip >> 8) & 0xFF;
    f_addr[3] = ip & 0xFF;

    lip_printf(LIP_LOG_MODULE_IP_LINK_LOCAL, LIP_LOG_LVL_PROGRESS, 0,
            "lip ip ll: try link-local addr %d.%d.%d.%d\n",
            f_addr[0], f_addr[1], f_addr[2], f_addr[3]);
}

static void f_clear_addr(void) {
    /* делаем адрес недействительным */
    if (lip_ipv4_addr_is_valid(g_lip_st.ip.ll_addr)) {
        t_lip_ipv4_addr prev_addr;
        unsigned flags = LIP_IP_ADDR_CHANGE_FLAG_PREV_INV;
        lip_ipv4_addr_cpy(prev_addr, g_lip_st.ip.ll_addr);

        lip_ipv4_acd_leave(prev_addr);

        lip_ipv4_addr_set_invalid(g_lip_st.ip.ll_addr);

        if (g_lip_st.ip.addr == g_lip_st.ip.ll_addr) {
            flags |= LIP_IP_ADDR_CHANGE_FLAG_PREF;
        }
        lip_ip_addr_changed(LIP_IPTYPE_IPv4, prev_addr, flags, LIP_IPV4_ADDR_SIZE);
    }
}

/**************************************************************************
 * callback-функция, вызываемая ACD-модулем при возникновении конфликта
 * или при утверждении проверяемого адреса
 **************************************************************************/
static void f_acd_cb(const uint8_t *addr, t_lip_ipv4_acd_event event) {
    if ((event == LIP_IPV4_ACD_EVENT_ADDR_ESTABLISHED) && (f_state == e_STATE_PROBE)) {
        t_lip_ipv4_addr prev_addr;
        unsigned flags = 0;

        lip_ipv4_addr_cpy(prev_addr, g_lip_st.ip.ll_addr);
        /* устанавливаем выбранный адрес  */
        lip_ipv4_addr_cpy(g_lip_st.ip.ll_addr, f_addr);

        if (lip_ipv4_addr_is_valid(prev_addr) && !lip_ipv4_addr_equ(prev_addr, f_addr)) {
            flags |= LIP_IP_ADDR_CHANGE_FLAG_PREV_INV;
        }
        if (g_lip_st.ip.addr == g_lip_st.ip.ll_addr) {
            /* если link-local основной адрес, то указываем, что основной сменился */
            flags |= LIP_IP_ADDR_CHANGE_FLAG_PREF;
        }
        /* если текущий routable-адрес не действителен
           то устанавливаем текущим адресом link-local */
        if (!lip_ipv4_cur_addr_is_valid()) {
            g_lip_st.ip.addr = g_lip_st.ip.ll_addr;
            /* если основной не был действителе и поменяли на link-local, то также
             * это означает, что основной адрес сменился */
            flags |= LIP_IP_ADDR_CHANGE_FLAG_PREF;
        }

        /* оповещаем о изменении верхний уровень */
        lip_ip_addr_changed(LIP_IPTYPE_IPv4, prev_addr, flags, LIP_IPV4_ADDR_SIZE);

        f_state = e_STATE_DONE;

        lip_printf(LIP_LOG_MODULE_IP_LINK_LOCAL, LIP_LOG_LVL_PROGRESS, 0,
                    "lip ip ll: set link-local addr %d.%d.%d.%d\n",
                    f_addr[0], f_addr[1], f_addr[2], f_addr[3]);
    } else if (event == LIP_IPV4_ACD_EVENT_ADDR_CONFLICT) {
        if (f_state == e_STATE_PROBE) {
            /* увеличиваем кол-во конфликтов */
            if (f_conflict_cnt != MAX_CONFLICT_CNT) {
                ++f_conflict_cnt;
            }
            /* выбираем новый адрес */
            f_calc_next_addr();
            /* если кол-во конфликтов превысило порог,
             * то ограничиваем частоту попыток выбора адреса */
            if (f_conflict_cnt >= LIP_IP_LINK_LOCAL_MAX_CONFLICTS) {
                lip_ipv4_acd_add(f_addr, f_acd_cb, LIP_IP_LINK_LOCAL_RATE_LIMIT_INTERVAL, 0);
            } else {
                lip_ipv4_acd_add(f_addr, f_acd_cb, lip_rand_range(LIP_IP_LINK_LOCAL_PROBE_WAIT), 0);
            }
            f_state = e_STATE_PROBE;
        } else {
           f_clear_addr();


            /* выбираем новый адрес */
            f_calc_next_addr();
            lip_ipv4_acd_add(f_addr, f_acd_cb, 0, 0);
            f_state = e_STATE_PROBE;
        }
    }
}




void lip_ip_link_local_enable(uint8_t en) {
    if (en && (f_state == e_STATE_OFF)) {
        g_lip_st.ip.ll_en = 1;
        f_state = e_STATE_WAIT_START;
    } else if (!en) {
        f_clear_addr();
        f_state = e_STATE_OFF;
        g_lip_st.ip.ll_en = 0;
    }
}

void lip_ip_link_local_init(void) {
    //изначально конфигурация локального ip отключена
    lip_ipv4_addr_set_invalid(g_lip_st.ip.ll_addr);
    g_lip_st.ip.ll_en = 0;
    f_state = e_STATE_OFF;
}

void lip_ip_link_local_reconnect(void) {
    if (g_lip_st.ip.ll_en) {
        f_clear_addr();
        f_state = e_STATE_WAIT_START;
    }
}

void lip_ip_link_local_start(void) {
    if (f_state == e_STATE_WAIT_START) {
        /* если текущий адрес не действителен - выбираем новый */
        if (!lip_ipv4_is_link_local(f_addr)) {
            const uint8_t *mac = lip_eth_cur_mac();
            /* для инициализации генератора используем число на основе только mac-адреса
               в результате чего изначально будет сделана попытка получить один и тот же адрес */
            f_rand = ((uint32_t)(mac[3] ^ mac[5]) << 24)
                    | ((uint32_t)(mac[2] ^ mac[4]) << 16)
                    | ((uint16_t)mac[1] << 8) | mac[0];
            f_calc_next_addr();
        }

        lip_ipv4_acd_add(f_addr, f_acd_cb, 0, 0);
        f_state = e_STATE_PROBE;

        f_conflict_cnt = 0;
    }
}





void lip_ip_link_local_pull(void) {
    if (g_lip_st.ip.ll_en) {
        if (f_state == e_STATE_WAIT_START) {
            /* как только появился линк, если не устанавлен действительный IP-адресс
               и не разрешен DHCP клиент - запускаем процесс получения link-local адреса
               */
            t_lip_state state = lip_state();
            if (((state == LIP_STATE_GET_IP)
                    || (state == LIP_STATE_CONFIGURED))
                    && !lip_ipv4_cur_addr_is_valid()
#if defined LIP_USE_DHCPC && !defined LIP_LINK_LOCAL_GET_ADDR_BEFORE_DHCP_FAIL
                    && !lip_dhcpc_is_enabled()
#endif
                    ) {

                lip_ip_link_local_start();
            }
        }
    }
}

#endif
