/***************************************************************************//**
  @addtogroup lip_ipv4
  @{
  @file lip_ip_reassembly.c
  @brief Файл содержит реализацию сборки принятых фрагментированных IPv4 пакетов
  по алгоритму кларка из @rfc{815} с учетом рекомендаций из @rfc{1122}

  Логика сборки включается при определении #LIP_USE_IPV4_REASSEMBLY в
    конфигурационном файле.
  Максимальное количестов собираемых дейтаграмм определяется с помощью
  #LIP_IPV4_REASSEMBLY_BUF_CNT, а максимальный размер каждой дейтаграммы
  через #LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE.



  @author Borisov Alexey <borisov@lcard.ru>
  @date   01.11.2010
 ******************************************************************************/


#include "lcspec.h"
#include "ltimer.h"
#include "lip/lip_private.h"
#include "lip/net/ipv4/lip_ip_private.h"
#include "lip_icmp.h"

#ifdef LIP_USE_IPV4_REASSEMBLY

#ifndef LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE
    #define LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE   1500
#endif

#ifndef LIP_IPV4_REASSEMBLY_MIN_TOUT
    #define LIP_IPV4_REASSEMBLY_MIN_TOUT        60000
#endif

#ifndef LIP_IPV4_REASSEMBLY_MAX_TOUT
    #define LIP_IPV4_REASSEMBLY_MAX_TOUT        120000
#endif

extern int lip_process_transport_packet(t_lip_ip_info* ip_info, uint8_t* data, uint16_t data_size);


/* описание дыры в принятых данных */
typedef struct {
    uint16_t first; /* номер байта, соответствующий первому отсутствующему в пакете */
    uint16_t last; /* номер байта, соответствующий последнему отсутствующему в пакете */
    uint16_t next; /* смещение следующего описания дыры (LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE,
                      если дыра последняя) */
} t_ip_hole;



typedef struct {
    uint32_t flags; /* флаги от IP-уровня */
    t_lclock_ticks  start_time; /* время начала сборки */
    uint16_t len; /* полная длина дейтаграммы после сборки */
    uint16_t hole_offset; /* указатель на первое описание дыры в данных */
    uint8_t valid; /* признак, действительно ли это описание */
    uint8_t hdr_size;  //размер заголовка IP
    t_lip_ipv4_hdr *hdr;
    uint8_t hdr_buf[LIP_IPV4_MAX_HDR_SIZE]; /* заголовок первого пакета */
    uint8_t data[LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE]; /* данные с описаниями дыр */
} t_lip_ip_reassembly_buf;


static t_lip_ip_reassembly_buf f_reas_list[LIP_IPV4_REASSEMBLY_BUF_CNT];


void lip_ipv4_reassembly_init(void) {
    unsigned i;
    for (i=0; i < LIP_IPV4_REASSEMBLY_BUF_CNT; i++)
        f_reas_list[i].valid = 0;
}


