/*
 * lip_ip.c
 *  Файл реализует протокол IP версии 4 (rfc791 + требования из rfc1122)
 *  Стек поддерживает только один шлюз по-умолчанию
 *  Стек поддерживает передачу multicast и broadcast запросов
 *  Стек поддерживает особое поведение для link-local запросов (rfc3927) -
 *     они всегда передаются на локальную сеть, а не маршрутизатору
 *  Стек поддерживает Route Alert option, использующуюся в IGMP
 *  Стек проверяет IP-адрес назначения, за исключением случая, когда идет получение
 *      адреса по DHCP (так как мы должны все равно обработать unicast-пакет) - в этом
 *      случае ip-уровень устанавливает флаг LIP_IP_FLAG_INVALID_DEST_ADDR, но пакет
 *      не отбрасывает
 *  todo:
 *      Стек не содержит таблицы маршрутизации (rfc1122 page 48 3.3.1.2)
 *      Стек не поддерживает ip-опции (но их наличие не приводит к краху)
 *      Стек не поддерживает сбор ip-дейтаграмм (rfc1122 3.3.2)
 *      Стек не поддерживает алгоритмы поиска мертвых маршрутизаторов
 *            (rfc1122 page 51 3.3.1.4)
 *
 *  Зависит от: lip_eth, lip_arp [lip_dhcpc, lip_igmp]
 *  Created on: 30.08.2010
 *      Author: borisov
 */

#include <string.h>
#include "lcspec.h"
#include "lip_ip_cfg_defs.h"
#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/link/lip_eth.h"
#include "lip/link/lip_arp.h"
#include "ltimer.h"
#include "lip/lip_log.h"
#include "lip_ll_port_features.h"

#ifdef LIP_USE_IGMP
    #include "lip_igmp.h"
#endif
#ifdef LIP_USE_DHCPC
    #include "lip/app/dhcp/lip_dhcpc_private.h"
#endif

#ifdef LIP_USE_IPV4_REASSEMBLY
    #include "lip_ip_reassembly_private.h"
#endif



//опции IP
#define LIP_IP_OPT_TYPE_ROUTE_ALERT   0x94
#define LIP_IP_OPT_TYPE_END           0x00
#define LIP_IP_OPT_TYPE_NOP           0x01
//длины опций
#define LIP_IP_OPT_LEN_ROUTE_ALERT       4

//версия IPv4
#define LIP_IPV4_VERSION                 4




static const uint8_t f_ipv4_invalid_addr[LIP_IPV4_ADDR_SIZE] = {0, 0, 0, 0};

//указатель на последний подготовленный заголовок
static t_lip_ipv4_hdr *f_last_hdr = NULL;
static uint32_t f_hdr_size = 0; //размер последнего заголовка в 32-битных словах



#ifdef LIP_IP_WAIT_ARP_BUF_SIZE
/********************************************************
 * переменные для хранения отложенного пакета, ожидающего
 * разрешения arp-адреса
 ********************************************************/
//адрес, для которого ждем разрешение
static uint8_t f_wt_ip_addr[LIP_MAC_ADDR_SIZE];
static uint16_t f_wt_ip_type;
static uint16_t f_wt_size;
static t_lclock_ticks f_wt_time;
LIP_IP_ARP_WAIT_BUF_MEM(static uint8_t f_wt_buf[LIP_IP_WAIT_ARP_BUF_SIZE]);
#endif

const uint8_t *lip_ipv4_cur_addr(void) {
    return g_lip_st.ip.addr;
}

const uint8_t *lip_ipv4_cur_mask(void) {
    return g_lip_st.ip.mask;
}

const uint8_t *lip_ipv4_cur_gate(void) {
    return g_lip_st.ip.gate;
}





/**************************************************************************************
 *   Вычисление контрольной суммы - используется в IP, ICMP, UDP, TCP
 *   Параметры:
 *      sum  (in) - начальное значение суммы
 *      data (in) - данные, для которых надо посчитать сумму
 *      len  (in) - размер данных в байтах
 *   Возвращаемое значение
 *      Контрольная сумма (порядок байт - хоста, а не сети!) *
 ****************************************************************************************/
