/***************************************************************************//**
  @addtogroup lip_udp
  @{
  @file lip_udp.c
  @brief Файл содержит реализацию модуля @ref lip_udp

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

#include "lip_config.h"

#ifdef LIP_USE_UDP

#include "lip_udp.h"
#include "lcspec.h"
#include "string.h"
#include "lip/lip_log.h"
#include "lip/net/ipv4/lip_ip_private.h"
#include "lip_ll_port_features.h"
#ifdef LIP_USE_IGMP
    #include "lip/net/ipv4/lip_igmp.h"
#endif


#include "lcspec_pack_start.h"

/** @internal заголовок UDP-пакета */
typedef struct st_lip_udp_hdr {
    uint16_t src_port; /**< порт источника */
    uint16_t dest_port; /**< порт назначения */
    uint16_t length; /**< длина данных с заголовком UDP */
    uint16_t checksum; /**< контрольная сумма по данным, заголовку и псевдозаголовку */
    uint8_t data[0]; /**< данные верхнего уровня произвольной длины */
} LATTRIBUTE_PACKED t_lip_udp_hdr;

#define LIP_UDP_HDR_SIZE sizeof(struct st_lip_udp_hdr)

/** @internal Псевдозаголовок, используемый для вычисления контрольной суммы в UDP */
typedef struct st_lip_udp_chk_hdr {
    uint8_t src_addr[LIP_IPV4_ADDR_SIZE]; /**< IP-адрес источника */
    uint8_t dst_addr[LIP_IPV4_ADDR_SIZE]; /**< IP-адрес назначения */
    uint8_t zero; /**< всегда 0 */
    uint8_t protocol; /**< протокол IP - всегда #LIP_PROTOCOL_UDP */
    uint16_t udp_length; /**< размер данных верхнего уровня + заголовка UDP */
} LATTRIBUTE_PACKED t_lip_udp_chk_hdr;

#define LIP_UDP_CHK_HDR_SIZE sizeof(struct st_lip_udp_chk_hdr)

#include "lcspec_pack_restore.h"

/** @internal информация о прослушиваемом порте */
typedef struct {
    uint16_t port; /**< Номер UDP-порта */
    t_lip_udp_in_proc_cb callback; /**< callback, вызываемый при приеме пакета на заданный порт */
} t_udp_std_proc;

/** @internal текущее количество прослушиваемых портов */
static int f_lip_udp_port_cnt;
/** @internal информация о прослушиваемых портах */
static t_udp_std_proc f_udp_proc_cb[LIP_UDP_MAX_DYN_PORTS];
/** @internal указатель на последний подготовленный заголовок */
static t_lip_udp_hdr* f_udp_hdr;
#if !LIP_LL_PORT_FEATURE_HW_TX_UDP_CHECKSUM
/** @internal последний подготовленный псевдозаголовок */
static t_lip_udp_chk_hdr f_tx_chk_hdr;
#endif

/***************************************************************************//**
 * @internal
 * Инициализация модуля @ref lip_udp.
 * Вызывается ядром стека из lip_init().
 ******************************************************************************/
void lip_udp_init(void) {
    f_lip_udp_port_cnt = 0;
    f_udp_hdr = 0;
}

/***************************************************************************//**
  Регистрация callback-функции на прием пакетов по заданному UDP-порту.
  Всего может быть зарегистрировано не более #LIP_UDP_MAX_DYN_PORTS функций.
  Если уже зарегистрировано максимальное количество, функция вернет
  #LIP_ERR_UDP_FREE_PORT_NOT_FOUND.
  На определенный порт может быть назначена только одна функция, если порт уже
  занет, то lip_udp_port_listen() вернет #LIP_ERR_UDP_PORT_ALREADY_LISTENED
  @param[in] port       Номер прослушиваемого UDP-порта
  @param[in] callback   Указатель на функцию, которая будет вызываться при приеме
                        пакета на заданный порт
  @return               Код ошибки
*******************************************************************************/
t_lip_errs lip_udp_port_listen(uint16_t port, t_lip_udp_in_proc_cb callback) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    uint16_t i;

    /* проверяем, что есть свободный слот для прослушивания порта */
    if (f_lip_udp_port_cnt >= LIP_UDP_MAX_DYN_PORTS)
        err = LIP_ERR_UDP_FREE_PORT_NOT_FOUND;

    /* проверяем, что данный порт не прослушивается еще кем-то */
    for (i=0; (i < f_lip_udp_port_cnt) && !err; i++) {
        if (f_udp_proc_cb[i].port == port)
            err = LIP_ERR_UDP_PORT_ALREADY_LISTENED;
    }

    /* добавляем порт */
    if (!err) {
        f_udp_proc_cb[f_lip_udp_port_cnt].port = port;
        f_udp_proc_cb[f_lip_udp_port_cnt].callback = callback;
        f_lip_udp_port_cnt++;
    }
    return err;
}

