/*
 * lip_dhcp.c
 *
 *
 *
 *  Файл содержит реализацию клиентской части протокола DHCP в соответствии с rfc2131/rfc2132
 *
 *  Для разрешения dhcp достаточно вызвать lip_dhcpc_enable(1) после lip_init.
 *      Если известен адрес с предыдущего сеанса, то можно сразу после
 *      вызвать lip_dhcpc_set_requested_addr, чтобы сразу запросить его.
 *      Состояние получения адреса можно отслдить с помощью callback-ов на успешное получение
 *      и на неудачу. При неудаче стек перейдет в начальное состояние и заново попытается
 *      найти сервер.
 *
 *  Поддерживаемые возможности:
 *    - экспаненциальные таймауты со случайным разбросом (rfc2131)
 *    - случайный TID (rfc2131)
 *    - проверка времени аренды с двумя таймерами t1 и t2 (rfc2131)
 *    - разбор опций, как в поле опций, так и в перегруженных file и/или sname
 *    - идентификация клиента в соответствии с rfc4361 или старая из rfc2131
 *    - проверка адресов с использованием алгоритма из rfc5227 и посылкой DECLINE при конфликте
 *      увеличение интервала между конфликтом и DISCOVERY до LIP_DHCPC_RATE_LIMIT_INTERVAL,
 *      если кол-во конфликтов превысило LIP_DHCPC_MAX_CONFLICTS
 *    - запрос предыдущего выделенного адреса, если известен, через INIT-REBOOT
 *       (при реконнекте или при установке адреса верхним уровнем вручную через
 *       lip_dhcpc_set_requested_addr)
 *    - поддержка Fast Commit опции (rfc4039) при определении LIP_DHCPC_USE_RAPID_COMMIT
 *    - поддержка реконфигурации в соответствии с rfc3202 при оределении
 *      LIP_DHCPC_USE_RECONFIGURE
 *
 *  При передаче сообщений всегда включается id-клиента.
 *       Если не определено LIP_DHCPC_USE_NODE_SPEC_ID то используется id,
 *       предложенный в rfc2132 9.14 page 30:  hard_id (1 byte) + mac-address.
 *       Иначе используется client-id из rfc4361 (который объявляет id из rfc2132 deprecated)
 *       в виде IAID+DUID и совместим с DHCPv6 ID. Так как стек не поддерживает multihouming,
 *       то IAID всегда берется 0, DUID используется 3-го типа (hrd_type+id - предназначен
 *       для систем в которых не может смениться mac-адрес и не имеют non-volatile памяти),
 *       если не определен LIP_DHCPC_MANUAL_DUID (в этом случае DUID получается вызовом callbacka
 *       lip_dhcpc_get_duid_cb()
 *
 *  Повторная передача сообщений осуществляется с использованием стратегии таймаутов
 *      из rfc2132 4.1 page 24: таймаут рассчитывается как случайное число в диапазоне:
 *      (LIP_DHCPC_RETRANS_TOUT * 2^retr_cnt +/- LIP_DHCPC_RETRANS_TOUT_VARIATION),
 *      но не более LIP_DHCPC_RETRANS_MAX_TOUT
 *
 *  todo:
 *    - поддержка аутентификаци - rfc3118
 *    - поле secs нужно ли устанавливать не в 0?
 *    - (?) объединение содержимого повторных опций (rfc3396) - но пока
 *        таких опций все равно не поддерживается
 *
 *  Зависит от: lip_udp, lip_rand, lip_ipv4_acd
 *  Created on: 02.09.2010
 *      Author: borisov
 */

#include "lip_config.h"

#ifdef LIP_USE_DHCPC

#include "ltimer.h"
#include "string.h"
#include "lip/lip_private.h"
#include "lip/misc/lip_rand_cfg_defs.h"

#include "lip/app/dhcp/lip_dhcpc_private.h"
#include "lip/app/dhcp/lip_dhcp_const.h"
#include "lip/transport/udp/lip_udp.h"
#include "lip/net/ipv4/lip_ip_private.h"
#include "lip/net/ipv4/lip_ipv4_acd.h"
#ifdef LIP_USE_LINK_LOCAL_ADDR
    #include "lip/net/ipv4/lip_ip_link_local_private.h"
#endif
#include "lip/misc/lip_rand.h"
#include "lip/link/lip_arp.h"
#include "lip/lip_log.h"


#if !LIP_RAND_ENABLE
    #error "dhcpc requires random generator. define LIP_CFG_RAND_ENABLE in lip_config.h"
#endif

#ifndef LIP_USE_UDP
    #error "dhcpc requires udp. define LIP_USE_UDP in lip_conf.h"
#endif



/* фиксированное поле в начале опций */
static const uint8_t f_magic_cookie[4] = {99, 130, 83, 99};


/******************************************************************************
 *  временные константы для определения работы протокола. могут быть
 *  переопределены в lip_conf.h
 ******************************************************************************/
/* минимальная задержка перед посылкой первого сообщения DHCP_DISCOVERY */
#ifndef LIP_DHCPC_INITIAL_DELAY_MIN
    #define LIP_DHCPC_INITIAL_DELAY_MIN         1000
#endif
/* максимальня задержка перед посылкой первого сообщения DHCP_DISCOVERY */
#ifndef LIP_DHCPC_INITIAL_DELAY_MAX
    #define LIP_DHCPC_INITIAL_DELAY_MAX         10000
#endif
/* начальный таймаут на повторную передачу сообщений */
#ifndef LIP_DHCPC_RETRANS_TOUT
    #define LIP_DHCPC_RETRANS_TOUT             4000
#endif
/* разброс значений таймаута на повторную передачу */
#ifndef LIP_DHCPC_RETRANS_TOUT_VARIATION
    #define LIP_DHCPC_RETRANS_TOUT_VARIATION   1000
#endif
/* маскимальное значение для таймаута на повторную передачу*/
#ifndef LIP_DHCPC_RETRANS_MAX_TOUT
    #define LIP_DHCPC_RETRANS_MAX_TOUT         64000
#endif

#ifndef LIP_DHCPC_RESTART_TOUT
    #define LIP_DHCPC_RESTART_TOUT             10000
#endif

/* количество повторов посылки DISCOVERY, после которого
 * будет сообщено, что получение адресе не удалось */
 #ifndef LIP_DHCPC_DISOVERY_RETRANS_FAIL
    #define LIP_DHCPC_DISOVERY_RETRANS_FAIL    4
#endif

/* количество повторов REQUEST, после которого считаем, что
 * сервер не ответил и начинаем конфигурацию заново */
#ifndef LIP_DHCPC_REQUEST_RETRANS_CNT
    #define LIP_DHCPC_REQUEST_RETRANS_CNT       4
#endif

/* сообщение, передаваемое с DHCP_DECLINE */
#ifndef LIP_DHCPC_DECLINE_MSG
    #define LIP_DHCPC_DECLINE_MSG "allocated address is already in use"
#endif

/* минимальный таймаут между попытками запросить
 * продление аренды адреса */
#ifndef LIP_DHCPC_LEASE_REACQ_TOUT_MIN
    #define LIP_DHCPC_LEASE_REACQ_TOUT_MIN  60000
#endif

/* максимальное неверных адресов, полученный от сервера,
   после которого идет ограничение на интервал между попытками */
#ifndef LIP_DHCPC_MAX_CONFLICTS
    #define LIP_DHCPC_MAX_CONFLICTS   10
#endif
/* интервал между попытками получения адреса, после LIP_DHCPC_MAX_CONFLICTS конфликтов */
#ifndef LIP_DHCPC_RATE_LIMIT_INTERVAL
    #define LIP_DHCPC_RATE_LIMIT_INTERVAL   64000
#endif


/* максимальный размер принимаемого DHCP-сообщения
 * передается в DISCOVER и REQUEST сообщениях  */
#ifndef LIP_DHCPC_MSG_SIZE_MAX
    #define LIP_DHCPC_MSG_SIZE_MAX 1514
#endif

/* максимальное значения поля retr_cnt.
 * используется для избежания переполнения */