uint16_t lip_chksum(uint16_t sum, const uint8_t *data, int len) {
    uint16_t t;
    const uint8_t *dataptr;
    const uint8_t *last_byte;

    dataptr = data;
    last_byte = data + len - 1;

    while(dataptr < last_byte)
    {    /* At least two more bytes */
        t = (dataptr[0] << 8) + dataptr[1];
        sum += t;
        if(sum < t)
            sum++;        /* carry */
        dataptr += 2;
    }

    if(dataptr == last_byte)
    {
        t = (dataptr[0] << 8) + 0;
        sum += t;
        if(sum < t)
        {
            sum++;        /* carry */
        }
    }
  /* Return sum in host byte order. */
  return (sum==0xFFFF) ? sum : ~sum;
}

void lip_ip_init(void) {
    //нулевой адрес используется только пока хост не получил настоящий адрем
    //см. rfc 1122 page 30
    lip_ipv4_addr_set_invalid(g_lip_st.ip.routable_addr);
    lip_ipv4_addr_set_invalid(g_lip_st.ip.previous_addr);
    lip_ipv4_addr_set_invalid(g_lip_st.ip.mask);
    lip_ipv4_addr_set_invalid(g_lip_st.ip.gate);
    g_lip_st.ip.addr = g_lip_st.ip.routable_addr;
#ifdef LIP_IP_WAIT_ARP_BUF_SIZE
    f_wt_size = 0;
#endif

#ifdef LIP_USE_IPV4_REASSEMBLY
    lip_ipv4_reassembly_init();
#endif
}

/************************************************************************************
 *  Устанавливаем текущий IPv4 адрес.
 *  Может вызываться вручную для установки статическо адреса или из dhcpc модуля
 *  При установке действительного адреса, если использовался link-local адрес, то
 *  идет переключение на установленный адрес
 *  Если передан NULL, то устанавливается недействительный адрес
 ***********************************************************************************/