static t_lip_errs f_process_fragment(t_lip_ip_reassembly_buf* rbuf,
                                     t_lip_ip_rx_packet *ip_packet) {

    t_ip_hole* hole;
    uint16_t first = (HTON16(ip_packet->hdr->offs_flgs) & 0x1FFF) << 3;
    uint16_t last = first + ip_packet->pl.size - 1;
    int more = HTON16(ip_packet->hdr->offs_flgs) & 0x2000;
    uint16_t next = rbuf->hole_offset;
    t_lip_errs err = LIP_ERR_SUCCESS;
    int out = 0;
    uint16_t* prev_next = &rbuf->hole_offset;

    /* проверяем, что размер не превышает максимальный */
    if (last >= LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE) {
        err = LIP_ERR_IP_DATAGRAMME_TOO_LARGE;
    } else if (more && (ip_packet->pl.size & 0x7)) { /* размер каждого сегмента, кроме последнего, должен быть кратен 8 */
        /** @todo ICMP invalid parameter */
        err = LIP_ERR_IP_HDR_PARAMETER;
    }


    if (err == LIP_ERR_SUCCESS) {
        for (hole = (t_ip_hole*)&rbuf->data[next]; !out;
             hole = (t_ip_hole*)&rbuf->data[next]) {
            next = hole->next;
            /* 2-3: если фрагмент и дыра не пересекаются => идем к следующей дыре */
            if ((first <= hole->last) && (last >= hole->first)) {
                /* 4-5: если фрагмент не из начала дыры, то дыру оставляем, просто уменьшаем
                 * размер, иначе удаляем запись о дыре */
                if (first > hole->first) {
                    hole->last = first-1;
                    prev_next = &hole->next;
                } else {
                    *prev_next = next;
                }
                /* 6: если это не последний фрагмент и его размер меньше последней
                 *  дыры => у нас остается дыра в конце => создаем новый дескриптор
                 *  на ее месте */
                if ((last < hole->last) && more) {
                    uint16_t hole_last = hole->last;
                    hole = (t_ip_hole*)&rbuf->data[last+1];
                    hole->first = last+1;
                    hole->last = hole_last;
                    hole->next = next;
                    *prev_next = hole->first;
                }
            }

            /* проверяем, что это не последняя дыра */
            if (next >= LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE)
                out = 1;
        }

        if ((first==0) || (rbuf->hole_offset==0)) {
            /* сохраняем заголовок из первой дейтаграммы (+ сохраняем
               заголовок до того, как нашли первую, чтобы сравнивать
               id, protocol и адреса с последующими) */
            if (ip_packet->hdr_size > LIP_IPV4_MAX_HDR_SIZE) {
                err = LIP_ERR_IP_DATAGRAMME_TOO_LARGE;
            } else {
                /* заголовок сохраняем непосредственно перед данными в конец
                 * буфера hdr_buf и сохраняем указатель на начало в hdr.
                 * это позволяет при необходимости обработать собранную
                 * дейтограмму как единый ip-пакет c данными */
                rbuf->hdr_size = ip_packet->hdr_size;
                rbuf->hdr = (t_lip_ipv4_hdr *)&rbuf->hdr_buf[LIP_IPV4_MAX_HDR_SIZE - rbuf->hdr_size];
                memcpy(rbuf->hdr, ip_packet->hdr, ip_packet->hdr_size);

                rbuf->flags = ip_packet->ip_info.flags;
            }
        }

        if (err == LIP_ERR_SUCCESS) {
            /* размер берем из последней дейтаграммы */
            if (!more) {
                rbuf->len = last + 1;
            }

            /* копируем данные (копирование должно быть всегда после изменения списка
             * дыр, чтобы не затереть дескриптор дыры!) */
            memcpy(&rbuf->data[first], ip_packet->pl.data, ip_packet->pl.size);

            /* проверяем, завершилась ли сборка */
            if (rbuf->hole_offset >= LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE) {
                /* заполняем информацию из сохраненных полей.
                 * Эту информацию можно получить и из сохраненного заголовка,
                 * но для этого его надо заново разбирать + сейчас разбор
                 * должен быть изменен... поэтому пока храним разобранные поля
                 * вместе с буфером */

                /** @todo проверить реализацию, т.к. поменялась */
                ip_packet->hdr = (t_lip_ipv4_hdr*)rbuf->hdr;
                memset(&ip_packet->ip_info, 0, sizeof(ip_packet->ip_info));
                ip_packet->ip_info.flags = rbuf->flags;
                ip_packet->ip_info.sa = ip_packet->hdr->src_addr;
                ip_packet->ip_info.da = ip_packet->hdr->dest_addr;
                ip_packet->ip_info.type = LIP_IPTYPE_IPv4;
                ip_packet->ip_info.addr_len = LIP_IPV4_ADDR_SIZE;
                ip_packet->hdr_size = rbuf->hdr_size;
                ip_packet->ip_info.ttl = ip_packet->hdr->time_to_live;
                ip_packet->ip_info.tos = ip_packet->hdr->tos;
                ip_packet->ip_info.protocol = ip_packet->hdr->protocol;
                ip_packet->pl.data = rbuf->data;
                ip_packet->pl.size = rbuf->len;



                /* удаляем буфер после успешной сборки */
                rbuf->valid = 0;
            } else {
                /* если буфер не закончен, обнуляем данные текущего пакета, чтобы
                 * не обрабатывать дальше */
                ip_packet->pl.data = NULL;
                ip_packet->pl.size = 0;
            }
        }
    }
    return err;
}

