/*
 * lip_arp.c
 *  Файл содержит реализацию протокола ARP в соответствии с rfc826
 *  В настоящий момент разрешаются только адреса IPv4
 *  Ответы на запрос ipv4-link-local адресов (rfc3927) посылаются через broadcast
 *  При включении IPV4 ACD на принятые ARP вызывается callback данного модуля
 *
 *  Соответствия адресов сохраняются в таблице, фиксированного размера
 *  Для каждого соотвествия есть время жизни, по истечению которого
 *  запись удаляется.
 *  При использовании адреса для передачи или при приема ARP для нас
 *  с заданным адресом время жизни продлевается
 *
 *  Для определения mac-адреса ip-модуль вызывает lip_arp_resolve, который
 *     либо возвращает адрес сразу (если есть в таблице), либо возвращает
 *     0 и шлет ARP-запрос
 *     (запрос не повторяется, повторы отрабатываются на верхнем уровне)
 *  Верхний уровень может вызвать lip_arp_invalidate для проверки соответствия адреса,
 *     при вызове шлется запрос, и сокращается время жизни, чтобы если ответ не прийдет
 *     запись удалится - может использоваться кнальным уровнем (сейчас не используется)
 *  Определения lip_arp_send_probe и lip_arp_send_announce используются в ACD для проверки и анонса адреса
 *  Created on: 30.07.2010
 *      Author: borisov
 */


#include "lip/lip_defs.h"
#include "lip/link/lip_arp.h"
#include "lip/link/lip_eth.h"
#include "lip/net/ipv4/lip_ip_private.h"
#include "lip/net/ipv4/lip_ipv4_acd.h"
#include "lip/net/ipv4/lip_ip_link_local_private.h"
#include "lip/lip_private.h"
#include "lip/ll/lip_ll.h"
#include "lcspec.h"
#include "ltimer.h"


#include <string.h>

#define ARP_TABLE_REC_MAX_TIME     65535

//опкоды для ARP пакета из заголовка
#define ARP_OP_REQUEST    1
#define ARP_OP_REPLAY     2

#define LIP_ARP_IP4_HDR_SIZE  28

#include "lcspec_pack_start.h"
//запись в таблице ARP
typedef struct  {
    uint8_t ip[LIP_IPV4_ADDR_SIZE];
    uint8_t mac[LIP_MAC_ADDR_SIZE];
    uint16_t age;
} LATTRIBUTE_PACKED t_lip_arp_rec_ipv4;

//общий заголовок ARP
typedef struct {
    uint16_t hrd; //hardware address space
    uint16_t pro; //protocol address space
    uint8_t  hln; //hard address length
    uint8_t  pln; //protocol address length
    uint16_t op;  //opcode
} LATTRIBUTE_PACKED t_lip_arp_hdr;

//заголовок ARP для IPv4
typedef struct {
    t_lip_arp_hdr hdr;
    uint8_t sha[LIP_MAC_ADDR_SIZE]; //source hardware address
    uint8_t spa[LIP_IPV4_ADDR_SIZE]; //source protocol address
    uint8_t tha[LIP_MAC_ADDR_SIZE]; //target hardware address
    uint8_t tpa[LIP_IPV4_ADDR_SIZE]; //target protocol address
} LATTRIBUTE_PACKED t_lip_arp_hdr_ipv4;

#include "lcspec_pack_restore.h"

//таблица с соответствем IPv4 адресов и MAC-адресов
t_lip_arp_rec_ipv4 f_arp_ip_table[LIP_ARP_IPV4_TABLE_LENGTH];


static const uint8_t f_ip0[LIP_IPV4_ADDR_SIZE] = {0,0,0,0};
static t_ltimer f_arp_timer;

/******************************************************************************
 * посылака arp запроса на разрешения адреса ip длиной len, протокола protocol
 ******************************************************************************/