#define LIP_DHCPC_RETRANS_CNT_MAX  255


/******************** Проверка параметров ***************************************/
#if LIP_DHCPC_RETRANS_TOUT <= LIP_DHCPC_RETRANS_TOUT_VARIATION
    #error "LIP_DHCPC_RETRANS_TOUT must be greater than LIP_DHCPC_RETRANS_TOUT_VARIATION"
#endif

#if (LIP_DHCPC_MSG_SIZE_MAX < 576) || (LIP_DHCPC_MSG_SIZE_MAX > 65535)
    #error "LIP_DHCPC_MSG_SIZE_MAX must be equal to or greater than 576 and less than 65536"
#endif

#if (LIP_DHCPC_DISOVERY_RETRANS_FAIL <= 0) || (LIP_DHCPC_DISOVERY_RETRANS_FAIL > LIP_DHCPC_RETRANS_CNT_MAX)
    #error "LIP_DHCPC_DISOVERY_RETRANS_FAIL must be in range (0, LIP_DHCPC_RETRANS_CNT_MAX]"
#endif

#if (LIP_DHCPC_REQUEST_RETRANS_CNT <= 0) || (LIP_DHCPC_REQUEST_RETRANS_CNT > LIP_DHCPC_RETRANS_CNT_MAX)
    #error "LIP_DHCPC_REQUEST_RETRANS_CNT must be in range (0, LIP_DHCPC_RETRANS_CNT_MAX]"
#endif





#define LIP_DHCPC_ADDR_IS_ACQUIRED() ((f_state.state == DHCP_STATE_BOUND) \
      || (f_state.state == DHCP_STATE_RENEWING) \
      || (f_state.state == DHCP_STATE_REBINDING))

#define LIP_DHCP_HDR_FILE_SIZE  128
#define LIP_DHCP_HDR_SNAME_SIZE  64

#include "lcspec_pack_start.h"
struct st_lip_dhcp_hdr {
    uint8_t op;
    uint8_t htype; //Hardware address type (1 - 10mb eth)
    uint8_t hlen;  //длина mac-адреса
    uint8_t hops; //устанавливается в 0 (увеличивается маршрутизатором)
    uint32_t xid; //id транзакции
    uint16_t secs; //кол-во секунд начиная с начала запроса адреса
    uint16_t flags;
    uint8_t ciaddr[LIP_IPV4_ADDR_SIZE];  //ip клиента
    uint8_t yiaddr[LIP_IPV4_ADDR_SIZE];
    uint8_t siaddr[LIP_IPV4_ADDR_SIZE]; //ip сервера
    uint8_t giaddr[LIP_IPV4_ADDR_SIZE]; //ip маршрутизатора
    uint8_t chaddr[16];           //mac-адрес клиента
    uint8_t sname[LIP_DHCP_HDR_SNAME_SIZE];            //server host name
    uint8_t file[LIP_DHCP_HDR_FILE_SIZE];           //boot file name
    uint8_t options[0];          //options
} LATTRIBUTE_PACKED;
typedef struct st_lip_dhcp_hdr t_lip_dhcp_hdr;


#include "lcspec_pack_restore.h"
/* состояния DCHP-клиента (см. rfc2131 Figure 5 page 35 - имеют
 * в названии состояния слово DHCP) + промежуточные свои */
typedef enum {
    DHCP_STATE_LINK_WAIT,       /* ожидание, пока будет установлен link */
    DHCP_STATE_INIT,       /* начальное состояние после включения. ожидаем таймаут перед DISCOVERY */
    DHCP_STATE_SELECTING,  /* послали DISCOVERY. ждем OFFER */
    DHCP_STATE_REQUESTING, /* приняли OFFER, послали REQUEST, ждем ACK/NACK */
    DHCP_STATE_ADDR_PROBE,      /* приняли ACK, проверяем действительность адреса */
    DHCP_STATE_INIT_REBOOT,
    DHCP_STATE_REBOOTING,
    DHCP_STATE_BOUND,
    DHCP_STATE_RENEWING,
    DHCP_STATE_REBINDING
} t_lip_dhcp_fsm_state;

typedef struct {
    t_lip_dhcp_fsm_state state; /* текущие состояние автомата */
    uint32_t xid;           /* id текущего обмена с сервером */
    t_ltimer tmr;            /* таймер, для таймаутов на передачу */
    t_lip_udp_info tx_info; /* информация UDP-уровня для посылки сообщений */
    t_lip_dhcp_info info; /* информация, полученная от DHCP-сервера */
    t_lclock_ticks start_time; /* время начала запуска процесса получения адреса */
    uint32_t elapsed_time; /* время, сколько использовался адрес */
    uint8_t retr_cnt; /* текущее кол-во повторных передач */
    uint8_t en;
    uint8_t conflict_cnt;
    uint8_t close_req;
} t_lip_dhcp_state;
static t_lip_dhcp_state f_state;

/* позиция от начала буфера, с которой начинаются опции (заголовок + cookie) */
#define LIP_DHCP_START_OPT_POS (sizeof(t_lip_dhcp_hdr) + sizeof (f_magic_cookie))


#ifdef LIP_USE_LINK_LOCAL_ADDR
    /* если разрешены link-local адреса, то при неудаче получения адреса
       по dhcp - запускаем процедуру получения link-local адреса */
    #define LINK_LOCAL_START() lip_ip_link_local_start();
#else
    #define LINK_LOCAL_START()
#endif



static void f_reconnect(void);

/**********************************************************************
 * генерация XID - случайного 32-х битного числа для идентификации
 * текущего сеанса обмена с сервером
 **********************************************************************/
static LINLINE uint32_t f_lip_dhcp_get_xid(void) {
    uint32_t xid = lip_rand();
    xid <<= 16;
    xid += lip_rand();
    return xid;
}

/*************************************************************************************
 * настраиваем таймер  f_state.tmr на таймаут для повторной передачи сообщения
 * таймаут выбирается равным LIP_DHCPC_RETRANS_TOUT * 2^f_state.retr_cnt, но
 * не больше чем LIP_DHCPC_RETRANS_MAX_TOUT
 * Также дополняется случайная добавка  +/- LIP_DHCPC_RETRANS_TOUT_VARIATION
 *************************************************************************************/
static LINLINE void f_set_tout(void) {
    ltimer_set(&f_state.tmr, LTIMER_MS_TO_CLOCK_TICKS(
            ((LIP_DHCPC_RETRANS_TOUT << f_state.retr_cnt) < LIP_DHCPC_RETRANS_MAX_TOUT ?
                    (LIP_DHCPC_RETRANS_TOUT << f_state.retr_cnt) : LIP_DHCPC_RETRANS_MAX_TOUT)
            - LIP_DHCPC_RETRANS_TOUT_VARIATION
            + lip_rand_range(2*LIP_DHCPC_RETRANS_TOUT_VARIATION)));
}


/*******************************************************************
 * сообщение о неудаче получить конфигурацию DHCP верхниму уровню
 * и другим модулям
 *******************************************************************/
static LINLINE void f_lip_dhcp_fail(void) {
    //LINK_LOCAL_START();
#ifdef LIP_DHCPC_FAILED_CB
    lip_dhcpc_failed_cb();
#endif
}

/********************************************************************
 * переход в начальное состояние и сообщение о DHCP ошибке
 *******************************************************************/
static void f_lip_dhcp_fail_reiinit(void) {
    lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_WARNING_HIGH, 0,
             "lip dhcpc: reinitialize dhcp configuration process\n");

    f_reconnect();
    if (f_state.conflict_cnt < LIP_DHCPC_MAX_CONFLICTS)
        f_state.conflict_cnt++;
    f_state.info.flags  = 0;
    lip_ipv4_addr_set_invalid(f_state.info.ip_addr);
    f_lip_dhcp_fail();
}






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

