/*
 * lip_core.c
 *  Центральная часть TCP/IP стека lip
 *  В этом файле происходит инициализация и продвижение остальных модулей стека
 *  Так же здесь находятся обработчики событий, о которых надо
 *     оповестить остальные модули
 *      - смена сетевого адреса
 *      - повторное подключение к новой сети
 *
 *  Created on: 20.09.2010
 *      Author: borisov
 */

#include "lip_config.h"
#include "lip_defs.h"
#include "lip_log.h"

#include "ltimer.h"
#include "lip/lip_private.h"

#include "lip/link/lip_eth.h"
#include "lip/link/lip_eth_private.h"
#include "lip/link/lip_arp.h"
#include "lip/net/ipv4/lip_ip_private.h"
#include "lip/net/ipv4/lip_icmp.h"
#include "lip/net/ipv4/lip_ipv4_acd.h"
#include "lip/phy/lip_phy.h"
#include "lip/ll/lip_ll_cfg_defs.h"
#include "lip/app/http/lip_http_server_cfg_defs.h"

#if LIP_SW_SUPPORT_KSZ8567
#include "lip/phy/ksz8567/lip_sw_ksz8567.h"
#endif
#ifdef LIP_USE_LINK_LOCAL_ADDR
    #include "lip/net/ipv4/lip_ip_link_local_private.h"
#endif
#ifdef LIP_USE_IGMP
    #include "lip/net/ipv4/lip_igmp.h"
#endif

#ifdef LIP_USE_UDP
    #include "lip/transport/udp/lip_udp_private.h"
#endif
#if LIP_TCP_ENABLE
    #include "lip/transport/tcp/lip_tcp_private.h"
#endif

#if LIP_PTP_ENABLE
    #include "lip/app/ptp/lip_ptp_private.h"
#endif

#if LIP_OPCUA_UADP_SUB_ENABLE
    #include "lip/app/opcua/pubsub/uadp/lip_opcua_uadp_private.h"
#endif

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

#if defined LIP_USE_TFTP_CLIENT || defined LIP_USE_TFTP_SERVER
    #include "lip/app/tftp/lip_tftp_private.h"
#endif

#ifdef LIP_USE_MDNS
    #include "lip/app/dns/mdns/lip_mdns_private.h"
#endif

#ifdef LIP_USE_DNSSD
    #include "lip/app/dns/dns_sd/lip_dns_sd_private.h"
#endif

#ifdef LIP_USE_MB_TCP_SERVER
    #include "lip/app/modbus/lip_mb_tcp_server_private.h"
#endif

#if LIP_HTTP_SERVER_ENABLE
    #include "lip/app/http/lip_http_server_private.h"
#endif

#if LIP_OPCUA_SERVER_ENABLE
    #include "lip/app/opcua/server/lip_opcua_server_private.h"
#endif

#ifndef LIP_PULL_TIME
    #define LIP_PULL_TIME 25
#endif



static t_ltimer f_close_tmr;
static t_lip_state f_lip_state = LIP_STATE_OFF;
static t_lip_state f_lip_prev_state = LIP_STATE_OFF;


/* Процедура повторного подключения к сети */
static void  f_link_reconnect(void) {
    /* сбрасываем таблицу ARP, так как link может быть другой */
    lip_arp_init();
#ifdef LIP_USE_IPV4_ACD
    lip_ipv4_acd_reconnect();
#endif
#ifdef LIP_USE_LINK_LOCAL_ADDR
    lip_ip_link_local_reconnect();
#endif
#ifdef LIP_USE_IGMP
    lip_igmp_reconnect();
#endif
#ifdef LIP_USE_DHCPC
    lip_dhcpc_link_reconnect();
#endif
#ifdef LIP_USE_MDNS
    lip_mdns_reconnect();
#endif
#if LIP_PTP_ENABLE
    lip_ptp_port_link_changed(0, true);
#endif
}




/* Функция вызывается IP-модулем, при смене использующегося в данный
 * момент IP-адреса заданного типа
 *     type - тип адреса (IPv4, IPv6)
 *     old_addr_ptr - указатель, какой адрес был до этого
 *                    (реально уже не действительный, только для
 *                    проверки, что если используемый верхним уровнем адрес
 *                    его использовал, то он не действительный)
 *     len          - длина адреса*/