t_lip_errs lip_arp_send_req(t_lip_ip_type type, const uint8_t *local_ip, const uint8_t *ip, uint8_t len) {
    int max_size;
    t_lip_errs err = LIP_ERR_SUCCESS;
    //получаем буфер
    t_lip_arp_hdr_ipv4 *arpip_pkt = (t_lip_arp_hdr_ipv4 *)lip_eth_prepare_def(
                LIP_ETHTYPE_ARP, g_lip_broadcast_mac, &max_size);
    if (arpip_pkt == NULL) {
        err = (t_lip_errs)max_size;
    } else {
        if (type == LIP_IPTYPE_IPv4) {
            if (max_size >= LIP_ARP_IP4_HDR_SIZE) {
                //заполняем поля запроса
                arpip_pkt->hdr.op = HTON16(ARP_OP_REQUEST);
                arpip_pkt->hdr.hrd = HTON16(LIP_HRD_ETHERNET);
                arpip_pkt->hdr.pro = HTON16(LIP_ETHTYPE_IPv4);
                arpip_pkt->hdr.hln = LIP_MAC_ADDR_SIZE;
                arpip_pkt->hdr.pln = len;
                memcpy(arpip_pkt->sha, lip_eth_cur_mac(), LIP_MAC_ADDR_SIZE);
                if (local_ip == NULL) {
                    lip_ipv4_addr_set_invalid(arpip_pkt->spa);
                } else {
                    lip_ipv4_addr_cpy(arpip_pkt->spa, local_ip);
                }
                lip_ipv4_addr_cpy(arpip_pkt->tpa, ip);
                memset(arpip_pkt->tha, 0, LIP_MAC_ADDR_SIZE);
                lip_eth_flush(LIP_ARP_IP4_HDR_SIZE);
            } else {
                err = LIP_ERR_TXBUF_TOO_SMALL;
            }
        } else {
            err = LIP_ERR_ARP_UNSUP_TYPE;
        }
    }
    return err;
}


/******************************************************************
 *  Инициализация переменных для протокола ARP
 ******************************************************************/
void lip_arp_init(void) {
    int i;
    //устанавливаем в таблице 0 время и нулевой ip во всех записях
    for (i = 0; i < LIP_ARP_IPV4_TABLE_LENGTH; ++i) {
        memset(f_arp_ip_table[i].ip, 0, LIP_IPV4_ADDR_SIZE);
        f_arp_ip_table[i].age = 0;
    }
    //запуск таймера для определения старения записей
    ltimer_set(&f_arp_timer, LTIMER_MS_TO_CLOCK_TICKS(LIP_ARP_AGE_INC_TIME));
}


/***************************************************************************
 * Выполнение фоновых задач ARP
 *    Здесь определяются счетчики старения для записей таблицы ARP
 ****************************************************************************/
void lip_arp_pull(void) {
    int i;

    //по таймеру увеличиваем поле age для всех действительных записей таблицы...
    if (ltimer_expired(&f_arp_timer)) {
        ltimer_reset(&f_arp_timer);
        for (i=0; i < LIP_ARP_IPV4_TABLE_LENGTH; ++i) {
            t_lip_arp_rec_ipv4 *arp_rec = &f_arp_ip_table[i];
            if (lip_ipv4_addr_is_valid(arp_rec->ip)) {
                /* todo мб сделать алгоритм с проверкой arp по таймауту
                 * см rfc1122 2.3.2.1 page 23
                 */

                //если время жизни превысило таймаут, выкидываем запись...
                if (arp_rec->age >= LIP_ARP_DISCARD_AGE) {
                    lip_ipv4_addr_cpy(arp_rec->ip, f_ip0);
                    arp_rec->age = 0;
                } else if (arp_rec->age!=ARP_TABLE_REC_MAX_TIME) {
                    arp_rec->age++;
                }
            }
        }
    }

}

/*************************************************************************
 *  Функция используется протоколами верхнего уровня, для проверки
 *  действительности соответствия mac и ip, если у них появлись сомнения
 *  Посылается arp запрос, а возраст записи устанавливается таким, чтобы
 *  через 1-2 периода таймера, если ответ не пришел - выкинуть
 *************************************************************************/
void lip_arp_invalidate(t_lip_ip_type type, const uint8_t *ip_addr) {
    if (type == LIP_IPTYPE_IPv4) {
        uint8_t i;
        uint8_t *res = 0;
        //шлем запрос, чтобы обновить запись
        lip_arp_send_req(LIP_IPTYPE_IPv4, g_lip_st.ip.addr, ip_addr, LIP_IPV4_ADDR_SIZE);

        for (i=0; (i < LIP_ARP_IPV4_TABLE_LENGTH) && (res==0); ++i) {
            if (lip_ipv4_addr_equ(ip_addr, f_arp_ip_table[i].ip)) {
                res = f_arp_ip_table[i].mac;
                //устанавливаем возраст на 2 меньше максимума,
                //чтобы как минимум один период таймера прошел, до
                //того, как выкинем запись - чтобы могли получить ответ
                f_arp_ip_table[i].age = LIP_ARP_DISCARD_AGE - 2;
            }
        }
    }
}




/********************************************************************************
 *  Поиск по таблице записи с заданным ip
 *     в случае успеха возвращает указатель на mac-адрес из записи,
 *     иначе - возвращает 0 и посылает запрос с указанным адресом
 *  Параметры:
 *    type    (in) - тип протокола верхнего уровня:
 *                    IPv4 - ETH_TYPE_IP
 *    ip_addr (in) - требуемый IP
 *  Возвращаемое значение:
 *    указатель на mac-адрес из таблицы ARP или 0 - если не найден
 ********************************************************************************/