/***************************************************************************//**
  Завершения прослушивания UDP-порта.
  Если на заданный порт была зарегистрирована функция с помощью lip_udp_port_listen(),
  то данный вызов отменяет регистрацию.
  Если на порт функция зарегистрирована не была, то вызов не имеет эффекта
  @param[in] port    UDP-порт
 ******************************************************************************/
void lip_udp_port_unlisten(uint16_t port) {
    for (int i=0; i < f_lip_udp_port_cnt; i++) {
        if (f_udp_proc_cb[i].port == port) {
            if (i!=(f_lip_udp_port_cnt-1)) {
                memmove(&f_udp_proc_cb[i], &f_udp_proc_cb[i+1], 
                        (f_lip_udp_port_cnt-i-1)*sizeof(f_udp_proc_cb[0]));
            }
            f_lip_udp_port_cnt--;
        }
    }
}



/***************************************************************************//**
    @internal
    Обработка принятого UDP пакета.
    Вызывается стеком, после обработки пакета IP-уровнем
    Проверяет контрольную сумму, проверяет, есть ли зарегистрированная с помощью
    lip_udp_port_listen() функция для заданного порта и, если есть,
    заполняет информацию о UDP-пакете (структура типа #t_lip_udp_info)
    и вызывает функцию
    @param[in] ip_packet  Указатель на информацию о принятом пакете с IP-уровня
    @return               Код ошибки
*******************************************************************************/
t_lip_errs lip_udp_process_packet(const t_lip_ip_rx_packet *ip_packet) {
    t_lip_errs err = LIP_ERR_SUCCESS;

    if (ip_packet->pl.size < sizeof(t_lip_udp_hdr)) {
        err = LIP_ERR_RXBUF_TOO_SMALL;
    } else {
        const t_lip_udp_hdr *hdr = (t_lip_udp_hdr*)ip_packet->pl.data;
        uint16_t udp_packet_size = NTOH16(hdr->length);
        if (ip_packet->pl.size < udp_packet_size) {
            err = LIP_ERR_RXBUF_TOO_SMALL;
        } else {
            /* проверка контрольной суммы, если не проверена уже аппаратно.
             * нулевая контрольная сумма - признак, что ее нет */
            if (!(ip_packet->eth_packet->ll_frame->info.ll_flags & LIP_LL_RXFLAGS_TRANSP_CHECKSUM_CHECKED)
                    && (hdr->checksum != 0))  {
                t_lip_udp_chk_hdr chk_hdr;
                uint16_t chk_sum;
                /* заполняем псевдозаголовок для вычисления контрольной суммы */
                memcpy(chk_hdr.dst_addr, ip_packet->ip_info.da, ip_packet->ip_info.addr_len);
                memcpy(chk_hdr.src_addr, ip_packet->ip_info.sa, ip_packet->ip_info.addr_len);
                chk_hdr.zero = 0;
                chk_hdr.protocol = ip_packet->ip_info.protocol;
                chk_hdr.udp_length = hdr->length;


                /* вычисляем сумму сначала по псевдозаголовку, затем по UDP-пакету */
                chk_sum = lip_chksum(0, (uint8_t*)&chk_hdr, LIP_UDP_CHK_HDR_SIZE);
                chk_sum = lip_chksum(~chk_sum, (const uint8_t*)hdr, udp_packet_size);

                if (chk_sum!=0xFFFF) {
                    err = LIP_ERR_UDP_CHECKSUM;
                    lip_printf(LIP_LOG_MODULE_UDP, LIP_LOG_LVL_WARNING_MEDIUM, res,
                            "lip udp: incomming udp packet checksum error\n");
                }
            } else {
                lip_printf(LIP_LOG_MODULE_UDP, LIP_LOG_LVL_DEBUG_MEDIUM, res,
                        "lip udp: checksum bypassed\n");
            }
        }

        if (err == LIP_ERR_SUCCESS) {
            uint16_t port = HTON16(hdr->dest_port);
            int fnd_listener = 0;

            for (int i=0; i < f_lip_udp_port_cnt; i++) {
                if (f_udp_proc_cb[i].port == port) {
    #ifdef LIP_USE_IGMP
                    /* для multicast-адресов - проверяем, разрешено ли сокету принимать
                     * данные. Регистрируемым сокетом служит адрес callback-функции */
                    if ((ip_packet->ip_info.type == LIP_IPTYPE_IPv4)
                            && (ip_packet->ip_info.flags & LIP_IP_FLAG_MULTICAST)) {
                        err = lip_igmp_check_sock((t_lip_igmp_sock)f_udp_proc_cb[i].callback,
                                ip_packet->ip_info.da, ip_packet->ip_info.sa);
                    }
    #endif
                    if (!err) {
                        /* если нашли зарегистрированный callback - заполняем
                          UDP-информацию и вызываем его */
                        t_lip_udp_info udp_info;
                        udp_info.data_size = udp_packet_size - LIP_UDP_HDR_SIZE;
                        udp_info.dst_port = HTON16(hdr->dest_port);
                        udp_info.src_port = HTON16(hdr->src_port);
                        udp_info.flags = ip_packet->ip_info.flags;
                        udp_info.src_addr = ip_packet->ip_info.sa;
                        udp_info.dst_addr = ip_packet->ip_info.da;
                        udp_info.ip_type = ip_packet->ip_info.type;
                        udp_info.ip_ttl = ip_packet->ip_info.ttl;
                        udp_info.ip_tos = ip_packet->ip_info.tos;
                        f_udp_proc_cb[i].callback(&ip_packet->pl.data[LIP_UDP_HDR_SIZE], udp_info.data_size, &udp_info);
                    }

                    fnd_listener = 1;
                }
            }

            /* если нет callback-функции на заданный порт - то возвращаем
              ошибку LIP_ERR_UDP_UNREACHABLE_PORT, чтобы из lip_pull()
              послать ICMP-сообщение "destination unreachable */
            if (!fnd_listener) {
                err = LIP_ERR_UDP_UNREACHABLE_PORT;
            }
        }
    }
    return err;
}