t_lip_errs lip_ipv4_set_addr_ex(const uint8_t *ip_addr, unsigned flags) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    if (ip_addr == NULL) {
        if (lip_ipv4_addr_is_valid(g_lip_st.ip.routable_addr)) {
            lip_ipv4_addr_cpy(g_lip_st.ip.previous_addr, g_lip_st.ip.routable_addr);
            lip_ipv4_addr_set_invalid(g_lip_st.ip.routable_addr);
            if (g_lip_st.ip.routable_addr_flags & LIP_IPV4_ADDR_FLAG_ARP_ANNOUNCE) {
                lip_ipv4_acd_leave(g_lip_st.ip.routable_addr);
            }
            g_lip_st.ip.routable_addr_flags = 0;
#ifdef LIP_USE_LINK_LOCAL_ADDR
            /* если разрешен link-local и получен действительный адрес по этому
               интерфейсу - то переходим к нему */
            if (lip_ip_link_local_is_en() &&
                    lip_ipv4_addr_is_valid(lip_ip_link_local_addr())) {
                g_lip_st.ip.addr = lip_ip_link_local_addr();
                lip_printf(LIP_LOG_MODULE_IPV4, LIP_LOG_LVL_PROGRESS, 0,
                           "lip ipv4: change routable address to link-local (%d.%d.%d.%d)\n",
                           g_lip_st.ip.addr[0], g_lip_st.ip.addr[1], g_lip_st.ip.addr[2], g_lip_st.ip.addr[3]);
                lip_ip_addr_changed(LIP_IPTYPE_IPv4, g_lip_st.ip.previous_addr,
                                    LIP_IP_ADDR_CHANGE_FLAG_PREF, LIP_IPV4_ADDR_SIZE);
            }
#endif
        }
#ifdef LIP_IP_ENABLE_DIRECT_SET_LINK_LOCAL
    } else if (lip_ipv4_addr_is_unicast(ip_addr)) {
#else
    } else if (lip_ipv4_addr_is_routable(ip_addr)) {
#endif
        /* проверяем, изменился ли в действительности адрес */
        if (!lip_ipv4_addr_equ(g_lip_st.ip.routable_addr, ip_addr)) {
            if (g_lip_st.ip.routable_addr_flags & LIP_IPV4_ADDR_FLAG_ARP_ANNOUNCE) {
                lip_ipv4_acd_leave(g_lip_st.ip.routable_addr);
            }
            lip_ipv4_addr_cpy(g_lip_st.ip.routable_addr, ip_addr);
            g_lip_st.ip.routable_addr_flags = flags;
            if (flags & LIP_IPV4_ADDR_FLAG_ARP_ANNOUNCE) {
                lip_ipv4_acd_add(g_lip_st.ip.routable_addr, NULL, 0,
                                   LIP_IPV4_ADC_FLAGS_SKIP_PROBE | LIP_IPV4_ADC_FLAGS_PERSIST);
            }

            /* адрес считается изменившимся, если он отличается от предыдущего (который был до
             * периода реконфигурации) или если текущим адресом является link-local и мы
             * с него переключаемся на routable*/
            if (!lip_ipv4_addr_equ(g_lip_st.ip.routable_addr, g_lip_st.ip.previous_addr)) {
                lip_printf(LIP_LOG_MODULE_IPV4, LIP_LOG_LVL_PROGRESS, 0,
                           "lip ipv4: current ip address changed to %d.%d.%d.%d\n",
                           ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
                g_lip_st.ip.addr = g_lip_st.ip.routable_addr;
                lip_ip_addr_changed(LIP_IPTYPE_IPv4,
                                    g_lip_st.ip.previous_addr,
                                    LIP_IP_ADDR_CHANGE_FLAG_PREF | LIP_IP_ADDR_CHANGE_FLAG_PREV_INV,
                                    LIP_IPV4_ADDR_SIZE);
#ifdef LIP_USE_LINK_LOCAL_ADDR
            /* если работали с link-local адресом, то переходим
              на routable и оповещаем об этом остальные модули */
            } else if (g_lip_st.ip.addr != g_lip_st.ip.routable_addr) {
                g_lip_st.ip.addr = g_lip_st.ip.routable_addr;
                lip_printf(LIP_LOG_MODULE_IPV4, LIP_LOG_LVL_PROGRESS, 0,
                           "lip ipv4: change cur addr from link-local to routable (%d.%d.%d.%d)\n",
                           ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
                lip_ip_addr_changed(LIP_IPTYPE_IPv4, g_lip_st.ip.previous_addr,
                                    LIP_IP_ADDR_CHANGE_FLAG_PREF | LIP_IP_ADDR_CHANGE_FLAG_PREV_INV,
                                    LIP_IPV4_ADDR_SIZE);
#endif
            } else {
                lip_printf(LIP_LOG_MODULE_IPV4, LIP_LOG_LVL_PROGRESS, 0,
                           "lip ipv4: reacquire previous ip address (%d.%d.%d.%d)\n",
                           ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
                lip_ip_addr_changed(LIP_IPTYPE_IPv4,
                                    NULL, LIP_IP_ADDR_CHANGE_FLAG_PREF,
                                    LIP_IPV4_ADDR_SIZE);
            }
        }
    } else {
        res = LIP_ERR_IP_INVALID_ADDR;
        lip_printf(LIP_LOG_MODULE_IPV4, LIP_LOG_LVL_ERROR_HIGH, res,
                "lip ipv4: set invalid routable address: %d.%d.%d.%d\n",
                ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
    }
    return res;
}

//функция для проверки, что стек уже получил правильный адрес
//(напирмер через dhcp или AutoIP)
int lip_ipv4_cur_addr_is_valid(void) {
    return lip_ipv4_addr_is_valid(g_lip_st.ip.addr);
}

/***************************************************************************************
 *  callback, вызываемый arp уровнем при добавлении нового адреса в таблицу
 *  используется для того, чтобы послать ip-пакет, отложенный из-за отсутствия
 *  ethernet-адреса
 ***************************************************************************************/
void lip_ip_arp_resolve_cb(t_lip_ip_type ip_type, const uint8_t *mac, const uint8_t *ip_addr, uint32_t len) {
#ifdef LIP_IP_WAIT_ARP_BUF_SIZE
    if (f_wt_size > 0) {
        if ((f_wt_ip_type == ip_type) && !memcmp(ip_addr, f_wt_ip_addr, len)) {
            int max_size = 0;
            //получаем буфер для данных
            uint8_t *buf = lip_eth_prepare_def(LIP_ETHTYPE_IPv4, mac, &max_size);
            if (buf && (max_size >= f_wt_size)) {
                memcpy(buf, f_wt_buf, f_wt_size);
                lip_eth_flush(f_wt_size);
                f_wt_size = 0;
            }
        }
    }
#endif
}


/**************************************************************************************************
 *  Обработка входного пакета ip. В случае успеха в info возвращает всю необходимую информацию
 *  Параметры
 *      hdr  (in)  - указатель на заголовок ip-пакета
 *      size (in)  - размер входного буфера от начала заголовка ip до конца
 *      info (out)  - возвращаемая информация о разобранном пакете
 *  Возвращаемое значение
 *      код ошибки
 **************************************************************************************************/
t_lip_errs lip_ip_process_packet(const t_lip_payload *pl, const t_lip_eth_rx_packet *eth_packet, t_lip_ip_rx_packet *ip_packet) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    const t_lip_ipv4_hdr *hdr = ip_packet->hdr = (const t_lip_ipv4_hdr *)pl->data;
    if (pl->size < 1) {
        res = LIP_ERR_RXBUF_TOO_SMALL;
    } else {
        ip_packet->eth_packet = eth_packet;
        ip_packet->ip_info.flags = 0;
        ip_packet->rx_flags = 0;

        if (((hdr->ver_ihl & 0xF0) >> 4)!= LIP_IPV4_VERSION) {
            res = LIP_ERR_IP_VERSION;
        } else {
            uint8_t hdr_size = (uint8_t)((hdr->ver_ihl & 0x0F) << 2);
            ip_packet->ip_info.type = LIP_IPTYPE_IPv4;
            ip_packet->hdr_size = hdr_size;

            //проверяем размер заголовка
            if (hdr_size < 20) {
                res = LIP_ERR_IP_HDR_SIZE;
            } else if (pl->size < hdr_size) {
                res = LIP_ERR_RXBUF_TOO_SMALL;
            } else if (HTON16(hdr->offs_flgs)&0x3FFF) {
    #ifdef LIP_USE_IPV4_REASSEMBLY
                ip_packet->rx_flags |= LIP_IP_RXFLAG_FRAGMETED;
    #else
                res = LIP_ERR_IP_FRAG_NOT_SUPPORT;
    #endif
            } else {
                /* проверяем контрольную сумму, если она уже не проверена на уровне порта */
                if (!(eth_packet->ll_frame->info.ll_flags & LIP_LL_RXFLAGS_IP_CHECKSUM_CHECKED)) {
                    if (lip_chksum(0, (const uint8_t*)hdr, hdr_size) != 0xFFFF) {
                        res = LIP_ERR_IP_CHECKSUM;
                    }
                }
            }
        }
    }


    /**********   проверка адреса назначения dest_addr *****************************/
    if (res == LIP_ERR_SUCCESS) {
        ip_packet->ip_info.da = &hdr->dest_addr[0];
        //проверка на broadcast. если разрешен link-local, то проверяем и на link-local broadcast
        if (lip_ipv4_addr_is_bcast(hdr->dest_addr, g_lip_st.ip.mask)
    #ifdef LIP_USE_LINK_LOCAL_ADDR
            || (lip_ip_link_local_is_en() && lip_ipv4_is_link_local_bcast(hdr->dest_addr))
    #endif
            ) {
            //broadcast => передаем наверх без дальнейшей проверки
            ip_packet->ip_info.flags |= LIP_IP_FLAG_BROADCAST;
        } else if (eth_packet->flags & LIP_ETH_FLAGS_BROADCAST) {
            /* если link layer адрес - bcast, а ip-нет -ошибочный пакет => выкидываем */
            res = LIP_ERR_IP_RX_ADDR;
        }

    #ifdef LIP_USE_IGMP
        /***************************************************************
         * Если включена реализация протокола IGMP, то разрешено принимать
         * multicast-пакеты
         */
        else if (lip_ipv4_addr_is_mcast(hdr->dest_addr)) {
            /* проверяем, разрешено ли принимать пакеты по заданному mcast-адресу */
            ip_packet->ip_info.flags |= LIP_IP_FLAG_MULTICAST;
            res = lip_igmp_check_mcast_addr(hdr->dest_addr, hdr->src_addr);
        }
    #endif
        /** @todo мб сделать обновление записи в таблице arp для входящей unicast ip
         * так как раз мы с хостом общаемся, значит запись следует оставить
         */
        /* проверяем на равенство dst_addr
         * если разрешен link-local, то сравниваем как с routable адресом, так и c link-local,
         * так как возможно был получен routable адрес, но остались сокеты с link-local
         */
        else if (lip_ipv4_addr_equ(hdr->dest_addr, lip_ipv4_cur_addr())) {
            ip_packet->ip_info.da = lip_ipv4_cur_addr();
        }
    #ifdef LIP_USE_LINK_LOCAL_ADDR
        else if (lip_ip_link_local_is_en() &&
                lip_ipv4_addr_equ(hdr->dest_addr, lip_ip_link_local_addr())) {
            ip_packet->ip_info.da = lip_ip_link_local_addr();
        }
    #endif
    #ifdef LIP_USE_DHCPC
        else if (lip_dhcpc_in_progress())  {
            /***********************************************************************************
             * если разрешен DHCP клиент, то мы должны иметь возможность
             * принять DHCP_OFFER/DHCP_ACK/DHCP_NACK с IP-адресом, который еще не назначен.
             * при этом уже может быть link-local адрес или статический адрес.
             * Проверяем, что если DHCP сейчас требуется эта возможность, то принимаем
             * пакет без проверки адреса, но ставим флаг
             **********************************************************************************/
            ip_packet->rx_flags |= LIP_IP_RXFLAG_INVALID_DEST_ADDR;
        }
    #endif
        else {
            res = LIP_ERR_IP_RX_ADDR;
        }
    }

    /********************************************************************
     * Проверяем scr адрес в соответствии с rfc1122 page 30-32
     ********************************************************************/
    if (res == LIP_ERR_SUCCESS)  {
        if (lip_ipv4_addr_is_bcast(hdr->src_addr, g_lip_st.ip.mask))   {
            res = LIP_ERR_IP_SRC_ADDR;
        } else if (lip_ipv4_addr_is_mcast(hdr->src_addr)) {
            res = LIP_ERR_IP_SRC_ADDR;
        }
        /*так же не может использоваться нулевой адрес, за исклучением
          момента до получения хостом адреса, но т.к. мы этого не знаем
          и м.б. в будущем будет сделан например dhcp-сервер, то по-видимому
          на нулевой адрес проверку делать все же не стоит...
          */
    }


    if (res == LIP_ERR_SUCCESS)  {
        uint16_t ip_len = HTON16(hdr->length);
        //заполняем поля информации при успешном завершении
        ip_packet->pl.size = ip_len - ip_packet->hdr_size;
        ip_packet->pl.data = pl->data + ip_packet->hdr_size;

        //проверка размера входного буфера
        if (pl->size < ip_len) {
            res = LIP_ERR_TXBUF_TOO_SMALL;
        } else {
            ip_packet->ip_info.addr_len = LIP_IPV4_ADDR_SIZE;
            ip_packet->ip_info.protocol = hdr->protocol;
            ip_packet->ip_info.sa =  hdr->src_addr;
            ip_packet->ip_info.tos = hdr->tos;
            ip_packet->ip_info.ttl = hdr->time_to_live;
#ifdef LIP_USE_IPV4_REASSEMBLY
            if (ip_packet->rx_flags & LIP_IP_RXFLAG_FRAGMETED) {
                res = lip_ipv4_reassembly_process_packet(ip_packet);
            }
#endif
        }
    }

    return res;
}








/***********************************************************************
 *  Получение multicast mac-адреса по IPv4 адресу c проверкой IPv4 адреса на
 *  принадлежность multicast
 ***********************************************************************/
t_lip_errs lip_ipv4_get_mcast_mac(const uint8_t *ip, uint8_t *mac) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    if (lip_ipv4_addr_is_mcast(ip)) {
        mac[5] = ip[3];
        mac[4] = ip[2];
        mac[3] = ip[1] & 0x7F;
        mac[2] = 0x5E;
        mac[1] = 0;
        mac[0] = 0x1;
    } else {
        res = LIP_ERR_IGMP_NONMCAST_ADDR;
    }
    return res;
}

/***************************************************************************//**
 * Подготовка IP-пакета для передачи, заполняет заголовок в соответствии с 
 *   переданными параметрами IP-уровня и возвращает буфер для заполнения 
 *   данных протокола верхнего уровня и макс. размер этих данных.
 *   После заполнения данных верхним уровнем должна быть вызвана функция
 *   lip_ip_flush() или lip_ip_split_flush() для передачи подготовленных данных 
 * @param[in] tx_info    Структура с информацией об ip-уровне. Может быть
 *                       изменены поля sa (если не был указан, заменяется 
 *                       на используемый) и addr_len
 * @param[out] max_size  Значение меньше нуля соответствует коду ошибки. 
 *                       Значение больше или равное нулю - максимальный размер
 *                       данных для протокола верхнего уровня
 * @return               Указатель на буфер для помещения в него данных протокола
 *                       верхнего уровня. При ошибке возвращается нулевой указатель
 *******************************************************************************/
uint8_t *lip_ip_prepare(t_lip_ip_info *tx_info, int *max_size) {
    const uint8_t *mac = 0;
    uint8_t *buf = 0, *res = 0;
    int err = 0;
    const uint8_t *resolv_ip = 0;
    uint8_t is_link_local = 0;
    uint8_t mcast_mac[LIP_MAC_ADDR_SIZE];

    if (tx_info->type == LIP_IPTYPE_IPv4) {
        tx_info->addr_len = LIP_IPV4_ADDR_SIZE;
    } else {
        err = LIP_ERR_IP_UNSUP_TYPE;
    }

    /************** определяем source address *****************/
    if (!err) {
        /* если флаг LIP_IP_FLAG_UNSPEC_LOCAL_IP, то src адресс всегда нулевой */
        if (tx_info->flags & LIP_IP_FLAG_UNSPEC_LOCAL_IP) {
            tx_info->sa = f_ipv4_invalid_addr;
        } else {
            /* если source-адрес не определен, то устанавливаем текущий -
             * иначе используем устанавленный верхнем уровнем в sa */
            if (tx_info->sa == NULL) {
                tx_info->sa = lip_ipv4_cur_addr();
            }
    
            /* если текущий адрес не действительный =>
             * возвращаем ошибку
             * (нулевой адрес может использоваться только при
             *  явно заданном LIP_IP_FLAG_UNSPEC_LOCAL_IP)  */
            if (!lip_ipv4_addr_is_valid(tx_info->sa)) {
                err = LIP_ERR_IP_SRC_ADDR_NOT_VALID;
            }
        }
    }

    if (!err) {
        /********** определяем mac-адрес назначения ******************************/
        if (tx_info->flags & LIP_IP_FLAG_BROADCAST) {
            /* если broadcast адрес - то mac назначения всегда все 0xFF */
            mac = g_lip_broadcast_mac;
        } else if (lip_ipv4_get_mcast_mac(tx_info->da, mcast_mac) == LIP_ERR_SUCCESS) {
            /* если multicast адрес - то mac определяется по ip */
            mac = mcast_mac;
        } else {
            /* если unicast - то определяем mac-адрес назначения через ARP.
               кроме того, если другая сеть - то нужен mac шлюза */
            
            /* всегда та же сеть для link-local (отдельно проверка broadcast) */
            if (lip_ipv4_is_link_local(tx_info->da)) {
                if (lip_ipv4_is_link_local_bcast(tx_info->da)) {
                    mac = g_lip_broadcast_mac;
                } else {
                    resolv_ip = tx_info->da;
                }
                is_link_local = 1;            
#ifdef LIP_USE_LINK_LOCAL_ADDR
            /* если мы сейчас используем link-local адрес, то можем слать пакеты
                только в локальную сеть - но не маршрутизатору (rfc3927 2.6.2 page 15) */
            } else if ((tx_info->sa != NULL) && lip_ipv4_is_link_local(tx_info->sa)) {
                resolv_ip = tx_info->da;
                is_link_local = 1;            
#endif
            } else {
                /** @todo проверить broadcast */
                
                /* проверяем - локальная ли сеть */
                if (lip_ipv4_addr_chk_msk(tx_info->da, lip_ipv4_cur_addr(), 
                                          lip_ipv4_cur_mask())) {
                    /* та же сеть */
                    resolv_ip = tx_info->da;
                } else if (lip_ipv4_addr_is_valid(lip_ipv4_cur_gate())) {
                    //иначе - используем адрес шлюза
                    resolv_ip =  lip_ipv4_cur_gate();
                } else {
                    lip_printf(LIP_LOG_MODULE_IPV4, LIP_LOG_LVL_WARNING_LOW, 0,
                               "lip ipv4: dest %03d.%03d.%03d.%03d is unreachable\n",
                                tx_info->da[0], tx_info->da[1], tx_info->da[2], tx_info->da[3]);
                    err = LIP_ERR_IP_HOST_UNREACHABLE;
                }
            }

            /* если mac не определен - ищем его по ARP-таблице */
            if (!err && (mac == NULL)) {
                mac = lip_arp_resolve(tx_info->type, resolv_ip);
            }
        }

        if (!err) {
            if (mac == NULL) {
#ifdef LIP_IP_WAIT_ARP_BUF_SIZE
                /*  rfc1122, 2.3.2.2, page 24 сказано, что нужно
                 * сохранять последний пакет, для которого нет адреса
                 * в arp-таблице до того, как адрес разрешится.
                 * Если определен LIP_IP_WAIT_ARP_BUF_SIZE то сохраняем пакт
                 * в буфер f_wt_buf, иначе возвращаем ошибку*/
                if (max_size != NULL)
                    *max_size = LIP_IP_WAIT_ARP_BUF_SIZE;
                buf = f_wt_buf;
                memcpy(f_wt_ip_addr, resolv_ip, tx_info->addr_len);
                f_wt_ip_type = tx_info->type;
#else
                err = LIP_ERR_ARP_REC_NOTFOUND;
 #endif
            } else {
                /* получаем буфер для данных */
                buf = lip_eth_prepare_def(LIP_ETHTYPE_IPv4, mac, max_size);
                
            }
        }
    }

    
    if (!err && (buf != NULL)) {
        /* заполняем заголовок IP-пакета */
        t_lip_ipv4_hdr *hdr = (t_lip_ipv4_hdr *)buf;
        uint8_t opt_size = 0;


        hdr->tos = tx_info->tos;
        hdr->id = 0; /* т.к. не используем фрагментацию пакетов, то можем использовать
                        любое значение ID, как описано в RFC6864 */

        hdr->offs_flgs = LIP_IPV4_HDR_FLAG_DF; /* запрещаем фрагментацию пакетов при передаче,
                                                  как рекомендуется, т.к. содержит ряд проблем.
                                                  (рекомендовано узнавать заранее допутимый размер
                                                   пакета) */
        //устанавливаем заданный TTL, если не определен - для link-local - 1, для routable - LIP_IP_TTL
        hdr->time_to_live = (tx_info->ttl) ? tx_info->ttl : (is_link_local) ? 1 :LIP_IP_TTL;
        hdr->protocol = tx_info->protocol;
        hdr->checksum = 0;

        if (tx_info->sa == NULL) {
            lip_ipv4_addr_set_invalid(hdr->src_addr);
        } else {
            lip_ipv4_addr_cpy(hdr->src_addr, tx_info->sa);
        }

        if (tx_info->flags & LIP_IP_FLAG_BROADCAST) {
            memset(hdr->dest_addr, 0xFF, LIP_IPV4_ADDR_SIZE);
            tx_info->da=hdr->dest_addr;
        } else {
            memcpy(hdr->dest_addr, tx_info->da, LIP_IPV4_ADDR_SIZE);
        }

#ifdef LIP_USE_IGMP
        /* если установлен флаг - добавляем Route Alert опцию (используется только с IGMP) */
        if (tx_info->flags & LIP_IP_FLAG_ROUTER_ALERT)  {
            hdr->opt[opt_size++] = LIP_IP_OPT_TYPE_ROUTE_ALERT;
            hdr->opt[opt_size++] = LIP_IP_OPT_LEN_ROUTE_ALERT;
            hdr->opt[opt_size++] = 0;
            hdr->opt[opt_size++] = 0;
        }
#endif

        f_last_hdr = hdr;
        f_hdr_size = LIP_IPV4_MIN_HDR_SIZE + opt_size;

        hdr->ver_ihl = (LIP_IPV4_VERSION << 4) | ((f_hdr_size >> 2) & 0xFF);

        res = &buf[f_hdr_size];
        if (max_size != NULL)
            *max_size -= f_hdr_size;
    }
    
    if (err && (max_size != NULL)) {
        *max_size = err;
    }    
    
    return res;
}



/***************************************************************************//**
 *  Передача подготовленного IP-пакета.
 *  Должна вызываться только после успешного вызова lip_ip_prepare() 
 *      до вызова lip_pull().
 *  Позволяет передать дополнительный внешний буфер с данными в дополнение к
 *      подготовленным данным в буфере, возвращаенном lip_ip_prepare().
 *  @param[in] size  Размер данных для передачи данных протокола верхнего уровня
 *                   в основном буфере.
 *  @param[in] data  Буфер с дополнительными данными или NULL, если
 *                   не используется.
 *  @param[in] data_size Размер данных в буфере data, если он используется.
 *  @return          Код ошибки.
 ******************************************************************************/
t_lip_errs lip_ip_flush_split(int size, const uint8_t *data, int data_size) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    if (f_last_hdr != 0) {
        uint16_t len = size + data_size + f_hdr_size;
        /* вычисляем сумму тут и изменяем размер, так как рельно размер может быть
           не определен при lip_ip_prepare() */
        f_last_hdr->length = HTON16(len);
#if !LIP_LL_PORT_FEATURE_HW_TX_IPV4_CHECKSUM
        f_last_hdr->checksum = lip_chksum(0, (uint8_t*)f_last_hdr, f_hdr_size);
        f_last_hdr->checksum = HTON16(f_last_hdr->checksum);
#endif
#ifdef LIP_IP_WAIT_ARP_BUF_SIZE
        if ((uint8_t*)f_last_hdr == f_wt_buf)  {
            if (len <= LIP_IP_WAIT_ARP_BUF_SIZE) {
                if (data_size != 0)
                    memcpy(&f_wt_buf[len - data_size], data, data_size);
                f_wt_size = len;
                f_wt_time = lclock_get_ticks();
            } else {
                res = LIP_ERR_BUF_NOT_PREPARED;
            }
        } else {
#endif
            res = lip_eth_flush_split(size + f_hdr_size, data, data_size);
#ifdef LIP_IP_WAIT_ARP_BUF_SIZE
        }
#endif            
    } else {
        res = LIP_ERR_BUF_NOT_PREPARED;
    }
    return res;
}


