/*
 * lip_icmp.c
 *  Файл содержит реализацию протокола ICMP (RFC792)
 *  В настоящее время реализованы только следующие возможности
 *     ответ на ICMP ECHO пакет
 *     посылка Destination Unreachable пакет
 *
 *  Created on: 31.08.2010
 *      Author: borisov
 */

#include "lip/net/ipv4/lip_icmp.h"
#include "lcspec.h"
#include "lip_config.h"
#include "lip_ll_port_features.h"

#include <string.h>

//стандартные типы ICPM пакетов
#define ICMP_TYPE_ECHO_REPLAY              0
#define ICMP_TYPE_DESTINATION_UNREACHABLE  3
#define ICMP_TYPE_SOURCE_QUENCH            4
#define ICMP_TYPE_REDIRECT                 5
#define ICMP_TYPE_ECHO                     8
#define ICMP_TYPE_TIME_EXCEEDED            11
#define ICMP_TYPE_PARAMETER_PROBLEM        12
#define ICMP_TYPE_TIMESTAMP                13
#define ICMP_TYPE_TIMESTAMP_REPLY          14
#define ICMP_TYPE_INFORMATION_REQUEST      15
#define ICMP_TYPE_INFORMATION_REPLY        16


#include "lcspec_pack_start.h"

//стандартный заголовок ICMP
struct st_lip_icmp_hdr {
    uint8_t type;
    uint8_t code;
    uint16_t checksum;
} LATTRIBUTE_PACKED;
typedef struct st_lip_icmp_hdr t_lip_icmp_hdr;

//заголовок ICMP ECHO пакета
struct st_lip_icmp_echo {
    t_lip_icmp_hdr hdr;
    uint16_t id;
    uint16_t seq_number;
    uint8_t data[0];
} LATTRIBUTE_PACKED;
typedef struct st_lip_icmp_echo t_lip_icmp_echo;

//заголовок ICMP Destination unreachable пакета
struct st_lip_icmp_dest_unreach {
    t_lip_icmp_hdr hdr;
    uint32_t unused;
    uint8_t data[0];
} LATTRIBUTE_PACKED;
typedef struct st_lip_icmp_dest_unreach t_lip_icmp_dest_unreach;

//заголовок ICMP Destination unreachable пакета
struct st_lip_icmp_time_exceed {
    t_lip_icmp_hdr hdr;
    uint32_t unused;
    uint8_t data[0];
} LATTRIBUTE_PACKED;
typedef struct st_lip_icmp_time_exceed t_lip_icmp_time_exceed;

#include "lcspec_pack_restore.h"


/*******************************************************************************************
 *  Обработка пришедшего пакета ICMP. Если необходимо - отправляется ответный пакет данных
 *     Пока отвечаем только на echo - для того, чтобы можно было "пинговать"
 *  Параметры:
 *     data      (in)  - указатель на массив данных, начинающийся с заголовка ICMP
 *     size      (in)  - размер данных (Размер заголовка ICMP и все что за ним)
 *     ip_info_t (in)  - указатель на структуру, которая содержит информацию из заголовка ip
 *  Возвращаемое значение:
 *     код ошибки
 *******************************************************************************************/
t_lip_errs lip_icmp_process_packet(const t_lip_ip_rx_packet *ip_packet) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    if (ip_packet->pl.size < sizeof(t_lip_icmp_hdr)) {
        res = LIP_ERR_RXBUF_TOO_SMALL;
    } else if (!(ip_packet->eth_packet->ll_frame->info.ll_flags & LIP_LL_RXFLAGS_TRANSP_CHECKSUM_CHECKED)) {
        /* проверка контрольной суммы, если не была уже проверена портом */
        if (lip_chksum(0, ip_packet->pl.data, ip_packet->pl.size)!=0xFFFF) {
            res = LIP_ERR_ICMP_CHECKSUM;
        }
    }

    if (res == LIP_ERR_SUCCESS) {
        const t_lip_icmp_hdr *icmp_hdr = (const t_lip_icmp_hdr *)ip_packet->pl.data;
        //смотрим тип ICMP пакета
        switch(icmp_hdr->type) {
            case ICMP_TYPE_ECHO: {
                    //на ECHO отвечаем ECHO_REPLAY - для пинга
                    int max_len;
                    t_lip_ip_info tx_info;
                    memset(&tx_info, 0, sizeof(tx_info));
                    tx_info.type = ip_packet->ip_info.type;
                    tx_info.da = ip_packet->ip_info.sa;
                    tx_info.protocol = LIP_PROTOCOL_ICMP;
                    tx_info.sa = ip_packet->ip_info.da;

                    const t_lip_icmp_echo *rx_d = (const t_lip_icmp_echo*)icmp_hdr;
                    //получаем буфер для передачи с подготовленным ip-заголовком
                    t_lip_icmp_echo *tx_d = (t_lip_icmp_echo*)lip_ip_prepare(&tx_info, &max_len);
                    if ((tx_d!=NULL) && (ip_packet->pl.size <= max_len)) {
                        //заполняем поля пакета для передачи
                        memcpy(tx_d, rx_d, ip_packet->pl.size);
                        tx_d->hdr.type = ICMP_TYPE_ECHO_REPLAY;
                        tx_d->hdr.checksum = 0;
#if !LIP_LL_PORT_FEATURE_HW_TX_ICMP_CHECKSUM
                        tx_d->hdr.checksum = lip_chksum(0, (uint8_t*)tx_d, ip_packet->pl.size);
                        tx_d->hdr.checksum = HTON16(tx_d->hdr.checksum);
#endif
                        //посылаем пакет
                        lip_ip_flush(ip_packet->pl.size);

                    } else {
                        res = (t_lip_errs)max_len;
                    }
                }
                break;
            default:
                res = LIP_ERR_ICMP_UNSUP_TYPE;
        }
    }
    return res;
}