void lip_ip_addr_changed(t_lip_ip_type type, const uint8_t *prev_addr, unsigned flags, int len) {
#if LIP_LL_PORT_FEATURE_IPADDR_TRACKING
    lip_ll_ip_addr_changed();
#endif
#ifdef LIP_USE_IGMP
    if ((type == LIP_IPTYPE_IPv4) && (flags & LIP_IP_ADDR_CHANGE_FLAG_PREF))
        lip_igmp_addr_changed();
#endif    
#if LIP_TCP_ENABLE
    if ((flags & LIP_IP_ADDR_CHANGE_FLAG_PREV_INV) && (prev_addr != NULL))
        lip_tcp_ip_addr_changed(type, prev_addr, len);
#endif
#ifdef LIP_USE_MDNS
    if (flags & LIP_IP_ADDR_CHANGE_FLAG_PREF)
        lip_mdns_ip_addr_changed(type);
#endif
#if LIP_OPCUA_SERVER_ENABLE
    lip_opcua_server_ip_addr_changed(type);
#endif
}


t_lip g_lip_st;


t_lip_errs lip_process_transport_packet(t_lip_ip_rx_packet *ip_packet) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    if ((!(ip_packet->rx_flags & LIP_IP_RXFLAG_INVALID_DEST_ADDR)
             || (ip_packet->ip_info.protocol == LIP_PROTOCOL_UDP)))  {
        /* проходимся по поддерживаемым протоколам */
        switch (ip_packet->ip_info.protocol) {
            case LIP_PROTOCOL_ICMP:
                res = lip_icmp_process_packet(ip_packet);
                break;
#ifdef LIP_USE_UDP
            case LIP_PROTOCOL_UDP:
                res = lip_udp_process_packet(ip_packet);
                if ((res == LIP_ERR_UDP_UNREACHABLE_PORT) &&
                        (ip_packet->ip_info.type == LIP_IPTYPE_IPv4) &&
                        !(ip_packet->ip_info.flags & (LIP_IP_FLAG_MULTICAST | LIP_IP_FLAG_BROADCAST))) {
                    /***********************************************************
                     * если пришла дейтаграмма на UDP-порт, который никто не
                     * слушает, то в соответсвии с rfc1122 4.1.3.1 page 77
                     * необходимо послать в ответ ICMP-сообщение destination
                     * unreachable с кодом port unreachable обрабатываем тут,
                     * так как нужно отвечать всей ip-дейтаграммой, на которую
                     * в UPD нет указателя
                     **********************************************************/
                    lip_icmp_send_dest_unreachable(ip_packet->ip_info.sa,
                                                   ip_packet->ip_info.da,
                                                   LIP_ICMP_CODE_PORT_UNREACHABLE,
                                                   (const uint8_t*)ip_packet->hdr,
                                                   (uint16_t)(ip_packet->hdr_size
                                                              + MIN(ip_packet->pl.size, 8)));
                    lip_printf(LIP_LOG_MODULE_UDP, LIP_LOG_LVL_DEBUG_HIGH, res,
                            "lip udp: received datagram for unreachable port\n");
                }
                break;
#endif
#if LIP_TCP_ENABLE
            case LIP_PROTOCOL_TCP:
                res = lip_tcp_process_packet(ip_packet);
                break;
#endif
#ifdef LIP_USE_IGMP
            case LIP_PROTOCOL_IGMP:
                res = lip_igmp_process_packet(ip_packet);
                break;
#endif
        }
    }
    return res;
}

void lip_process_rx_frame(t_lip_rx_frame *rx_frame) {
    t_lip_eth_rx_packet rx_eth;
    t_lip_errs err = LIP_ERR_SUCCESS;
    err = lip_rx_process(rx_frame);

    if (err == LIP_ERR_SUCCESS) {
        lip_eth_process_packet(rx_frame, &rx_eth);
    }
    if (err == LIP_ERR_SUCCESS) {
        if (rx_eth.type == LIP_ETHTYPE_IPv4) {
            t_lip_ip_rx_packet ip_packet;
            err = lip_ip_process_packet(&rx_eth.pl, &rx_eth, &ip_packet);
            if ((err == LIP_ERR_SUCCESS) && (ip_packet.pl.data != NULL)) {
                err = lip_process_transport_packet(&ip_packet);
            }
#if LIP_PTP_ENABLE
        } else if (rx_eth.type == LIP_ETHTYPE_PTP) {
            lip_ptp_process_packet(&rx_eth.pl, &rx_eth);

#endif
#if LIP_OPCUA_UADP_SUB_ENABLE
        } else if (rx_eth.type == LIP_ETHTYPE_OPC_UADP) {
            lip_opcua_uadp_process_packet(&rx_eth.pl, &rx_eth);
#endif
        } else if (rx_eth.type == LIP_ETHTYPE_ARP) {
            lip_arp_process_packet(&rx_eth.pl, &rx_eth);
#if LIP_ETH_RX_UNSUP_TYPE_CB
        } else {
            lip_eth_user_packet_process(&rx_eth.pl, &rx_eth);
#endif
        }
    }
}