uint8_t *lip_arp_resolve(t_lip_ip_type type, const uint8_t *ip_addr) {
    uint8_t *res = NULL;
    const uint8_t *search_ip = ip_addr;

    if (type == LIP_IPTYPE_IPv4) {
        uint8_t i;

        for (i=0; (i < LIP_ARP_IPV4_TABLE_LENGTH) && (res==0); ++i) {
            if (lip_ipv4_addr_equ(search_ip, f_arp_ip_table[i].ip)) {
                res = f_arp_ip_table[i].mac;
                f_arp_ip_table[i].age = 0; //обнуляем возраст для использованной записи...
            }
        }

        if (res == NULL) {
            //если не нашли - посылаем пакет с запросом
            lip_arp_send_req(LIP_IPTYPE_IPv4, g_lip_st.ip.addr, ip_addr, LIP_IPV4_ADDR_SIZE);
        }
    }
    return res;
}



/*********************************************************************
 *  Поиск в таблице arp записи с нужным ip и обновление для нее mac
 *  Параметры:
 *    ip_addr  (in) - ip-адресс
 *    hrd_addr (in) - mac-адрес
 *  Возвращаемое значение:
 *      1 - если запись найдена, иначе - возвращаем 0
 **********************************************************************/
uint8_t lip_arp_upd_ip(const uint8_t *ip_addr, const uint8_t *hrd_addr) {
    uint8_t i, fnd = 0;
    //ищем запись, и если находим с таким же IP - обновляем hrd_addr
    for (i=0; (i < LIP_ARP_IPV4_TABLE_LENGTH) && !fnd; ++i) {
        if (lip_ipv4_addr_equ(ip_addr, f_arp_ip_table[i].ip)) {
            fnd = 1;
            memcpy(f_arp_ip_table[i].mac, hrd_addr, LIP_MAC_ADDR_SIZE);
            f_arp_ip_table[i].age = 0;
        }
    }
    return fnd;
}

/********************************************************************************************************
 *   Добавление новой записи в таблицу соответствия адресов ip и mac
 *      (проверка наличия такой записи не выполняется - это делает lip_arp_upd_ip!)
 *       Данные записываются в первую незанятую запись таблицы или, если все заняты, в самую старую
 *   Параметры:
 *      ip_addr   (in) - IPv4 адрес
 *      hrd_addr  (in) - MAC-адрес
 ********************************************************************************************************/
void lip_arp_add_ip(const uint8_t *ip_addr, const uint8_t *hrd_addr) {
    uint16_t max_age = 0;
    int i, fnd_i = -1, max_age_i = -1;

    //ищем первую незанятую запись или запись с наибольшим возрастом
    for (i=0; (i < LIP_ARP_IPV4_TABLE_LENGTH) && (fnd_i == -1); ++i) {
        if (lip_ipv4_addr_equ(f_arp_ip_table[i].ip, f_ip0)) {
            fnd_i = i;
        } else if (f_arp_ip_table[i].age > max_age) {
            max_age = f_arp_ip_table[i].age;
            max_age_i = i;
        }
    }

    //если не нашли незанятую - затираем самую старую
    if (fnd_i == -1)
        fnd_i = max_age_i;

    //записываем данные в запись в таблице
    memcpy(f_arp_ip_table[fnd_i].mac, hrd_addr, LIP_MAC_ADDR_SIZE);
    memcpy(f_arp_ip_table[fnd_i].ip, ip_addr, LIP_IPV4_ADDR_SIZE);
    f_arp_ip_table[fnd_i].age = 0;

    //вызываем callback ip-уровня, чтобы если есть отложенный пакет,
    //ожидающий адрес - он бы выслался
    lip_ip_arp_resolve_cb(LIP_IPTYPE_IPv4, f_arp_ip_table[fnd_i].mac,
                          f_arp_ip_table[fnd_i].ip, LIP_IPV4_ADDR_SIZE);
}



/**********************************************************************
 * обработка пришедшего ARP пакета
 *    проверяем параметры, обновляем таблицу f_arp_ip_table,
 *    если необходимо - посылаем ответ
 *    Может изменять поля заголовка в pkt!!!!!!
 * Параметры:
 *    pkt (in) - указатель на данные ARP (начиная с заголовка) пришедшего пакета
 *    len (in) - размер данных (начиная с заголовка ARP)
 * Возвращаемое значение:
 *    код ошибки
 ***********************************************************************/