static uint8_t *f_lip_dhcp_addopt_msg_type(const uint8_t *end_buf,
                                           uint8_t *optptr, uint8_t type) {
    if (optptr != NULL) {
        if ((end_buf - optptr) >= (LIP_DHCP_OPTLEN_MSG_TYPE + 2)) {
            *optptr++ = LIP_DHCP_OPT_MSG_TYPE;
            *optptr++ = LIP_DHCP_OPTLEN_MSG_TYPE;
            *optptr++ = type;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}

/***************************************************************************
 * Добавление опции, идентифицирующей клиента. используется во всех сообщениях
 * о том как формируется - см. описание в начале файла
 * **************************************************************************/
static uint8_t* f_lip_dhcp_addopt_client_id(const uint8_t *end_buf, uint8_t *optptr) {
    if ((optptr != NULL) && ((end_buf - optptr) >= 2)) {
        int id_len = 0;
        *optptr++ = LIP_DHCP_OPT_CLIENT_ID;
        /* определяем длину client-id */
#if !defined LIP_DHCPC_USE_NODE_SPEC_ID
        id_len = LIP_MAC_ADDR_SIZE;
#elif !defined LIP_DHCPC_MANUAL_DUID
        id_len = LIP_DHCP_IAID_SIZE + sizeof(t_lip_dhcp_duid_ll_eth);
#else
        /* получаем сразу DUID и его размер через callback,
         * DUID располагается после размера опции, типа адреса и IAID
         */
        id_len = lip_dhcpc_get_duid_cb(optptr + 2 + LIP_DHCP_IAID_SIZE,
            (end_buf - (optptr + 2 + LIP_DHCP_IAID_SIZE)));
        id_len += LIP_DHCP_IAID_SIZE;
#endif
        if ((end_buf - optptr) >= (id_len + 2)) {
            *optptr++ = id_len + 1;
#ifndef LIP_DHCPC_USE_NODE_SPEC_ID
            /* id из rfc2132 - байт типа, затем mac-адрес */
            *optptr++ = LIP_HRD_ETHERNET;
            memcpy(optptr, g_lip_st.eth.addr, LIP_MAC_ADDR_SIZE);
            optptr += LIP_MAC_ADDR_SIZE;
#else
            /* в качестве типа при id из rfc4361 - 255 */
            *optptr++ = 255;
            /* в качестве IAID используем 0, так как работаем
             * только с одним интерфейсом */
            memset(optptr, 0, LIP_DHCP_IAID_SIZE);
#ifndef LIP_DHCPC_MANUAL_DUID
            /* формируем DUID из link-layer адреса, если не сформировали пользователм */
            t_lip_dhcp_duid_ll_eth *duid = (t_lip_dhcp_duid_ll_eth *)(&optptr[LIP_DHCP_IAID_SIZE]);
            duid->type = HTON16(LIP_DHCP_DUID_TYPE_LL);
            duid->hrd_type = HTON16(LIP_HRD_ETHERNET);
            memcpy(duid->hrd_addr, lip_eth_cur_mac(), LIP_MAC_ADDR_SIZE);
#endif
            optptr += id_len;
#endif
        } else {
            optptr = NULL;
        }
    } else {
        optptr = NULL;
    }

    return optptr;
}

static uint8_t *f_lip_dhcp_addopt_server_id(const uint8_t *end_buf, uint8_t *optptr,
                                            const uint8_t *server_id) {
    if (optptr != NULL) {
        if ((end_buf - optptr) >= (LIP_DHCP_OPTLEN_SERVER_ID + 2)) {
            *optptr++ = LIP_DHCP_OPT_SERVER_ID;
            *optptr++ = LIP_DHCP_OPTLEN_SERVER_ID;
            memcpy(optptr, server_id, LIP_DHCP_OPTLEN_SERVER_ID);
            optptr = optptr + LIP_DHCP_OPTLEN_SERVER_ID;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}

static uint8_t *f_lip_dhcp_addopt_ipaddr(const uint8_t *end_buf, uint8_t *optptr,
                                         const uint8_t *ip_addr) {
    if (optptr != NULL) {
        if ((end_buf - optptr) >= (LIP_DHCP_OPTLEN_ADDRESS_REQUEST + 2)) {
            *optptr++ = LIP_DHCP_OPT_ADDRESS_REQUEST;
            *optptr++ = LIP_DHCP_OPTLEN_ADDRESS_REQUEST;
            memcpy(optptr, ip_addr, LIP_DHCP_OPTLEN_ADDRESS_REQUEST);
            optptr = optptr + LIP_DHCP_OPTLEN_ADDRESS_REQUEST;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}

static uint8_t *f_lip_dhcp_addopt_lease(const uint8_t *end_buf, uint8_t *optptr,
                                        const uint32_t lease) {
    if (optptr != NULL) {
        if ((end_buf - optptr) >= (LIP_DHCP_OPTLEN_ADDRESS_TIME + 2)) {
            *optptr++ = LIP_DHCP_OPT_ADDRESS_TIME;
            *optptr++ = LIP_DHCP_OPTLEN_ADDRESS_TIME;
            *optptr++ = (lease >> 24) & 0xFF;
            *optptr++ = (lease >> 16) & 0xFF;
            *optptr++ = (lease >> 8) & 0xFF;
            *optptr++ = (lease) & 0xFF;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}



static uint8_t *f_lip_dhcp_addopt_msg(const uint8_t *end_buf, uint8_t *optptr,
                                      const char* msg, uint8_t msglen) {
    if (optptr != NULL) {
        if ((end_buf - optptr) >= (msglen + 2)) {
            *optptr++ = LIP_DHCP_OPT_MESSAGE;
            *optptr++ = msglen;
            memcpy(optptr, msg, msglen);
            optptr = optptr + msglen;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}

/*******************************************************************
 * добавление опции, указывающей максимальный размер пакета.
 * используется в DHCP_DISCOVERY и DHCP_REQUEST
 *************************************************************/
static uint8_t *f_lip_dhcp_addopt_msg_size(const uint8_t *end_buf, uint8_t *optptr) {
    if (optptr != NULL) {
        if ((end_buf - optptr) >= (LIP_DHCP_OPTLEN_MAX_MSG_SIZE + 2)) {
            *optptr++ = LIP_DHCP_OPT_MAX_MSG_SIZE;
            *optptr++ = LIP_DHCP_OPTLEN_MAX_MSG_SIZE;
            *optptr++ = (LIP_DHCPC_MSG_SIZE_MAX >> 8) & 0xFF;
            *optptr++ = LIP_DHCPC_MSG_SIZE_MAX&0xFF;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}

static LINLINE uint8_t *f_lip_dhcp_addopt_reqlist(const uint8_t *end_buf, uint8_t *optptr) {
    if (optptr != NULL) {
        int req_opt_cnt = 4;
        if ((end_buf - optptr) >= (req_opt_cnt + 2)) {
            *optptr++ = LIP_DHCP_OPT_PAREMETER_LIST;
            *optptr++ = req_opt_cnt;
            *optptr++ = LIP_DHCP_OPT_SUBNET_MASK;
            *optptr++ = LIP_DHCP_OPT_ROUTER;
            *optptr++ = LIP_DHCP_OPT_DOMAIN_SERVER;
            *optptr++ = LIP_DHCP_OPT_DOMAIN_NAME;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}

#ifdef LIP_DHCPC_USE_RAPID_COMMIT
static uint8_t *f_lip_dhcp_addopt_rapid_commit(const uint8_t *end_buf, uint8_t *optptr) {
    if (optptr != NULL) {
        if ((end_buf - optptr) >= (LIP_DHCP_OPTLEN_RAPID_COMMIT + 2)) {
            *optptr++ = LIP_DHCP_OPT_RAPID_COMMIT;
            *optptr++ = LIP_DHCP_OPTLEN_RAPID_COMMIT;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}

#endif


static uint8_t *f_lip_dhcp_addopt_end(const uint8_t *end_buf, uint8_t *optptr) {
    if (optptr != NULL) {
        if ((end_buf - optptr) > 0) {
            *optptr++ = LIP_DHCP_OPT_END;
        } else {
            optptr = NULL;
        }
    }
    return optptr;
}







/*********************************************************************************
 * Получение буфера для передачи dhcp-сообщения и заполнение
 *       стандартного заголовка DHCP
 * Параметры:
 *         max_size - (out) макс. размер для помещения опций
 *         end      - (out) указатель на позицию в буфере, с которой можно добавлять следующие опции
 * Возвращаемое значение:
 *      указатель на буфер для DHCP сообщения
 *********************************************************************************/
static uint8_t *f_lip_dhcp_create_msg(uint8_t **end, int *max_size, int unicast) {
    t_lip_dhcp_hdr *m;
    /* если адрес уже назначен через DHCP - то используем его,
      иначе - нулевой адресс */
    if (LIP_DHCPC_ADDR_IS_ACQUIRED()) {
        f_state.tx_info.flags &= ~LIP_IP_FLAG_UNSPEC_LOCAL_IP;
    } else {
        f_state.tx_info.flags |= LIP_IP_FLAG_UNSPEC_LOCAL_IP;
    }

    if (unicast) {
        f_state.tx_info.dst_addr = f_state.info.serverid;
        f_state.tx_info.flags &= ~LIP_IP_FLAG_BROADCAST;
    } else {
        f_state.tx_info.flags |= LIP_IP_FLAG_BROADCAST;
    }

    m = (t_lip_dhcp_hdr *)lip_udp_prepare(&f_state.tx_info, max_size);

    if (m && (*max_size >= (int)LIP_DHCP_START_OPT_POS)) {
        m->op = LIP_DHCP_OP_BOOTREQUEST;
        m->htype = LIP_HRD_ETHERNET;
        m->hlen = LIP_MAC_ADDR_SIZE;
        m->hops = 0;
        m->xid = f_state.xid;
        m->secs = 0;//(clock_time() - f_state.start_time)/CLOCK_CONF_SECOND;
        m->flags = 0;//HTON16(LIP_DHCP_FLAGS_BROADCAST); /*  Broadcast bit. */
        /* действительный адрес в поле ciaddr может быть только в определенных
         * состояниях - смотри rfc2131 Table 5 Page 37 */
        if (LIP_DHCPC_ADDR_IS_ACQUIRED()) {
            memcpy(m->ciaddr, f_state.info.ip_addr, LIP_IPV4_ADDR_SIZE);
        } else {
            memset(m->ciaddr, 0, LIP_IPV4_ADDR_SIZE);
        }
        memset(m->yiaddr, 0, LIP_IPV4_ADDR_SIZE);
        memset(m->siaddr, 0, LIP_IPV4_ADDR_SIZE);
        memset(m->giaddr, 0, LIP_IPV4_ADDR_SIZE);
        memcpy(m->chaddr, lip_eth_cur_mac(), LIP_MAC_ADDR_SIZE);
        memset(&m->chaddr[LIP_MAC_ADDR_SIZE], 0, sizeof(m->chaddr) - LIP_MAC_ADDR_SIZE);
        memset(m->sname, 0, sizeof(m->sname));
        memset(m->file, 0, sizeof(m->file));
        memcpy(m->options, f_magic_cookie, sizeof(f_magic_cookie));
        *end = ((uint8_t*)m) + LIP_DHCP_START_OPT_POS;
    } else {
        if (m != NULL)  {
            m = NULL;
            *max_size = LIP_ERR_DHCPC_TX_INSUF_SIZE;
        }
        *end = 0;
    }
    return (uint8_t*)m;
}


/**********************************************************************************
 *   Посылает запрос поиска сервера DHCP.
 ***********************************************************************************/
static int f_lip_dhcp_send_discover(void) {
    int max_size = 0;
    uint8_t *end, *tx_buf, *end_buf;

    tx_buf = f_lip_dhcp_create_msg(&end, &max_size, 0);

    if (tx_buf != NULL)  {
        end_buf = tx_buf + max_size;
        end = f_lip_dhcp_addopt_msg_type(end_buf, end, LIP_DHCP_MSGTYPE_DISCOVER);
        end = f_lip_dhcp_addopt_reqlist(end_buf, end);
        end = f_lip_dhcp_addopt_client_id(end_buf, end);
        end = f_lip_dhcp_addopt_msg_size(end_buf, end);
#ifdef LIP_DHCPC_INITIAL_LEASE_REQUEST
        end = f_lip_dhcp_addopt_lease(end_buf, end, LIP_DHCPC_INITIAL_LEASE_REQUEST);
#endif
#ifdef LIP_DHCPC_USE_RAPID_COMMIT
        end = f_lip_dhcp_addopt_rapid_commit(end_buf, end);
#endif
        end = f_lip_dhcp_addopt_end(end_buf, end);
        if (end != NULL) {
            max_size = lip_udp_flush(end - tx_buf);
        } else {
            max_size = LIP_ERR_DHCPC_TX_INSUF_SIZE;
        }
    }
    return max_size;
}


/**********************************************************************************
 *   Посылает запрос к серверу DHCP на получения адреса. Вызывается после приема сообщения DHCP_OFFER
 *   Параметры:
 *      info   (in) - структура с данными из сообщения DHCP_OFFER на который мы отвечаем запросом
 *   Возвращаемое значение:
 *      код ошибки
 ***********************************************************************************/
static int f_lip_dhcp_send_request(void) {
    int max_size = 0;
    uint8_t *end, *tx_buf, *end_buf;

    tx_buf = f_lip_dhcp_create_msg(&end, &max_size, (f_state.state == DHCP_STATE_RENEWING));
    if (tx_buf != NULL) {
        end_buf = tx_buf + max_size;
        end = f_lip_dhcp_addopt_msg_type(end_buf, end, LIP_DHCP_MSGTYPE_REQUEST);
        if ((f_state.state == DHCP_STATE_SELECTING)
                || (f_state.state == DHCP_STATE_REQUESTING)) {
            end = f_lip_dhcp_addopt_server_id(end_buf, end, f_state.info.serverid);
        }

        if ((f_state.state != DHCP_STATE_RENEWING) &&
                (f_state.state != DHCP_STATE_REBINDING)) {
            end = f_lip_dhcp_addopt_ipaddr(end_buf, end, f_state.info.ip_addr);
        }

        end = f_lip_dhcp_addopt_client_id(end_buf, end);
        end = f_lip_dhcp_addopt_msg_size(end_buf, end);

        /* если продлеваем адренду - запрашиваем то же время, что
         * было изначально выделенно
         */
        if ((f_state.state == DHCP_STATE_RENEWING) ||
             (f_state.state == DHCP_STATE_REBINDING)) {
            end = f_lip_dhcp_addopt_lease(end_buf, end, f_state.info.lease_time);
#ifdef LIP_DHCPC_INITIAL_LEASE_REQUEST
        } else {
            end = f_lip_dhcp_addopt_lease(end_buf, end, LIP_DHCPC_INITIAL_LEASE_REQUEST);
#endif
        }

        end = f_lip_dhcp_addopt_end(end_buf, end);

        if (end != NULL) {
            max_size = lip_udp_flush(end - tx_buf);
        } else {
            max_size = LIP_ERR_DHCPC_TX_INSUF_SIZE;
        }
    }
    return max_size;
}



/* посылка DHCP_DECLINE сообщения. Используются в при случае,
 * если выданный сервером адрес не прошел проверку
 */
static int f_lip_dhcp_send_decline(void) {
    int max_size = 0;
    uint8_t *end, *tx_buf, *end_buf;

    tx_buf = f_lip_dhcp_create_msg(&end, &max_size,0);
    if (tx_buf != NULL) {
        end_buf = tx_buf + max_size;
        f_state.xid = f_lip_dhcp_get_xid();
        end = f_lip_dhcp_addopt_msg_type(end_buf, end, LIP_DHCP_MSGTYPE_DECLINE);
        end = f_lip_dhcp_addopt_server_id(end_buf, end, f_state.info.serverid);
        end = f_lip_dhcp_addopt_ipaddr(end_buf, end, f_state.info.ip_addr);
        end = f_lip_dhcp_addopt_client_id(end_buf, end);
        end = f_lip_dhcp_addopt_msg(end_buf, end, LIP_DHCPC_DECLINE_MSG, sizeof(LIP_DHCPC_DECLINE_MSG));
        end = f_lip_dhcp_addopt_end(end_buf, end);

        if (end != NULL) {
            max_size = lip_udp_flush(end - tx_buf);
        } else {
            max_size = LIP_ERR_DHCPC_TX_INSUF_SIZE;
        }
    }
    return max_size;
}


/* посылка DHCP_RELEASE сообщения. Используется, когда
 * клиент перестает использовать выделенный адрес (при lip_dhcpc_enable(0) или lip_dhcpc_close)
 */
static int f_lip_dhcp_send_release(void) {
    int max_size = 0;
    uint8_t *end, *tx_buf, *end_buf;

    tx_buf = f_lip_dhcp_create_msg(&end, &max_size, 1);
    if (tx_buf != NULL) {
        end_buf = tx_buf + max_size;
        end = f_lip_dhcp_addopt_msg_type(end_buf, end, LIP_DHCP_MSGTYPE_RELEASE);
        end = f_lip_dhcp_addopt_server_id(end_buf, end, f_state.info.serverid);
        end = f_lip_dhcp_addopt_client_id(end_buf, end);
        end = f_lip_dhcp_addopt_end(end_buf, end);

        if (end != NULL) {
            max_size = lip_udp_flush(end - tx_buf);
        } else {
            max_size = LIP_ERR_DHCPC_TX_INSUF_SIZE;
        }
    }
    return max_size;
}


/**********************************************************************
  разбор опций из массива optptr длиной len
  информация о опциях сохраняется в поля info и type
  разбор идет или до конца массива или до опции LIP_DHCP_OPT_END
  ********************************************************************/
static void f_check_options(const uint8_t *optptr, const uint8_t *end,
                            t_lip_dhcp_info *info, uint8_t *type) {
    while(optptr < end) {
        switch(*optptr) {
            case LIP_DHCP_OPT_MSG_TYPE:
                *type = *(optptr + 2);
                break;
            case LIP_DHCP_OPT_SERVER_ID:
                memcpy(info->serverid, optptr + 2, LIP_IPV4_ADDR_SIZE);
                break;
            case LIP_DHCP_OPT_SUBNET_MASK:
                memcpy(info->msk, optptr + 2, LIP_IPV4_ADDR_SIZE);
                break;
            case LIP_DHCP_OPT_ROUTER:
                memcpy(info->gate, optptr + 2, LIP_IPV4_ADDR_SIZE);
                break;
            case LIP_DHCP_OPT_DOMAIN_SERVER:
                memcpy(info->dns, optptr + 2, LIP_IPV4_ADDR_SIZE);
                break;
            case LIP_DHCP_OPT_ADDRESS_TIME:
                if (optptr[1] == LIP_DHCP_OPTLEN_ADDRESS_TIME) {
                    memcpy(&info->lease_time, optptr + 2, LIP_DHCP_OPTLEN_ADDRESS_TIME);
                    info->lease_time = NTOH32(info->lease_time);
                }
                break;
            case LIP_DHCP_OPT_END:
                optptr = end;
                break;
            case LIP_DHCP_OPT_PAD:
                optptr++;
                break;
            case LIP_DHCP_OPT_RENEWAL_TIME:
                if (optptr[1] == LIP_DHCP_OPTLEN_RENEWAL_TIME) {
                     memcpy(&info->t1, optptr+2, LIP_DHCP_OPTLEN_RENEWAL_TIME);
                     info->t1 = NTOH32(info->t1);
                }
                break;
            case LIP_DHCP_OPT_REBINDING_TIME:
                if (optptr[1] == LIP_DHCP_OPTLEN_REBINDING_TIME) {
                    memcpy(&info->t2, optptr+2, LIP_DHCP_OPTLEN_REBINDING_TIME);
                    info->t2 = NTOH32(info->t2);
                }
                break;
            case LIP_DHCP_OPT_OVERLOAD:
                if (optptr[1] == LIP_DHCP_OPTLEN_OVERLOAD) {
                    if (optptr[2]==LIP_DHCP_OPTVAL_OVERLOAD_FILE) {
                        info->flags |= LIP_DHCP_INFOFLAGS_OVERLOAD_FILE;
                    } else  if (optptr[2]==LIP_DHCP_OPTVAL_OVERLOAD_SNAME) {
                        info->flags |= LIP_DHCP_INFOFLAGS_OVERLOAD_SNAME;
                    } else if (optptr[2]==LIP_DHCP_OPTVAL_OVERLOAD_BOTH) {
                        info->flags |= (LIP_DHCP_INFOFLAGS_OVERLOAD_FILE |
                                        LIP_DHCP_INFOFLAGS_OVERLOAD_SNAME);
                    }
                }
                break;
#ifdef LIP_DHCPC_USE_RAPID_COMMIT
                /* для Rapid Commit опции важно только ее присутствие - это
                 * значит что сервер послал сразу ACK на DISCOVERY
                 */
            case LIP_DHCP_OPT_RAPID_COMMIT:
                if (optptr[1] == LIP_DHCP_OPTLEN_RAPID_COMMIT) {
                    info->flags |= LIP_DHCP_INFOFLAGS_RAPID_COMMIT_PRESENT;
                }
                break;
#endif
        }
        /* во всех опциях кроме END и PAD второй октет - длина */
        if ((*optptr != LIP_DHCP_OPT_END) && (*optptr != LIP_DHCP_OPT_PAD)) {
            optptr += optptr[1] + 2;
        }
    }
}

/**********************************************************************************
 *   Разбор опций dhcp-пакета и сохранение нужных в info
 *   Параметры:
 *      optptr (in)  - указатель на начало опций dhcp
 *      len    (in)  - размер буфера с опциями
 *      info   (out) - указатель на структуру, куда сохраняются нужные опции
 *   Возвращаемое значение:
 *      тип dhcp сообщения
 ***********************************************************************************/
static uint8_t f_lip_dhcp_parse_options(const t_lip_dhcp_hdr *hdr, const uint8_t *optptr,
                                        int len, t_lip_dhcp_info *info) {
    const uint8_t *end = optptr + len;
    uint8_t type = 0;

    info->t2 = info->t1 = 0;
    info->flags = 0;
    /* вначале проверяем наличие опций в поле опций */
    f_check_options(optptr, end, info, &type);
    /* если есть признак, что поле file перегружено - то дальше
      опции проверяются в нем  - rfc2131 page 24*/
    if (info->flags & LIP_DHCP_INFOFLAGS_OVERLOAD_FILE)
        f_check_options(hdr->file, hdr->file + LIP_DHCP_HDR_FILE_SIZE, info, &type);

    /* следующим проверяется поле sname */
    if (info->flags & LIP_DHCP_INFOFLAGS_OVERLOAD_SNAME)
        f_check_options(hdr->sname, hdr->sname+LIP_DHCP_HDR_SNAME_SIZE, info, &type);


    if ((type == LIP_DHCP_MSGTYPE_ACK) || (type == LIP_DHCP_MSGTYPE_OFFER)) {
        memcpy(info->ip_addr, hdr->yiaddr, LIP_IPV4_ADDR_SIZE);
    }

    if (type == LIP_DHCP_MSGTYPE_ACK) {
        /* при бесконечной аренде t1 и t2 не имеют значения */
        if (info->lease_time == LIP_DHCP_TIME_INFINITY) {
            info->t1 = info->t2 = LIP_DHCP_TIME_INFINITY;
        } else {
            /* если времена не заданы опциями, то используются значения
              по-умолчанию из rfc2131 4.4.5 page 41*/
            if (!info->t1) {
                /* t1 = 0.5 * lease */
                info->t1 = info->lease_time >> 1;
            }

            if (!info->t2) {
                /* t2 = 0.875 * lease */
                info->t2 = (info->lease_time >> 3) * 7;
            }
        }
    }
    return type;
}


static void f_addr_conflict(void) {
    t_lip_dhcp_fsm_state state = f_state.state;
    lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_WARNING_HIGH, 0,
       "lip dhcpc: dhcp address conflict detected!\n");
    /* меняем состояние перед тем как послать DECLINE (чтобы не включать ciaddr)*/
    f_state.state = DHCP_STATE_LINK_WAIT;
    f_lip_dhcp_send_decline();
    f_state.state = state;
    /* возвращаемся в начальное состояние, чтобы
    * повторить операцию получения адреса */
    f_lip_dhcp_fail_reiinit();
}


/**************************************************************************
 * callback-функция, вызываемая ACD-модулем при возникновении конфликта
 * или при утверждении проверяемого адреса
 **************************************************************************/
static void f_acd_cb(const uint8_t *addr, t_lip_ipv4_acd_event event) {
    if ((event == LIP_IPV4_ACD_EVENT_ADDR_ESTABLISHED) && (f_state.state == DHCP_STATE_ADDR_PROBE)) {
        /* адрес, выделенный DHCP-сервером, проверен */
        f_state.state = DHCP_STATE_BOUND;

        lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_PROGRESS, 0,
                   "lip dhcpc: acquire dhcp address %d.%d.%d.%d for %u seconds\n",
                   f_state.info.ip_addr[0], f_state.info.ip_addr[1], f_state.info.ip_addr[2],
                   f_state.info.ip_addr[3], f_state.info.lease_time);

        /* устанавливаем параметры ip-уровня */
        lip_ipv4_set_addr_ex(f_state.info.ip_addr, 0);
        lip_ipv4_set_mask(f_state.info.msk);
        lip_ipv4_set_gate(f_state.info.gate);

        /* если нужен пользовательский callback - вызываем */
#ifdef LIP_DHCPC_CONFIGURED_CB
        lip_dhcpc_configured_cb(&f_state.info);
#endif
    } else if (event == LIP_IPV4_ACD_EVENT_ADDR_CONFLICT) {
        if (f_state.en) {
            f_addr_conflict();
        } else {
            /* если DHCP не разрешен, то сбрасываем адрес, чтобы не использовать
             * его в дальнейшем */
            lip_ipv4_addr_set_invalid(f_state.info.ip_addr);
            f_state.state = DHCP_STATE_LINK_WAIT;
        }
    }
}


/***********************************************************************************
 *   Обработка пришедших пакетов для dhcp клиента
 *   Параметры
 *     pkt             (in) - указатель на буфер с данными
 *     size            (in)- длина данных
 *     lip_udp_info_t  (in) - информация о дейтаграмме с UDP уровня
 *   Возвращаемое значение:
 *     код ошибки
 ************************************************************************************ */
static t_lip_errs f_lip_dhcpc_process_packet(uint8_t *pkt, size_t size, const t_lip_udp_info *info) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    if (f_state.en) {
        if (size < sizeof(t_lip_dhcp_hdr)) {
            res = LIP_ERR_DHCPC_RX_PACKET_SIZE;
        } else {
            const t_lip_dhcp_hdr *hdr = (const t_lip_dhcp_hdr *)(pkt);
            /* принимает только ответ, сверяем индентификатор, mac и
             * dhcp magic cookie
             */
            if ((hdr->op == LIP_DHCP_OP_BOOTREPLY) &&
                 (memcmp(hdr->chaddr, lip_eth_cur_mac(), LIP_MAC_ADDR_SIZE) == 0) &&
                 (memcmp(hdr->options, f_magic_cookie, sizeof(f_magic_cookie)) == 0)) {

                t_lip_dhcp_info dhcp_info;
                /* разбираем пришедшее сообщение */
                uint8_t type = f_lip_dhcp_parse_options(hdr, &pkt[LIP_DHCP_START_OPT_POS],
                                                        size - LIP_DHCP_START_OPT_POS, &dhcp_info);
                if (!memcmp(&hdr->xid, &f_state.xid, sizeof(f_state.xid))) {
                    /* если пришело предложение и мы его ждем - посылаем запрос
                     * запрос шлется на первый пришедший OFFER. не ждем нескольких */
                    if ((f_state.state == DHCP_STATE_SELECTING) && (type == LIP_DHCP_MSGTYPE_OFFER)) {
                        lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                   "lip dhcpc: offer received. send request\n");

                        /* сохраняем server id и адресс */
                        lip_ipv4_addr_cpy(f_state.info.ip_addr, dhcp_info.ip_addr);
                        lip_ipv4_addr_cpy(f_state.info.serverid, dhcp_info.serverid);
                        /* шлем запрос */
                        f_lip_dhcp_send_request();
                        f_state.state = DHCP_STATE_REQUESTING;
                        f_state.retr_cnt = 0;
                        f_set_tout();
                    } else if ((f_state.state == DHCP_STATE_REQUESTING)
                                || (f_state.state == DHCP_STATE_REBOOTING)
#ifdef LIP_DHCPC_USE_RAPID_COMMIT
                                /* можем принять сразу ACK без OFFER с
                                 * RAPID-COMMIT если поддерживаем */
                                || ((f_state.state == DHCP_STATE_SELECTING)
                                        && (dhcp_info.flags & LIP_DHCP_INFOFLAGS_RAPID_COMMIT_PRESENT))
#endif
                        ) {

                        if (type == LIP_DHCP_MSGTYPE_ACK) {
                            /* сохраняем все параметры сообщения */
                            f_state.info = dhcp_info;
                            f_state.elapsed_time = 0;

                            /* вначале проверяем, что адрес нормальный */
                            if (!lip_ipv4_addr_is_routable(f_state.info.ip_addr)) {
                                lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_ERROR_HIGH,
                                           LIP_ERR_DHCPC_INVALID_ADDR,
                                           "lip dhcpc: invalid addr received in ack: %d.%d.%d.%d\n",
                                           f_state.info.ip_addr[0], f_state.info.ip_addr[1],
                                           f_state.info.ip_addr[2], f_state.info.ip_addr[3]);
                                f_addr_conflict();
                            } else {
                                /* пршел ack - по rfc2131 нужно проверть адрес
                                 * используем алгоритм из rfc5227,
                                 * раелизованный в модуле lip_ipv4_acd
                                 */
                                lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                      "lip dhcpc: ack received. start address probe\n");
                                f_state.state = DHCP_STATE_ADDR_PROBE;
                                lip_ipv4_acd_add(f_state.info.ip_addr, f_acd_cb, 0,
#ifdef LIP_DHCPC_FAST_ADDR_CHECK
                                                   LIP_IPV4_ADC_FLAGS_FAST_PROBE
#else
                                                   0
#endif
                                                   );
                            }
                        } else if (type == LIP_DHCP_MSGTYPE_NAK) {
                            /* приняли NACK => переходим в начальное состояние */
                            lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_WARNING_HIGH, 0,
                                      "lip dhcpc: dhcp nack recieved!\n");
                            f_lip_dhcp_fail_reiinit();
                        }
                    } else if ((f_state.state == DHCP_STATE_RENEWING)
                                || (f_state.state == DHCP_STATE_REBINDING)) {
                        /* если ACK => продлили аренду */
                        if (type == LIP_DHCP_MSGTYPE_ACK) {
                            f_state.info.lease_time = dhcp_info.lease_time;
                            f_state.info.t1 = dhcp_info.t1;
                            f_state.info.t2 = dhcp_info.t2;
                            f_state.elapsed_time = 0;
                            f_state.state = DHCP_STATE_BOUND;
                            lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                       "lip dhcpc: lease time reacquired for %u seconds\n", f_state.info.lease_time);
                        } else if (type == LIP_DHCP_MSGTYPE_NAK) {
                            /* приняли NACK => переходим в начальное состояние */
                            lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_WARNING_HIGH, 0,
                                      "lip dhcpc: dhcp nack recieved!\n");
                            f_lip_dhcp_fail_reiinit();
                        }
                    }
                } /* if (!memcmp(&hdr->xid, &f_state.xid, sizeof(f_state.xid))) */
#ifdef LIP_DHCPC_USE_RECONFIGURE
                if ((type == LIP_DHCP_MSGTYPE_FORCERENEW)
                        && (f_state.state == DHCP_STATE_BOUND)) {
                    /* по FORCERENEW переходим в renew состояние для продления
                     * аренды или получения нового адреса. просто устанавливаем
                     * t1 на время для срабатывания перехода - при слудующем
                     * f_check_lease - все выполнится автоматом    */
                    f_state.info.t1 = f_state.elapsed_time;
                    /* если аренда дана на бесконечное время - не войдем
                     * в renew => устанавливаем время аренды относительно
                     * на время, что t1 будет временем по-умолчнию
                     */
                    if (f_state.info.lease_time == LIP_DHCP_TIME_INFINITY) {
                        f_state.info.lease_time = 2*f_state.elapsed_time;
                        f_state.info.t2 = (f_state.info.lease_time >> 3) * 7;
                    }
                }
#endif
            } /* if ((hdr->op == LIP_DHCP_OP_BOOTREPLY)     &&
                 !memcmp(hdr->chaddr, g_lip_st.eth.addr, LIP_MAC_ADDR_SIZE) &&
                 !memcmp(hdr->options, f_magic_cookie, sizeof(f_magic_cookie))) */
        } /* if (size < sizeof(t_lip_dhcp_hdr)) else */
    } /* if (f_state.en) */
    return res;
}

/* устанавливаем таймаут для следующей попытки продлить аренду:
   устанавливаем на середину времени между t1 и t2
 * но не меньше LIP_DHCPC_LEASE_REACQ_TOUT_MIN */
static uint32_t f_next_lease_tout(uint32_t t1, uint32_t t2) {
    uint32_t add_dt = (t2- t1)/2;
    if (add_dt < LIP_DHCPC_LEASE_REACQ_TOUT_MIN/1000)
        add_dt = LIP_DHCPC_LEASE_REACQ_TOUT_MIN/1000;

    /* добавляем прсевдослучайный разброс в таймауте на
     * основе текущего времени */
    switch (lclock_get_ticks() & 0x3) {
        case 0:
            add_dt -= 1;
            break;
        case 1:
            add_dt += 1;
            break;
        case 2:
            add_dt -= 2;
            break;
        default:
            break;
    }

    return t1 + add_dt;
}

static void f_check_lease(void) {
    if (f_state.info.lease_time != LIP_DHCP_TIME_INFINITY) {
        t_lclock_ticks cur_clock = lclock_get_ticks();
        if ((cur_clock - f_state.start_time) > LCLOCK_TICKS_PER_SECOND) {
            /* увеличиваем израсходованное время аренды */
            f_state.elapsed_time +=  (cur_clock - f_state.start_time)/LCLOCK_TICKS_PER_SECOND;

            if ((f_state.state == DHCP_STATE_BOUND) &&
                    (f_state.elapsed_time >= f_state.info.t1)) {
                /* по истечению таймаута t1 переходим в состояние
                  RENEWING и пытаемся продлить аренду */
                lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_PROGRESS, 0,
                           "lip dhcpc: t1 lease timeout expired. try reacquire address lease\n");
                f_state.state = DHCP_STATE_RENEWING;
                if (f_lip_dhcp_send_request() == LIP_ERR_SUCCESS) {
                    f_state.info.t1 = f_next_lease_tout(f_state.info.t1, f_state.info.t2);
                }
            } else if (f_state.state == DHCP_STATE_RENEWING) {
                if (f_state.elapsed_time >= f_state.info.t2) {
                    /* по истечению таймаута t2 переходим в состояние
                      RENEWING и пытаемся продлить аренду */
                    lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_WARNING_MEDIUM, 0,
                               "lip dhcpc: t2 lease timeout expired! try reacquire address lease over bcast\n");
                    f_state.state = DHCP_STATE_REBINDING;
                    if (f_lip_dhcp_send_request() == LIP_ERR_SUCCESS) {
                        f_state.info.t2 = f_next_lease_tout(f_state.info.t2,
                                                            f_state.info.lease_time);
                    }
                } else if (f_state.elapsed_time >= f_state.info.t1) {
                    if (f_lip_dhcp_send_request() == LIP_ERR_SUCCESS) {
                        f_state.info.t1 = f_next_lease_tout(f_state.info.t1, f_state.info.t2);
                    }
                }
            } else if (f_state.state == DHCP_STATE_REBINDING) {
                /* если закончилось время аренды - перестаем использовать адрес */
                if (f_state.elapsed_time >= f_state.info.lease_time) {
                    f_lip_dhcp_fail_reiinit();
                } else if (f_state.elapsed_time >= f_state.info.t2) {
                    if (f_lip_dhcp_send_request() == LIP_ERR_SUCCESS) {
                        f_state.info.t2 = f_next_lease_tout(f_state.info.t2,
                                                            f_state.info.lease_time);
                    }
                }
            }

            f_state.start_time = cur_clock;
        } /* if ((cur_clock - f_state.start_time) > CLOCK_CONF_SECOND) */
    } /* if (f_state.info.lease_time!=LIP_DHCP_TIME_INFINITY) */
}

static void f_reconnect(void) {
    /* если был уже получен адрес, или был установлен вручную пользовательский
     * адрес - то его сохраняем, чтобы повторно запросить.
     * иначе - начинаем поиск заново
     */
    f_state.elapsed_time = 0;
    f_state.info.lease_time = LIP_DHCP_TIME_INFINITY;

    /* если находились в состояниях, в которых адрес
      был уже установлен, то сбрасываем текущий адрес */
    if (f_state.en && LIP_DHCPC_ADDR_IS_ACQUIRED()) {
        lip_ipv4_set_addr_ex(NULL, 0);
    }
    /* адрес больше не нужен => перестаем следить за конфликтами */
    if (LIP_DHCPC_ADDR_IS_ACQUIRED() || (f_state.state == DHCP_STATE_ADDR_PROBE)) {
        lip_ipv4_acd_leave(f_state.info.ip_addr);
    }

    if (!LIP_DHCPC_ADDR_IS_ACQUIRED() &&
            !(f_state.info.flags & LIP_DHCP_INFOFLAGS_MANUAL_REQ_ADDR)) {
        lip_ipv4_addr_set_invalid(f_state.info.ip_addr);
    }

    f_state.state = DHCP_STATE_LINK_WAIT;
}


/***********************************************************************************
 *   Инициализация параметров DHCP клиента
 ***********************************************************************************/
t_lip_errs lip_dhcpc_init(void) {
    f_state.state = DHCP_STATE_LINK_WAIT;
    f_state.en = 0;
    f_state.close_req = 0;

    f_state.elapsed_time = 0;
    f_state.info.lease_time = LIP_DHCP_TIME_INFINITY;
    f_state.info.flags  = 0;
    f_state.conflict_cnt = 0;

    /**  заполняем UDP-параметры для передачи пакетов */
    memset(&f_state.tx_info, 0, sizeof(f_state.tx_info));
    f_state.tx_info.dst_port = LIP_UDP_PORT_DHCP_SERVER;
    f_state.tx_info.src_port = LIP_UDP_PORT_DHCP_CLIENT;
    f_state.tx_info.flags = LIP_IP_FLAG_UNSPEC_LOCAL_IP | LIP_IP_FLAG_BROADCAST;
    f_state.tx_info.ip_type = LIP_IPTYPE_IPv4;

    lip_ipv4_addr_set_invalid(f_state.info.ip_addr);

    /* добавляем UDP порт для прослушивания DHCP-сообщений */
    lip_udp_port_listen(LIP_UDP_PORT_DHCP_CLIENT, f_lip_dhcpc_process_packet);

    return LIP_ERR_SUCCESS;
}

/*************************************************************************************
 * <i> подключение к новой сети. если был адресс - будем пытаться запросить его *
 *************************************************************************************/
t_lip_errs lip_dhcpc_link_reconnect(void) {
    f_reconnect();
    f_state.conflict_cnt = 0;
    return LIP_ERR_SUCCESS;
}

/****************************************************************************
 * установка адреса для DHCP, который был получен в предыдущий сеанс.
 * Должен вызываться после lip_init(), но до последующих lip_pull()
 ****************************************************************************/
t_lip_errs lip_dhcpc_set_requested_addr(const uint8_t *addr) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    if (!f_state.en || (f_state.state == DHCP_STATE_LINK_WAIT)) {
        if ((addr != NULL) && lip_ipv4_addr_is_routable(addr)) {
            lip_ipv4_addr_cpy(f_state.info.ip_addr, addr);
            f_state.info.flags |= LIP_DHCP_INFOFLAGS_MANUAL_REQ_ADDR;
        } else {
            res = LIP_ERR_DHCPC_INVALID_ADDR;
            if (addr != NULL) {
                lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_ERROR_HIGH, res,
                    "lip dhcpc: try to set invalid dhcp address: %d.%d.%d.%d\n",
                    addr[0], addr[1], addr[2], addr[3]);
            } else {
                lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_ERROR_HIGH, res,
                    "lip dhcpc: NULL pointer in addr in lip_dhcp_set_requested_addr()\n");
            }
        }
    } else {
        res = LIP_ERR_DHCPC_INVALID_STATE;
    }
    return res;

}

/***********************************************************************************
 * Разрешение/запрещение получение параметров IP по DHCP
 * Параметры:
 *    en - 1 - разрешение (если было запрещено - прводит к началу последовтельности запроса к DHCP-серверу)
 *         0 - запрещение (статические параметры нужно востановить вручную)
 *
 *************************************************************************************/
void lip_dhcpc_enable(uint8_t en) {
    if (!f_state.en && en) {
        /* при разрешении - действия аналогичны подключению к новой сети */
        lip_dhcpc_link_reconnect();
        f_state.en = 1;        
    } else if (en == 0) {
        f_state.en = 0;        
        /* освобождаем адресс, если был выделен */
        if (LIP_DHCPC_ADDR_IS_ACQUIRED()) {
            f_lip_dhcp_send_release();
            lip_ipv4_set_addr_ex(NULL, 0);
        }
    }
}

/************************************************************
 *  Проверка, разрешен ли DHCP client
 *  Возвращает:
 *     0     - dhcp client не разрешен
 *     иначе - разрешен
 ************************************************************/
int lip_dhcpc_is_enabled(void) {
    return f_state.en;
}

int lip_dhcpc_in_progress(void) {
    return ((f_state.state == DHCP_STATE_SELECTING)
            || (f_state.state == DHCP_STATE_REQUESTING)
            || (f_state.state == DHCP_STATE_REBOOTING));
}

void lip_dhcpc_close(unsigned tout) {
    if (f_state.en) {
        ltimer_set(&f_state.tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));
        f_state.close_req = 1;
    }
}


/******************************************************************************************
 *  Продвижение dhcp-сервера
 *  отрабатывает главным образом таймауты
 ******************************************************************************************/
t_lip_errs lip_dhcpc_pull(void) {
    t_lip_errs res = LIP_ERR_SUCCESS;
    if (f_state.close_req) {
        if (ltimer_expired(&f_state.tmr)) {
            lip_dhcpc_enable(0);
            f_state.close_req = 0;
        }
    } else if (f_state.en) {
        switch (f_state.state) {
        case DHCP_STATE_LINK_WAIT:
            if (lip_state() > LIP_STATE_INIT) {
                /* если сконфигурировали link - переходим в состояние INIT */

                /* для посылки первого DHCP_DISCOVERY выбираем случайную задержку
                 * в интервале от LIP_DHCPC_INITIAL_DELAY_MIN до LIP_DHCPC_INITIAL_DELAY_MAX
                 * (rfc2131 4.4.1 page 36)
                 * если было заданное кол-во конфликтов - уменьшаем время - используем
                 * время LIP_DHCPC_RATE_LIMIT_INTERVAL (по принципу из rfc5227)
                 */
                if (f_state.conflict_cnt < LIP_DHCPC_MAX_CONFLICTS) {
                    ltimer_set(&f_state.tmr,
                              LTIMER_MS_TO_CLOCK_TICKS(LIP_DHCPC_INITIAL_DELAY_MIN) +
                              lip_rand_range(LTIMER_MS_TO_CLOCK_TICKS(LIP_DHCPC_INITIAL_DELAY_MAX
                                                                      -LIP_DHCPC_INITIAL_DELAY_MIN)));
                } else {
                    ltimer_set_ms(&f_state.tmr, LIP_DHCPC_RATE_LIMIT_INTERVAL);
                }

                /* если уже есть действительный адрес - то переходим
                 * к INIT_REBOOT для повторного запроса адреса */
                if (lip_ipv4_addr_is_valid(f_state.info.ip_addr)) {
                    f_state.state = DHCP_STATE_INIT_REBOOT;
                } else {
                    f_state.state = DHCP_STATE_INIT;
                }
            }
            break;
        case DHCP_STATE_INIT:
            if (ltimer_expired(&f_state.tmr)) {
                f_state.xid = f_lip_dhcp_get_xid();
                f_state.start_time = lclock_get_ticks();
                if (f_lip_dhcp_send_discover() == LIP_ERR_SUCCESS) {
                    f_state.state = DHCP_STATE_SELECTING;
                    f_state.retr_cnt = 0;
                    f_set_tout();
                    lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_PROGRESS, 0,
                             "lip dhcpc: start dhcp server discovery\n");
                }
            }
            break;
        case DHCP_STATE_INIT_REBOOT:
            /* по таймеру начинаем запрос к серверу сразу с Request */
            if (ltimer_expired(&f_state.tmr)) {
                f_state.xid = f_lip_dhcp_get_xid();
                f_state.start_time = lclock_get_ticks();
                if (f_lip_dhcp_send_request() == LIP_ERR_SUCCESS) {
                    f_state.state = DHCP_STATE_REBOOTING;
                    f_state.retr_cnt = 0;
                    f_set_tout();
                    lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_PROGRESS, 0,
                            "lip dhcpc: start requesting previously acquired address (%d.%d.%d.%d)\n",
                            f_state.info.ip_addr[0], f_state.info.ip_addr[1],
                            f_state.info.ip_addr[2] ,f_state.info.ip_addr[3]);
                }
            }
            break;
        case DHCP_STATE_SELECTING:
            /* осуществляем повторную передачу DHCP-Discover по таймауту */
            if (ltimer_expired(&f_state.tmr)) {
                /* запускаем получение link-local адреса, если не пришел ответ */
                LINK_LOCAL_START();

                if (f_lip_dhcp_send_discover() == LIP_ERR_SUCCESS) {
                    f_state.start_time = lclock_get_ticks();
                    if (f_state.retr_cnt < LIP_DHCPC_RETRANS_CNT_MAX)
                        f_state.retr_cnt++;
                    /* сообщаем об ошибке, если получили ответов на заданное кол-во DISCOVERY */
                    if (f_state.retr_cnt == LIP_DHCPC_DISOVERY_RETRANS_FAIL) {
                        f_lip_dhcp_fail();
                    }

                    f_set_tout();

                    lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_WARNING_MEDIUM, 0,
                               "lip dhcpc: dhcp timeout expired. dhcp discover retransmitted\n");
                }
            }
            break;
        case DHCP_STATE_REQUESTING:
        case DHCP_STATE_REBOOTING:
            /* осуществляем повторную передачу DHCP-Request по таймауту */
            if (ltimer_expired(&f_state.tmr)) {
                LINK_LOCAL_START();
                /* если не получили ответа за заданное кол-во попыток =>
                 * переходим в начальное состояние реконфигурации
                 */
                if (f_state.retr_cnt == (LIP_DHCPC_REQUEST_RETRANS_CNT-1))
                    f_lip_dhcp_fail_reiinit();
                else if (f_lip_dhcp_send_request() == LIP_ERR_SUCCESS) {
                    f_state.retr_cnt++;
                    f_set_tout();

                    lip_printf(LIP_LOG_MODULE_DHCPC, LIP_LOG_LVL_WARNING_MEDIUM, 0,
                               "lip dhcpc: dhcp timeout expired. dhcp request retransmitted\n");
                }
            }
            break;
        case DHCP_STATE_ADDR_PROBE:
            break;
        default:
            f_check_lease();
            break;
        }
    }
    return res;
}

#endif