void lip_ip_pull(void) {
#ifdef LIP_IP_WAIT_ARP_BUF_SIZE
    /* если ответный ARP за заданное время - выкидываем ожидающий буфер */
    if ((f_wt_size > 0) && ((lclock_get_ticks() - f_wt_time)
            > LTIMER_MS_TO_CLOCK_TICKS(LIP_IP_WAIT_ARP_TIME))) {
        f_wt_size = 0;
    }
#endif
#ifdef LIP_USE_IPV4_REASSEMBLY
    lip_ipv4_reassembly_pull();
#endif
}




t_lip_errs lip_ipv4_set_mask(const uint8_t *mask)  {
    memcpy(g_lip_st.ip.mask, mask, LIP_IPV4_ADDR_SIZE);
    return LIP_ERR_SUCCESS;
}


t_lip_errs lip_ipv4_set_gate(const uint8_t *gate) {
    memcpy(g_lip_st.ip.gate, gate, LIP_IPV4_ADDR_SIZE);
    return LIP_ERR_SUCCESS;
}


void lip_ip_addr_fill(t_lip_ip_addr *addr, const uint8_t *addr_data, t_lip_ip_type type) {
    addr->ip_type = type;
    addr->addr_len = addr->ip_type==LIP_IPTYPE_IPv4 ? LIP_IPV4_ADDR_SIZE : 0;
    memcpy(addr->addr, addr_data, addr->addr_len);
}