/***************************************************************************//**
    Начальная инициализация стека. До вызова этой функции нельзя вызывать
    никакие функции стека lip, за исключением lip_state(), которая вернет
    значение #LIP_STATE_OFF. Также безопасен вызов lip_pull(), так как при
    этом состоянии в нем ничего не выполняется.

    Данная функция инициализирует как программные модули, так и начинает
    выполнение инициализации MAC-контроллера. При этом аппаратная инициализация
    не завершается (т.к. может требовать время, а данная функция, как и остальные
    функции стека, не блокирует выполнение) --- она выполняется в дальнейшем
    при последующих вызовах lip_pull();

    @return Код ошибки. В случае ошибки дальнейшая работа невозможна (можно
            только попытаться сделать еще раз lip_init())
*******************************************************************************/
t_lip_errs lip_init(void) {
    t_lip_errs err = LIP_ERR_SUCCESS;

    if (f_lip_state != LIP_STATE_OFF)
        lip_close();


    f_lip_state = LIP_STATE_INIT;

    /* если клок не был инициализирован извне, инициализируем его тут */
    if (!lclock_is_initialized())
        lclock_init();

    err = lip_mac_init(0);
    if (err != LIP_ERR_SUCCESS) {
        f_lip_state = LIP_STATE_ERROR;
    } else {
        lip_ip_init();
        lip_arp_init();
#ifdef LIP_USE_IPV4_ACD
        lip_ipv4_acd_init();
#endif
#ifdef LIP_USE_LINK_LOCAL_ADDR
        lip_ip_link_local_init();
#endif
#ifdef LIP_USE_IGMP
        lip_igmp_init();
#endif
#ifdef LIP_USE_UDP
        lip_udp_init();
#endif
#if LIP_PTP_ENABLE
        lip_ptp_init();
#endif
#ifdef LIP_USE_DHCPC
        lip_dhcpc_init();
#endif
#if LIP_TCP_ENABLE
        lip_tcp_init();
#endif
#ifdef LIP_USE_MDNS
        lip_mdns_init();
#endif
#ifdef LIP_USE_DNSSD
        lip_dnssd_init();
#endif
#if defined LIP_USE_TFTP_CLIENT || defined LIP_USE_TFTP_SERVER
        lip_tftp_init();
#endif
#ifdef LIP_USE_MB_TCP_SERVER
        lip_mb_tcp_server_init();
#endif
#if LIP_HTTP_SERVER_ENABLE
        lip_http_server_init();
#endif
#if LIP_OPCUA_SERVER_ENABLE && LIP_OPCUA_SERVER_AUTO_START_ENABLE
        lip_opcua_server_enable();
#endif
    }
    return err;
}

/***************************************************************************//**
   Функция начинает корректное завершение работы стека. При этом идет
   запрос ко всем программным модулям на завершение работы. Это приводит
   закрытию открытых соединений, а также для передачи пакетов, извещающих
   о завершении работы устройства, если требуется.

   На корректное завершение работы отводится переданный в функции таймаут.
   После завершения указанного таймаута аппаратура будет полностью выключена.

   Сама функция возвращает управление немедленно, при этом стек переходит в
   состояние #LIP_STATE_CLOSE_REQUEST. После этого необходимо периодически
   вызывать lip_pull() до тех пор, пока стек не перейдет в состояние
   #LIP_STATE_OFF.

   Кроме lip_pull() в этом состоянии возможно только вызывать функцию lip_state(),
   а также возможно остановить работу стека принудительно, не дожидаясь истечения
   таймаута, с помощью lip_close(). Никакие другие функции вызываться при этом
   не должны.

   @param[in] tout   Время в мс, отводящееся на завершение всех операций по
                     корректному завершению работы стека. Так как обычно нет
                     возможности узнать, что все пакеты переданы в MAC-контроллере,
                     то данное время сейчас всегда ожидается полностью
 ******************************************************************************/