/***************************************************************************//**
 Подготовка UDP-пакета для передачи в соответствии с параметрами, переданными
 в tx_info.
 @param[in]  tx_info   Параметры UDP и IP уровня для формирования пакета
 @param[out] max_size  Указатель на переменную, в которой будет возвращен
                       максимальный размер данных верхнего уровня
                       в подготовленном пакете (>=0)
                       или код ошибки при неудаче (<0)
 @return               Указатель на буфер, в который можно поместить данные
                       верхнего уровня или NULL при неудаче
 ******************************************************************************/
void* lip_udp_prepare(const t_lip_udp_info *tx_info, int *max_size) {
    uint8_t* ret_buf = NULL;
    /* заполняем структуру с ip-информацией */
    t_lip_ip_info ip_info;
    ip_info.type = tx_info->ip_type;
    ip_info.da = tx_info->dst_addr;
    ip_info.sa = tx_info->src_addr;
    ip_info.protocol = LIP_PROTOCOL_UDP;
    ip_info.flags = tx_info->flags;
    ip_info.tos = tx_info->ip_tos;
    ip_info.ttl = tx_info->ip_ttl;

    /* подготавливаем IP-пакет */
    f_udp_hdr = (t_lip_udp_hdr*)lip_ip_prepare(&ip_info, max_size);
    if (f_udp_hdr!=NULL) {
        /* заполняем UDP-заголовок */
        f_udp_hdr->src_port = HTON16(tx_info->src_port);
        f_udp_hdr->dest_port = HTON16(tx_info->dst_port);
        f_udp_hdr->checksum = 0;

#if !LIP_LL_PORT_FEATURE_HW_TX_UDP_CHECKSUM
        /* заполняем поля псевдозаголовка (которые уже известны) для вычисления
           контрольной суммы, если она не вычисляется аппаратно */
        f_tx_chk_hdr.protocol = LIP_PROTOCOL_UDP;
        memcpy(f_tx_chk_hdr.src_addr, ip_info.sa, ip_info.addr_len);
        memcpy(f_tx_chk_hdr.dst_addr, ip_info.da, ip_info.addr_len);
        f_tx_chk_hdr.zero = 0;
#endif
        
        if (max_size) {
            *max_size -= LIP_UDP_HDR_SIZE;
        }
        ret_buf = &((uint8_t*)f_udp_hdr)[LIP_UDP_HDR_SIZE];
    }

    return ret_buf;
}


/***************************************************************************//**
  Передача последнего подготовленного с помощью lip_udp_prepare() пакета.
  Функция заполняет длину данных и вычисляет контрольную сумму, т.к. при подготовке
  пакета размер и содержимое данных верхнего уровня еще не известно. После чего
  сформированный пакет ставится в очередь на отправку.

  @param[in] size    Размер данных протокола верхнего уровня в передаваемом пакете
  @return            Код ошибки
 ******************************************************************************/
t_lip_errs lip_udp_flush(int size) {
    if (f_udp_hdr!=0) {
        /* заполняем размер пакета */
        f_udp_hdr->length = size + LIP_UDP_HDR_SIZE;
        f_udp_hdr->length = HTON16(f_udp_hdr->length);
#if !LIP_LL_PORT_FEATURE_HW_TX_UDP_CHECKSUM
        f_tx_chk_hdr.udp_length = f_udp_hdr->length;
        /* вычисляем контрольную сумму по данным, заголовку и псевдозаголовку */
        uint16_t check_sum = lip_chksum(0, (uint8_t*)&f_tx_chk_hdr, LIP_UDP_CHK_HDR_SIZE);
        uint16_t chk_sum = lip_chksum(~check_sum, (uint8_t*)f_udp_hdr, size+LIP_UDP_HDR_SIZE);
        f_udp_hdr->checksum = HTON16(chk_sum);
#endif

        f_udp_hdr = 0;        

        /* запрос IP-уровню на передачу пакета */
        return lip_ip_flush(size+LIP_UDP_HDR_SIZE);
    } else {
        return LIP_ERR_BUF_NOT_PREPARED;
    }
}

#endif

/** @}*/