t_lip_ip_addr_type lip_ip_addr_type(const t_lip_ip_addr *addr) {
    t_lip_ip_addr_type type = LIP_IP_ADDR_TYPE_INVALID;
    if (addr!=NULL) {
        if ((addr->ip_type==LIP_IPTYPE_IPv4)  && (addr->addr_len==LIP_IPV4_ADDR_SIZE)
                && (lip_ipv4_addr_is_valid(addr->addr))) {
            if (lip_ipv4_addr_is_mcast(addr->addr)) {
                type = LIP_IP_ADDR_TYPE_MCAST;
            } else if (lip_ipv4_addr_is_loopback(addr->addr)) {
                type = LIP_IP_ADDR_TYPE_LOOPBACK;
            } else {
                /** @todo проверка на broadcast */
                type = LIP_IP_ADDR_TYPE_UNICAST;
            }

            if (lip_ipv4_is_link_local(addr->addr)) {
                type |= LIP_IP_ADDR_TYPE_LINKLOCAL | LIP_IP_ADDR_TYPE_LOCAL;
            } else if (lip_ipv4_cur_addr_is_valid() &&
                       lip_ipv4_addr_chk_msk(addr->addr, lip_ipv4_cur_addr(),
                                             lip_ipv4_cur_mask())) {
                type |= LIP_IP_ADDR_TYPE_LOCAL;
            }
        }
    }
    return type;
}

t_lip_errs lip_ip_addr_check(const t_lip_ip_addr *addr, t_lip_ip_addr_type type) {
    return (type & lip_ip_addr_type(addr)) == 0 ? LIP_ERR_IP_INVALID_ADDR : LIP_ERR_SUCCESS;
}


uint32_t lip_ip_get_max_send_size(const t_lip_ip_info *info) {
    (void)info;
    return  LIP_ETH_MTU_SIZE - sizeof(t_lip_ipv4_hdr);
}
uint32_t lip_ip_get_max_recv_size(const t_lip_ip_info *info) {
    (void)info;
    return  LIP_ETH_MTU_SIZE - sizeof(t_lip_ipv4_hdr);
}
uint32_t lip_ip_get_options_size(const t_lip_ip_info *info) {
    (void)info;
    return  0;
}