void lip_close_request(unsigned tout) {
    if (f_lip_state != LIP_STATE_OFF) {
#if LIP_OPCUA_SERVER_ENABLE
        lip_opcua_server_close();
#endif
#if defined LIP_USE_TFTP_CLIENT || defined LIP_USE_TFTP_SERVER
        lip_tftp_close();
#endif
#ifdef LIP_USE_DNSSD
        lip_dnssd_close();
#endif
#ifdef LIP_USE_MDNS
        lip_mdns_close();
#endif
#if LIP_TCP_ENABLE
        /* На корректное закрытие сокетов отводим половину времени.
           чтобы успеть послать RST, если не смогли закрыть нормально */
        lip_tcp_close(tout/2);
#endif
#ifdef LIP_USE_DHCPC
        /* Освобождаем адрес DHCP после разрыва всех tcp-соединений */
        lip_dhcpc_close(tout*3/4);
#endif
#if LIP_PTP_ENABLE
        lip_ptp_close();
#endif
        f_lip_prev_state = f_lip_state;
        f_lip_state = LIP_STATE_CLOSE_REQUEST;
        ltimer_set(&f_close_tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));
    }
}



/***************************************************************************//**
   Принудительно немедленное завершение работы стека.
   Эта функция завершает работу всех модулей (если она не была завершена
   при предыдущем вызове lip_close_request()), а также аппаратно отключает
   MAC-контроллер и переводит микросхему физического интерфейса в сброшенное
   состояние, если определен макрос #LIP_PHY_RESET_SET.
   Стек переходит в состояние #LIP_STATE_OFF.

   После вызова этой функции возможен вызов только lip_state(), а также lip_init()
   для повторной инициализации стека.
 ******************************************************************************/
void lip_close(void) {
    if (f_lip_state != LIP_STATE_OFF)  {
        /* если программные модули не закрыты, то закрываем их сейчас
           без ожидания */
        if (f_lip_state != LIP_STATE_CLOSE_REQUEST)
            lip_close_request(0);
        f_lip_state = LIP_STATE_OFF;
        /* отключение MAC-контроллера */
        lip_mac_close();
    }
}


/***************************************************************************//**
   Данная функция возвращает текущее общее состояние стека. Эта функция может
   вызываться всегда, даже когда стек еще не был инициализирован с помощью
   lip_init() (в этом случае возвращается состояние #LIP_STATE_OFF), чтобы
   узнать, требуется ли его инициализация.
   @return состояние подключения из #t_lip_state
 ******************************************************************************/
t_lip_state lip_state(void) {
    return f_lip_state;
}


/***************************************************************************//**
    Данная функция проверяет, возможна ли в принципе передача пакета в данном
    состоянии стека. Передача не возможна в первую очередь, если еще не была
    инициализирован MAC-контроллер. Данная проверка выполняется всегда внутри
    подготовки пакета, однако может выполняться и протоколом верхнего уровня
    при необходимости для того, чтобы не формировать пакет, если заранее известно,
    что его передача невозможна.
   @return Код ошибки. #LIP_ERR_SUCCESS означает, что передача возможна.
 ******************************************************************************/
t_lip_errs lip_check_tx_enabled(void) {
    return ((f_lip_state == LIP_STATE_CONFIGURED) || (f_lip_state == LIP_STATE_GET_IP)
            || ((f_lip_state == LIP_STATE_CLOSE_REQUEST) &&
                ((f_lip_prev_state == LIP_STATE_CONFIGURED) || (f_lip_prev_state == LIP_STATE_GET_IP)))) ?
                LIP_ERR_SUCCESS : LIP_ERR_TX_NOT_INIT;
}


/***************************************************************************//**
    Данная функция служит для проверки, есть ли свободный буфер для формирования
    нового пакета на передачу. В случае отсутствия буфера соответствующая
    функция подготовки нужного пакета вернет ошибку, что также корректно использовать
    как признак отсутствия свободного места, однако данная функция позволяет
    выполнить это быстрее, так как не требует проверки входных параметров
    на каждом уровне формирования пакета.

    @return Если нет свободного буфера на передачу, возвращает 0, иначе --- 1
 ******************************************************************************/
int lip_tx_buf_rdy(void) {
    return lip_mac_tx_rdy();
}







/***************************************************************************//**
   Данная функция выполняет внутри себя все фоновые задачи стека, такие как:
    - обработка принятых пакетов
    - проверка таймаутов
    - отправка запланированных
    - отслеживает наличие подключения к сети
    - и дргих

    Данная функция должна вызываться периодически в основном цикле программы для
    корректной работы стека. Сам период вызова и его равномерность не имеют большого
    значения, однако большой интервал между вызовами может влиять на скорость
    работы стека и корректность выдержки нужных временных интервалов.

    Большинство функций обратного вызова вызывается при выполнении данной функции.

    @return   Код ошибки. Возвращается не #LIP_ERR_SUCCESS только в случае
              критических ошибок, когда стек переходит в состояние #LIP_STATE_ERROR.
              В этом случае есть возможность только закрыть работу lip_close()
              и попытаться начать работу заново с lip_init().
 ******************************************************************************/