static void f_rbuf_timeout(t_lip_ip_reassembly_buf* rbuf) {
    /* если нам пришла первая дейтаграмма, то нужно послать
     * ICMP Time Exceeded */
    if (rbuf->hole_offset != 0) {
        lip_icmp_send_frag_time_exceed(rbuf->hdr->src_addr, rbuf->hdr->dest_addr,
                                       (uint8_t *)rbuf->hdr, rbuf->hdr_size+8);
    }
    rbuf->valid = 0;
}

t_lip_errs lip_ipv4_reassembly_process_packet(t_lip_ip_rx_packet *ip_packet) {
    t_lip_ip_reassembly_buf* rbuf = 0;
    unsigned i;
    t_lip_errs err = LIP_ERR_SUCCESS;

    /* ещем - есть ли уже информация о сборке этого пакета */
    for (i=0; !rbuf && (i < LIP_IPV4_REASSEMBLY_BUF_CNT); i++) {
        t_lip_ipv4_hdr* rbuf_hdr = (t_lip_ipv4_hdr*)f_reas_list[i].hdr;
        if (f_reas_list[i].valid &&
                (rbuf_hdr->id == ip_packet->hdr->id) &&
                (rbuf_hdr->protocol == ip_packet->hdr->protocol) &&
                lip_ipv4_addr_equ(rbuf_hdr->src_addr, ip_packet->hdr->src_addr) &&
                lip_ipv4_addr_equ(rbuf_hdr->dest_addr, ip_packet->hdr->dest_addr)
                ) {
            rbuf = &f_reas_list[i];
        }
    }

    if (rbuf) {
        err = f_process_fragment(rbuf, ip_packet);
    } else {
        /* новый пакет => ищем неиспользуемый буфер */
        for (i=0; !rbuf && (i < LIP_IPV4_REASSEMBLY_BUF_CNT); i++) {
            if (!f_reas_list[i].valid) {
                rbuf = &f_reas_list[i];
            }
        }

        if (rbuf == NULL) {
            /* если все буферы заняты, то смотрим, может есть тот для которого
             * превышен минимальный таймаут. Если такой есть, то выкидываем */
            for (i=0; !rbuf && (i < LIP_IPV4_REASSEMBLY_BUF_CNT); i++) {
                if (f_reas_list[i].valid &&
                        ((lclock_get_ticks()-f_reas_list[i].start_time) >
                         LTIMER_MS_TO_CLOCK_TICKS(LIP_IPV4_REASSEMBLY_MIN_TOUT))) {
                    f_rbuf_timeout(&f_reas_list[i]);
                    rbuf = &f_reas_list[i];
                }
            }
        }


        if (rbuf != NULL) {
            t_ip_hole* hole = (t_ip_hole*)rbuf->data;
            rbuf->start_time = lclock_get_ticks();

            /* изначальначально кладем описание дыры равное всей дейтаграмме */
            hole->first = 0;
            hole->last = LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE;
            hole->next = LIP_IPV4_REASSEMBLY_MAX_DATA_SIZE;
            rbuf->hole_offset = 0;
            rbuf->valid = 1;

            err = f_process_fragment(rbuf, ip_packet);
            /* если дейтаграмма не верна, то выкидываем созданный только что буфер */
            if (err != LIP_ERR_SUCCESS)
                rbuf->valid = 0;
        } else {
            err = LIP_ERR_IP_REASSEMBLY_BUF_BUSY;
        }
    }

    return err;
}


void lip_ipv4_reassembly_pull(void) {
    unsigned i;
    for (i=0; i < LIP_IPV4_REASSEMBLY_BUF_CNT; i++) {
        if (f_reas_list[i].valid &&
                ((lclock_get_ticks()-f_reas_list[i].start_time) >
                 LTIMER_MS_TO_CLOCK_TICKS(LIP_IPV4_REASSEMBLY_MAX_TOUT))) {
            f_rbuf_timeout(&f_reas_list[i]);
        }
    }
}




#endif

/** @}*/