/********************************************************************************************
 * Передача сообщения ICMP Destination Unreachable (RFC792, page 4).
 * dest_addr   - (in) адрес назначения (обычно адрес источноки и дейтаграммы)
 * code        - (in) код в соответствующем поле ICMP - LIP_ICMP_CODE_XXX
 * data        - (in) доп данные - обычно IP-заголовок и первые 8 байт дейтограммы,
 *                    на которую шлется сообщение
 * data_size   - (in) размер данных в data
 ****************************************************************************************/
t_lip_errs lip_icmp_send_dest_unreachable(const uint8_t* dest_addr, const uint8_t* src_addr,
        uint8_t code, const uint8_t* data, uint16_t data_size) {
    int max_size = 0, size;
    t_lip_errs err = LIP_ERR_SUCCESS;
    t_lip_icmp_dest_unreach* pkt;
    //подготавливаем ip-пакет
    t_lip_ip_info tx_info;
    memset(&tx_info, 0, sizeof(tx_info));
    tx_info.type = LIP_IPTYPE_IPv4;
    tx_info.da = dest_addr;
    tx_info.sa = src_addr;
    tx_info.protocol = LIP_PROTOCOL_ICMP;

    pkt = (t_lip_icmp_dest_unreach*)lip_ip_prepare(&tx_info, &max_size);
    if (pkt!=NULL) {
        //проверяем, что достаточно места для отправки сообщения
        size = sizeof(t_lip_icmp_dest_unreach) + data_size;
        if (max_size < size) {
            err = LIP_ERR_ICMP_INSUFFICIENT_SIZE;
        } else {
            //заполняем поля
            pkt->hdr.type = ICMP_TYPE_DESTINATION_UNREACHABLE;
            pkt->hdr.code = code;
            pkt->hdr.checksum = 0;
            pkt->unused = 0;
            memcpy(pkt->data, data, data_size);
#if !LIP_LL_PORT_FEATURE_HW_TX_ICMP_CHECKSUM
            pkt->hdr.checksum = lip_chksum(0, (uint8_t*)pkt, size);
            pkt->hdr.checksum = HTON16(pkt->hdr.checksum);
#endif
            err = lip_ip_flush(size);
        }
    } else {
        err = (t_lip_errs)max_size;
    }
    return err;
}
#ifdef LIP_USE_IPV4_REASSEMBLY
t_lip_errs lip_icmp_send_frag_time_exceed(const uint8_t* dest_addr, const uint8_t* src_addr,
                                   const uint8_t* data, uint16_t data_size) {
    int max_size = 0, size;
    t_lip_errs err = LIP_ERR_SUCCESS;
    t_lip_icmp_time_exceed* pkt;
    //подготавливаем ip-пакет
    t_lip_ip_info tx_info;
    memset(&tx_info, 0, sizeof(tx_info));
    tx_info.type = LIP_IPTYPE_IPv4;
    tx_info.da = dest_addr;
    tx_info.sa = src_addr;
    tx_info.protocol = LIP_PROTOCOL_ICMP;

    pkt = (t_lip_icmp_time_exceed*)lip_ip_prepare(&tx_info, &max_size);
    if (pkt!=NULL) {
        //проверяем, что достаточно места для отправки сообщения
        size = sizeof(t_lip_icmp_time_exceed) + data_size;
        if (max_size < size) {
            err = LIP_ERR_ICMP_INSUFFICIENT_SIZE;
        } else {
            //заполняем поля
            pkt->hdr.type = ICMP_TYPE_DESTINATION_UNREACHABLE;
            pkt->hdr.code = 1;
            pkt->hdr.checksum = 0;
            pkt->unused = 0;
            memcpy(pkt->data, data, data_size);
#if !LIP_LL_PORT_FEATURE_HW_TX_ICMP_CHECKSUM
            pkt->hdr.checksum = lip_chksum(0, (uint8_t*)pkt, size);
            pkt->hdr.checksum = HTON16(pkt->hdr.checksum);
#endif
            //отсылаем
            err = lip_ip_flush(size);
        }
    } else {
        err = (t_lip_errs)max_size;
    }
    return err;
}

#endif