t_lip_errs lip_arp_process_packet(t_lip_payload *pl, const t_lip_eth_rx_packet *eth_packet) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    uint8_t merge = 0;
    t_lip_arp_hdr *pkt = (t_lip_arp_hdr *)pl->data;

    /* проверяем что нужный размер */
    if (pl->size < sizeof(t_lip_arp_hdr)) {
        err = LIP_ERR_RXBUF_TOO_SMALL;
    } else {
        /* проверяем, что нужный тип протокола и ethernet */
        if ((pkt->pro == NTOH16(LIP_ETHTYPE_IPv4))
                && (pkt->hrd == NTOH16(LIP_HRD_ETHERNET))) {

            if (pl->size < (sizeof(t_lip_arp_hdr) + (size_t)(2*(pkt->pln + pkt->hln)))) {
                err = LIP_ERR_RXBUF_TOO_SMALL;
            }
        } else {
            err = LIP_ERR_ARP_UNSUP_TYPE;
        }
    }

    if (err == LIP_ERR_SUCCESS) {
        t_lip_arp_hdr_ipv4 *arpip_pkt = (t_lip_arp_hdr_ipv4*)pkt;
        //если есть в таблице запись - обновляем
        merge = lip_arp_upd_ip(arpip_pkt->spa, arpip_pkt->sha);
#ifdef LIP_USE_IPV4_ACD
        /* вызываем callback для проверки конфликтных адресов
         * если включен address conflict detection
         */
        if (pkt->pro == NTOH16(LIP_ETHTYPE_IPv4)) {
            lip_ipv4_acd_arp_cb(arpip_pkt->spa, arpip_pkt->tpa, arpip_pkt->sha);
        }
#endif

        //адресовано нам?
#ifdef LIP_USE_LINK_LOCAL_ADDR
        int ll_src = lip_ipv4_addr_equ(arpip_pkt->tpa, g_lip_st.ip.ll_addr);
#endif
        if (lip_ipv4_addr_is_valid(arpip_pkt->tpa) &&
             (lip_ipv4_addr_equ(arpip_pkt->tpa, g_lip_st.ip.addr)
#ifdef LIP_USE_LINK_LOCAL_ADDR
               || ll_src
#endif
               || !(eth_packet->flags & (LIP_ETH_FLAGS_BROADCAST | LIP_ETH_FLAGS_MULTICAST))
             )) {

            /* если не было в таблице - добавляем */
            if (!merge)
                lip_arp_add_ip(arpip_pkt->spa, arpip_pkt->sha);

            /* В случае, если это запрос -> требуется ответ.
               Если порт поддерживает аппаратную посылку ответа на ARP, то шаг
               пропускаем для штатного адреса. Для link local все равно шлем, т.к.
               его нужно слать broadcast */
            if ((pkt->op == HTON16(ARP_OP_REQUEST))
#if LIP_LL_PORT_FEATURE_HW_ARP_RESP
                && (!(eth_packet->ll_frame->info.ll_flags & LIP_LL_RXFLAGS_ARP_RESP_SENT)
#ifdef LIP_USE_LINK_LOCAL_ADDR
                         || ll_src
#endif
                        )
#endif
                     ) {
                uint8_t tpa[LIP_IPV4_ADDR_SIZE];
                //если это был запрос -> посылаем ответ
                //меняем op на ARP_OP_REPLAY, spa <-> tpa, sha -> tha, свой mac -> tpa
                pkt->op = HTON16(ARP_OP_REPLAY);
                memcpy(tpa, arpip_pkt->tpa, pkt->pln);
                memcpy(arpip_pkt->tpa, arpip_pkt->spa, pkt->pln);
                memcpy(arpip_pkt->spa, tpa, pkt->pln);
                memcpy(arpip_pkt->tha, arpip_pkt->sha, pkt->hln);
                memcpy(arpip_pkt->sha, lip_eth_cur_mac(), pkt->hln);

#ifdef LIP_USE_LINK_LOCAL_ADDR
                /* для link-local локального адреса нужно ARP слать broadcast,
                 * а не unicast (rfc3927 2.5 - page 14)
                 */
                if (ll_src) {
                    lip_eth_send((uint8_t*)arpip_pkt, sizeof(t_lip_arp_hdr_ipv4),
                                 LIP_ETHTYPE_ARP, g_lip_broadcast_mac);
                } else {
#endif
                    lip_eth_send((uint8_t*)arpip_pkt, sizeof(t_lip_arp_hdr_ipv4),
                                 LIP_ETHTYPE_ARP, arpip_pkt->tha);
#ifdef LIP_USE_LINK_LOCAL_ADDR
                }
#endif
            }
        }
    } /* if (res == LIP_ERR_SUCCESS) */

    return err;
}