t_lip_errs lip_pull(void) {
    static t_lclock_ticks f_pull_time;
    t_lip_errs err = lip_mac_pull();
    if (err != LIP_ERR_SUCCESS) {
        f_lip_state = LIP_STATE_ERROR;
    } else if ((f_lip_state == LIP_STATE_INIT) || (f_lip_state == LIP_STATE_REINIT)) {
        if (lip_mac_in_work_state()) {
            /* успешно завершилась => переходим в состояние получения адреса */
            if (f_lip_state == LIP_STATE_REINIT) {
                f_link_reconnect();
            }         
            f_pull_time = lclock_get_ticks();
            f_lip_state = LIP_STATE_GET_IP;
#ifdef LIP_CONN_ESTABLISH_CB
            lip_cb_conn_establish();
#endif
        }
    } else if (f_lip_state == LIP_STATE_ERROR) {
        err = LIP_ERR_STACK_IN_ERROR_STATE;
    } else if (f_lip_state != LIP_STATE_OFF) {
        /*********************************************************************
         *  основной цикл в состоянии с установленным соединением
         *********************************************************************/
        t_lclock_ticks cur_time = lclock_get_ticks();
        t_lip_state proc_state;

        if (f_lip_state != LIP_STATE_CLOSE_REQUEST) {
            f_lip_state = lip_ipv4_cur_addr_is_valid() ?
                           LIP_STATE_CONFIGURED : LIP_STATE_GET_IP;

            if (!lip_mac_in_work_state()) {
                f_lip_state = LIP_STATE_REINIT;
#ifdef LIP_CONN_LOST_CB
                lip_cb_conn_lost();
#endif

            }
        }

        proc_state = (f_lip_state == LIP_STATE_CLOSE_REQUEST) ? f_lip_prev_state : f_lip_state;


#if LIP_PTP_ENABLE
        /* PTP выполняется независимо от IP-уровня и измерение задержек
             * может начинаться до получения адреса */
        lip_ptp_pull();
#endif
        if ((proc_state ==  LIP_STATE_GET_IP) ||
                (proc_state == LIP_STATE_CONFIGURED)) {

            /***********************************************************
             * продвижение поддерживаемых протоколов
             ***********************************************************/
            if (lip_tx_buf_rdy()) {
#if LIP_HTTP_SERVER_ENABLE
                lip_http_server_pull();
#endif
#if LIP_OPCUA_SERVER_ENABLE
                lip_opcua_server_pull();
#endif
#if LIP_TCP_ENABLE
                lip_tcp_pull();
#endif
                /* протоколы, которые просто выполняют фоновые не слишком требовательные
                 * задачи можно вызывать не чаще LIP_PULL_TIME    */
                if ((cur_time - f_pull_time) > LTIMER_MS_TO_CLOCK_TICKS(LIP_PULL_TIME)) {
                    lip_ip_pull();
#ifdef LIP_USE_IPV4_ACD
                    lip_ipv4_acd_pull();
#endif
                    lip_arp_pull();
#ifdef LIP_USE_LINK_LOCAL_ADDR
                    lip_ip_link_local_pull();
#endif
#ifdef LIP_USE_IGMP
                    lip_igmp_pull();
#endif
#ifdef LIP_USE_DHCPC
                    lip_dhcpc_pull();
#endif
#if defined LIP_USE_TFTP_CLIENT || defined LIP_USE_TFTP_SERVER
                    lip_tftp_pull();
#endif
                    f_pull_time = lclock_get_ticks();
                }
#ifdef LIP_USE_MDNS
                lip_mdns_pull();
#endif
#ifdef LIP_USE_MB_TCP_SERVER
                lip_mb_tcp_server_pull();
#endif
            }

            /* дополнительный вызов опроса MAC непосредственно перед проверкой
             * и обработкой входных пакетов, чтобы обработать изменение
             * состояния дескрипторов и выполнять обработку с обновленным состоянием */
            lip_mac_pull();
        }
    }

    if ((f_lip_state == LIP_STATE_CLOSE_REQUEST) && ltimer_expired(&f_close_tmr))
        lip_close();

    return err;
}

