
// adapted from dhcpserver.c distributed in esp8266 sdk 2.0.0
// same license may apply

#include "lwip/inet.h"
#include "lwip/err.h"
#include "lwip/pbuf.h"
#include "lwip/udp.h"
#include "lwip/mem.h"
#include "osapi.h"

#include "lwip/apps-esp/dhcpserver.h"

#include "user_interface.h"
#include "mem.h"

#include "glue.h"
#include "lwip-helper.h"

#ifdef MEMLEAK_DEBUG
static const char mem_debug_file[] ICACHE_RODATA_ATTR = __FILE__;
#endif

////////////////////////////////////////////////////////////////////////////////////
//static const uint8_t xid[4] = {0xad, 0xde, 0x12, 0x23};
//static u8_t old_xid[4] = {0};
static const uint32 magic_cookie ICACHE_RODATA_ATTR = 0x63538263;
static struct udp_pcb *pcb_dhcps = NULL;
static ip_addr_t broadcast_dhcps;
static struct ipv4_addr server_address;
static struct ipv4_addr client_address;//added

static struct dhcps_lease dhcps_lease;
//static bool dhcps_lease_flag = true;
static list_node *plist = NULL;
static uint8 offer = 0xFF;
static bool renew = false;
#define DHCPS_LEASE_TIME_DEF    (120)
uint32 dhcps_lease_time = DHCPS_LEASE_TIME_DEF;  //minute

void wifi_softap_dhcps_client_leave(u8 *bssid, struct ipv4_addr *ip,bool force);
uint32 wifi_softap_dhcps_client_update(u8 *bssid, struct ipv4_addr *ip);

/******************************************************************************
 * FunctionName : node_insert_to_list
 * Description  : insert the node to the list
 * Parameters   : arg -- Additional argument to pass to the callback function
 * Returns      : none
*******************************************************************************/
void ICACHE_FLASH_ATTR node_insert_to_list(list_node **phead, list_node* pinsert)
{
    list_node *plist = NULL;
    struct dhcps_pool *pdhcps_pool = NULL;
    struct dhcps_pool *pdhcps_node = NULL;
    if (*phead == NULL)
        *phead = pinsert;
    else {
        plist = *phead;
        pdhcps_node = (struct dhcps_pool*)pinsert->pnode;
        pdhcps_pool = (struct dhcps_pool*)plist->pnode;

        if(pdhcps_node->ip.addr < pdhcps_pool->ip.addr) {
            pinsert->pnext = plist;
            *phead = pinsert;
        } else {
            while (plist->pnext != NULL) {
                pdhcps_pool = (struct dhcps_pool*)plist->pnext->pnode;
                if (pdhcps_node->ip.addr < pdhcps_pool->ip.addr) {
                    pinsert->pnext = plist->pnext;
                    plist->pnext = pinsert;
                    break;
                }
                plist = plist->pnext;
            }

            if(plist->pnext == NULL) {
                plist->pnext = pinsert;
            }
        }
    }
//  pinsert->pnext = NULL;
}

/******************************************************************************
 * FunctionName : node_delete_from_list
 * Description  : remove the node from list
 * Parameters   : arg -- Additional argument to pass to the callback function
 * Returns      : none
*******************************************************************************/
void ICACHE_FLASH_ATTR node_remove_from_list(list_node **phead, list_node* pdelete)
{
    list_node *plist = NULL;

    plist = *phead;
    if (plist == NULL){
        *phead = NULL;
    } else {
        if (plist == pdelete){
            *phead = plist->pnext;
            pdelete->pnext = NULL;
        } else {
            while (plist != NULL) {
                if (plist->pnext == pdelete){
                    plist->pnext = pdelete->pnext;
                    pdelete->pnext = NULL;
                }
                plist = plist->pnext;
            }
        }
    }
}

/******************************************************************************
 * FunctionName : wifi_softap_add_dhcps_lease
 * Description  : add static lease on the list, this will be the next available @
 * Parameters   : mac address
 * Returns      : true if ok and false if this mac already exist or if all ip are already reserved
*******************************************************************************/
bool ICACHE_FLASH_ATTR wifi_softap_add_dhcps_lease(uint8 *macaddr)
{
    struct dhcps_pool *pdhcps_pool = NULL;
    list_node *pback_node = NULL;
	
    uint32 start_ip = dhcps_lease.start_ip.addr;
    uint32 end_ip = dhcps_lease.end_ip.addr;
	
    for (pback_node = plist; pback_node != NULL; pback_node = pback_node->pnext) {
        pdhcps_pool = pback_node->pnode;
        if (os_memcmp(pdhcps_pool->mac, macaddr, sizeof(pdhcps_pool->mac)) == 0) {
#if DHCPS_DEBUG
            os_printf("this mac already exist");
#endif
            return false;
        }
        else start_ip = htonl((ntohl(start_ip) + 1));
    }
	
    if (start_ip > end_ip) {
#if DHCPS_DEBUG
        os_printf("no more ip available");
#endif
        return false;
    }
	
    pdhcps_pool = (struct dhcps_pool *)os_zalloc(sizeof(struct dhcps_pool));
    pdhcps_pool->ip.addr = start_ip;
    os_memcpy(pdhcps_pool->mac, macaddr, sizeof(pdhcps_pool->mac));
    pdhcps_pool->lease_timer = DHCPS_LEASE_TIMER;
    pdhcps_pool->type = DHCPS_TYPE_STATIC;
    pdhcps_pool->state = DHCPS_STATE_ONLINE;
    pback_node = (list_node *)os_zalloc(sizeof(list_node ));
    pback_node->pnode = pdhcps_pool;
    pback_node->pnext = NULL;
    node_insert_to_list(&plist,pback_node);
	
    return true;
}

///////////////////////////////////////////////////////////////////////////////////
/*
 * ��DHCP msg��Ϣ�ṹ����������
 *
 * @param optptr -- DHCP msg��Ϣλ��
 * @param type -- Ҫ��ӵ�����option
 *
 * @return uint8_t* ����DHCP msgƫ�Ƶ�ַ
 */
///////////////////////////////////////////////////////////////////////////////////
static uint8_t* ICACHE_FLASH_ATTR add_msg_type(uint8_t *optptr, uint8_t type)
{

        *optptr++ = DHCP_OPTION_MSG_TYPE;
        *optptr++ = 1;
        *optptr++ = type;
        return optptr;
}
///////////////////////////////////////////////////////////////////////////////////
/*
 * ��DHCP msg�ṹ������offerӦ������
 *
 * @param optptr -- DHCP msg��Ϣλ��
 *
 * @return uint8_t* ����DHCP msgƫ�Ƶ�ַ
 */
///////////////////////////////////////////////////////////////////////////////////
static uint8_t* ICACHE_FLASH_ATTR add_offer_options(uint8_t *optptr)
{
        struct ipv4_addr ipadd;

        ipadd.addr = server_address.addr;

        struct ip_info if_ip;
        os_bzero(&if_ip, sizeof(struct ip_info));
        wifi_get_ip_info(SOFTAP_IF, &if_ip);
    
        *optptr++ = DHCP_OPTION_SUBNET_MASK;
        *optptr++ = 4;
        *optptr++ = ip4_addr1( &if_ip.netmask);
        *optptr++ = ip4_addr2( &if_ip.netmask);
        *optptr++ = ip4_addr3( &if_ip.netmask);
        *optptr++ = ip4_addr4( &if_ip.netmask);

        *optptr++ = DHCP_OPTION_LEASE_TIME;
        *optptr++ = 4;  
        *optptr++ = ((DHCPS_LEASE_TIMER * 60) >> 24) & 0xFF;
        *optptr++ = ((DHCPS_LEASE_TIMER * 60) >> 16) & 0xFF;
        *optptr++ = ((DHCPS_LEASE_TIMER * 60) >> 8) & 0xFF;
        *optptr++ = ((DHCPS_LEASE_TIMER * 60) >> 0) & 0xFF;

        *optptr++ = DHCP_OPTION_SERVER_ID;
        *optptr++ = 4;  
        *optptr++ = ip4_addr1( &ipadd);
        *optptr++ = ip4_addr2( &ipadd);
        *optptr++ = ip4_addr3( &ipadd);
        *optptr++ = ip4_addr4( &ipadd);

        if (dhcps_router_enabled(offer)){
            *optptr++ = DHCP_OPTION_ROUTER;
            *optptr++ = 4;
            *optptr++ = ip4_addr1( &if_ip.gw);
            *optptr++ = ip4_addr2( &if_ip.gw);
            *optptr++ = ip4_addr3( &if_ip.gw);
            *optptr++ = ip4_addr4( &if_ip.gw);
        }

#ifdef USE_DNS
        *optptr++ = DHCP_OPTION_DNS_SERVER;
        *optptr++ = 4;
        *optptr++ = ip4_addr1( &ipadd);
        *optptr++ = ip4_addr2( &ipadd);
        *optptr++ = ip4_addr3( &ipadd);
        *optptr++ = ip4_addr4( &ipadd);
#endif

#ifdef CLASS_B_NET
        *optptr++ = DHCP_OPTION_BROADCAST_ADDRESS;
        *optptr++ = 4;  
        *optptr++ = ip4_addr1( &ipadd);
        *optptr++ = 255;
        *optptr++ = 255;
        *optptr++ = 255;
#else
        *optptr++ = DHCP_OPTION_BROADCAST_ADDRESS;
        *optptr++ = 4;  
        *optptr++ = ip4_addr1( &ipadd);
        *optptr++ = ip4_addr2( &ipadd);
        *optptr++ = ip4_addr3( &ipadd);
        *optptr++ = 255;
#endif

        *optptr++ = DHCP_OPTION_INTERFACE_MTU;
        *optptr++ = 2;  
#ifdef CLASS_B_NET
        *optptr++ = 0x05;
        *optptr++ = 0xdc;
#else
        *optptr++ = 0x02;
        *optptr++ = 0x40;
#endif

        *optptr++ = DHCP_OPTION_PERFORM_ROUTER_DISCOVERY;
        *optptr++ = 1;  
        *optptr++ = 0x00; 

        *optptr++ = 43;
        *optptr++ = 6;

        *optptr++ = 0x01;
        *optptr++ = 4;  
        *optptr++ = 0x00;
        *optptr++ = 0x00;
        *optptr++ = 0x00;
        *optptr++ = 0x02;

        return optptr;
}
///////////////////////////////////////////////////////////////////////////////////
/*
 * ��DHCP msg�ṹ����ӽ����־����
 *
 * @param optptr -- DHCP msg��Ϣλ��
 *
 * @return uint8_t* ����DHCP msgƫ�Ƶ�ַ
 */
///////////////////////////////////////////////////////////////////////////////////
static uint8_t* ICACHE_FLASH_ATTR add_end(uint8_t *optptr)
{

        *optptr++ = DHCP_OPTION_END;
        return optptr;
}
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
static void ICACHE_FLASH_ATTR create_msg(struct dhcps_msg *m)
{
        struct ipv4_addr client;

        client.addr = client_address.addr;

        m->op = DHCP_REPLY;
        m->htype = DHCP_HTYPE_ETHERNET;
        m->hlen = 6;  
        m->hops = 0;
//        os_memcpy((char *) xid, (char *) m->xid, sizeof(m->xid));
        m->secs = 0;
        m->flags = htons(BOOTP_BROADCAST); 

        os_memcpy((char *) m->yiaddr, (char *) &client.addr, sizeof(m->yiaddr));

        os_memset((char *) m->ciaddr, 0, sizeof(m->ciaddr));
        os_memset((char *) m->siaddr, 0, sizeof(m->siaddr));
        os_memset((char *) m->giaddr, 0, sizeof(m->giaddr));
        os_memset((char *) m->sname, 0, sizeof(m->sname));
        os_memset((char *) m->file, 0, sizeof(m->file));

        os_memset((char *) m->options, 0, sizeof(m->options));

//For xiaomi crash bug
		uint32 magic_cookie1 = magic_cookie;
        os_memcpy((char *) m->options, &magic_cookie1, sizeof(magic_cookie1));
}
///////////////////////////////////////////////////////////////////////////////////
/*
 * ����һ��OFFER
 *
 * @param -- m ָ����Ҫ���͵�DHCP msg����
 */
///////////////////////////////////////////////////////////////////////////////////
static void ICACHE_FLASH_ATTR send_offer(struct dhcps_msg *m)
{
        uint8_t *end;
        struct pbuf *p, *q;
        u8_t *data;
        u16_t cnt=0;
        u16_t i;
        create_msg(m);

        end = add_msg_type(&m->options[4], DHCPOFFER);
        end = add_offer_options(end);
        end = add_end(end);

        p = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct dhcps_msg), PBUF_RAM);
#if DHCPS_DEBUG
        os_printf("udhcp: send_offer>>p->ref = %d\n", p->ref);
#endif
        if(p != NULL){

#if DHCPS_DEBUG
            os_printf("dhcps: send_offer>>pbuf_alloc succeed\n");
            os_printf("dhcps: send_offer>>p->tot_len = %d\n", p->tot_len);
            os_printf("dhcps: send_offer>>p->len = %d\n", p->len);
#endif
            q = p;
            while(q != NULL){
                data = (u8_t *)q->payload;
                for(i=0; i<q->len; i++)
                {
                    data[i] = ((u8_t *) m)[cnt++];
                }

                q = q->next;
            }
        }else{

#if DHCPS_DEBUG
            os_printf("dhcps: send_offer>>pbuf_alloc failed\n");
#endif
            return;
        }
        if (!lwiperr_check("send_offer", udp_sendto( pcb_dhcps, p, &broadcast_dhcps, DHCPS_CLIENT_PORT ))){
#if DHCPS_DEBUG
            os_printf("dhcps: send_offer>>udp_sendto\n");
#endif
	}
        if(p->ref != 0){
#if DHCPS_DEBUG
            os_printf("udhcp: send_offer>>free pbuf\n");
#endif
            pbuf_free(p);
        }
}
///////////////////////////////////////////////////////////////////////////////////
/*
 * ����һ��NAK��Ϣ
 *
 * @param m ָ����Ҫ���͵�DHCP msg����
 */
///////////////////////////////////////////////////////////////////////////////////
static void ICACHE_FLASH_ATTR send_nak(struct dhcps_msg *m)
{

        u8_t *end;
        struct pbuf *p, *q;
        u8_t *data;
        u16_t cnt=0;
        u16_t i;
        create_msg(m);

        end = add_msg_type(&m->options[4], DHCPNAK);
        end = add_end(end);

        p = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct dhcps_msg), PBUF_RAM);
#if DHCPS_DEBUG
        os_printf("udhcp: send_nak>>p->ref = %d\n", p->ref);
#endif
        if(p != NULL){

#if DHCPS_DEBUG
            os_printf("dhcps: send_nak>>pbuf_alloc succeed\n");
            os_printf("dhcps: send_nak>>p->tot_len = %d\n", p->tot_len);
            os_printf("dhcps: send_nak>>p->len = %d\n", p->len);
#endif
            q = p;
            while(q != NULL){
                data = (u8_t *)q->payload;
                for(i=0; i<q->len; i++)
                {
                    data[i] = ((u8_t *) m)[cnt++];
                }

                q = q->next;
            }
        }else{

#if DHCPS_DEBUG
            os_printf("dhcps: send_nak>>pbuf_alloc failed\n");
#endif
            return;
        }
        lwiperr_check("dhcps send nak", udp_sendto( pcb_dhcps, p, &broadcast_dhcps, DHCPS_CLIENT_PORT ));
        if(p->ref != 0){
#if DHCPS_DEBUG
            os_printf("udhcp: send_nak>>free pbuf\n");
#endif
            pbuf_free(p);
        }
}
///////////////////////////////////////////////////////////////////////////////////
/*
 * ����һ��ACK��DHCP�ͻ���
 *
 * @param m ָ����Ҫ���͵�DHCP msg����
 */
///////////////////////////////////////////////////////////////////////////////////
static void ICACHE_FLASH_ATTR send_ack(struct dhcps_msg *m)
{

        u8_t *end;
        struct pbuf *p, *q;
        u8_t *data;
        u16_t cnt=0;
        u16_t i;
        create_msg(m);

        end = add_msg_type(&m->options[4], DHCPACK);
        end = add_offer_options(end);
        end = add_end(end);

        p = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct dhcps_msg), PBUF_RAM);
#if DHCPS_DEBUG
        os_printf("udhcp: send_ack>>p->ref = %d\n", p->ref);
#endif
        if(p != NULL){

#if DHCPS_DEBUG
            os_printf("dhcps: send_ack>>pbuf_alloc succeed\n");
            os_printf("dhcps: send_ack>>p->tot_len = %d\n", p->tot_len);
            os_printf("dhcps: send_ack>>p->len = %d\n", p->len);
#endif
            q = p;
            while(q != NULL){
                data = (u8_t *)q->payload;
                for(i=0; i<q->len; i++)
                {
                    data[i] = ((u8_t *) m)[cnt++];
                }

                q = q->next;
            }
        }else{

#if DHCPS_DEBUG
            os_printf("dhcps: send_ack>>pbuf_alloc failed\n");
#endif
            return;
        }
        if (!lwiperr_check("dhcps send ack", udp_sendto( pcb_dhcps, p, &broadcast_dhcps, DHCPS_CLIENT_PORT ))){
#if DHCPS_DEBUG
            os_printf("dhcps: send_ack>>udp_sendto\n");
#endif
	}

        if(p->ref != 0){
#if DHCPS_DEBUG
            os_printf("udhcp: send_ack>>free pbuf\n");
#endif
            pbuf_free(p);
        }
}
///////////////////////////////////////////////////////////////////////////////////
/*
 * ����DHCP�ͻ��˷�����DHCP����������Ϣ�����Բ�ͬ��DHCP��������������Ӧ��Ӧ��
 *
 * @param optptr DHCP msg�е���������
 * @param len ��������Ĵ��?(byte)
 *
 * @return uint8_t ���ش�����DHCP Server״ֵ̬
 */
///////////////////////////////////////////////////////////////////////////////////
static uint8_t ICACHE_FLASH_ATTR parse_options(uint8_t *optptr, sint16_t len)
{
        struct ipv4_addr client;
        bool is_dhcp_parse_end = false;
        struct dhcps_state s;

        client.addr = client_address.addr;

        u8_t *end = optptr + len;
        u16_t type = 0;

        s.state = DHCPS_STATE_IDLE;

        while (optptr < end) {
#if DHCPS_DEBUG
            os_printf("dhcps: (sint16_t)*optptr = %d\n", (sint16_t)*optptr);
#endif
            switch ((sint16_t) *optptr) {

                case DHCP_OPTION_MSG_TYPE:  //53
                        type = *(optptr + 2);
                        break;

                case DHCP_OPTION_REQ_IPADDR://50
                        //os_printf("dhcps:0x%08x,0x%08x\n",client.addr,*(uint32*)(optptr+2));
                        if( os_memcmp( (char *) &client.addr, (char *) optptr+2,4)==0 ) {
#if DHCPS_DEBUG
                            os_printf("dhcps: DHCP_OPTION_REQ_IPADDR = 0 ok\n");
#endif
                            s.state = DHCPS_STATE_ACK;
                        }else {
#if DHCPS_DEBUG
                            os_printf("dhcps: DHCP_OPTION_REQ_IPADDR != 0 err\n");
#endif
                            s.state = DHCPS_STATE_NAK;
                        }
                        break;
                case DHCP_OPTION_END:
                        {
                            is_dhcp_parse_end = true;
                        }
                        break;
            }

            if(is_dhcp_parse_end){
                    break;
            }

            optptr += optptr[1] + 2;
        }

        switch (type){
            case DHCPDISCOVER://1
                s.state = DHCPS_STATE_OFFER;
#if DHCPS_DEBUG
                os_printf("dhcps: DHCPD_STATE_OFFER\n");
#endif
                break;

            case DHCPREQUEST://3
                if ( !(s.state == DHCPS_STATE_ACK || s.state == DHCPS_STATE_NAK) ) {
                    if(renew == true) {
                        s.state = DHCPS_STATE_ACK;
                    } else {
                        s.state = DHCPS_STATE_NAK;
                    }
#if DHCPS_DEBUG
                        os_printf("dhcps: DHCPD_STATE_NAK\n");
#endif
                }
                break;

            case DHCPDECLINE://4
                s.state = DHCPS_STATE_IDLE;
#if DHCPS_DEBUG
                os_printf("dhcps: DHCPD_STATE_IDLE\n");
#endif
                break;

            case DHCPRELEASE://7
                s.state = DHCPS_STATE_RELEASE;
#if DHCPS_DEBUG
                os_printf("dhcps: DHCPD_STATE_IDLE\n");
#endif
                break;
        }
#if DHCPS_DEBUG
        os_printf("dhcps: return s.state = %d\n", s.state);
#endif
        return s.state;
}
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
static sint16_t ICACHE_FLASH_ATTR parse_msg(struct dhcps_msg *m, u16_t len)
{
    if(os_memcmp((char *)m->options,
            &magic_cookie,
            sizeof(magic_cookie)) == 0){
        struct ipv4_addr ip;
        os_memcpy(&ip.addr,m->ciaddr,sizeof(ip.addr));
        client_address.addr = wifi_softap_dhcps_client_update(m->chaddr,&ip);

        sint16_t ret = parse_options(&m->options[4], len);

        if(ret == DHCPS_STATE_RELEASE) {
            wifi_softap_dhcps_client_leave(m->chaddr,&ip,TRUE); // force to delete
            client_address.addr = ip.addr;
        }

        return ret;
    }
    return 0;
}
///////////////////////////////////////////////////////////////////////////////////
/*
 * DHCP ��������ݰ���մ���ص�����˺�����LWIP UDPģ������ʱ������
 * ��Ҫ����udp_recv()������LWIP����ע��.
 *
 * @param arg
 * @param pcb ���յ�UDP��Ŀ��ƿ�?
 * @param p ���յ���UDP�е��������?
 * @param addr ���ʹ�UDP���Դ�����IP��ַ
 * @param port ���ʹ�UDP���Դ�����UDPͨ���˿ں�
 */
///////////////////////////////////////////////////////////////////////////////////
static void ICACHE_FLASH_ATTR handle_dhcp(void *arg, 
                                    struct udp_pcb *pcb,
                                    struct pbuf *p,
                                    const ip_addr_t *addr,
                                    uint16_t port)
{
	(void)arg;
	(void)pcb;
	(void)addr;
	(void)port;

        struct dhcps_msg *pmsg_dhcps = NULL;
        sint16_t tlen = 0;
        u16_t i = 0;
        u16_t dhcps_msg_cnt = 0;
        u8_t *p_dhcps_msg = NULL;
        u8_t *data = NULL;

#if DHCPS_DEBUG
        os_printf("dhcps: handle_dhcp-> receive a packet\n");
#endif
        if (p==NULL) return;

        pmsg_dhcps = (struct dhcps_msg *)os_zalloc(sizeof(struct dhcps_msg));
        if (NULL == pmsg_dhcps){
            pbuf_free(p);
            return;
        }
        p_dhcps_msg = (u8_t *)pmsg_dhcps;
        tlen = p->tot_len;
        data = (u8_t*)p->payload;

#if DHCPS_DEBUG
        os_printf("dhcps: handle_dhcp-> p->tot_len = %d\n", tlen);
        os_printf("dhcps: handle_dhcp-> p->len = %d\n", p->len);
#endif

        for(i=0; i<p->len; i++){
            p_dhcps_msg[dhcps_msg_cnt++] = data[i];
        }

        if(p->next != NULL) {
#if DHCPS_DEBUG
            os_printf("dhcps: handle_dhcp-> p->next != NULL\n");
            os_printf("dhcps: handle_dhcp-> p->next->tot_len = %d\n",p->next->tot_len);
            os_printf("dhcps: handle_dhcp-> p->next->len = %d\n",p->next->len);
#endif

            data = (u8_t*)p->next->payload;
            for(i=0; i<p->next->len; i++){
                p_dhcps_msg[dhcps_msg_cnt++] = data[i];
            }
        }

        /*
         * DHCP �ͻ���������Ϣ����
        */
#if DHCPS_DEBUG
        os_printf("dhcps: handle_dhcp-> parse_msg(p)\n");
#endif

        switch(parse_msg(pmsg_dhcps, tlen - 240)) {

            case DHCPS_STATE_OFFER://1
#if DHCPS_DEBUG            
                 os_printf("dhcps: handle_dhcp-> DHCPD_STATE_OFFER\n");
#endif
                 send_offer(pmsg_dhcps);
                 break;
            case DHCPS_STATE_ACK://3
#if DHCPS_DEBUG
                 os_printf("dhcps: handle_dhcp-> DHCPD_STATE_ACK\n");
#endif
                 send_ack(pmsg_dhcps);
                 wifi_softap_set_station_info(pmsg_dhcps->chaddr, &client_address);
                 break;
            case DHCPS_STATE_NAK://4
#if DHCPS_DEBUG            
                 os_printf("dhcps: handle_dhcp-> DHCPD_STATE_NAK\n");
#endif
                 send_nak(pmsg_dhcps);
                 break;
            default :
                 break;
        }
#if DHCPS_DEBUG
        os_printf("dhcps: handle_dhcp-> pbuf_free(p)\n");
#endif
        pbuf_free(p);
        os_free(pmsg_dhcps);
        pmsg_dhcps = NULL;
}
///////////////////////////////////////////////////////////////////////////////////
static void ICACHE_FLASH_ATTR wifi_softap_init_dhcps_lease(uint32 ip)
{
    uint32 softap_ip = 0,local_ip = 0;
    uint32 start_ip = 0;
    uint32 end_ip = 0;
//  if (dhcps_lease_flag) {
    if (dhcps_lease.enable == TRUE) {
        softap_ip = htonl(ip);
        start_ip = htonl(dhcps_lease.start_ip.addr);
        end_ip = htonl(dhcps_lease.end_ip.addr);
        /*config ip information can't contain local ip*/
        if ((start_ip <= softap_ip) && (softap_ip <= end_ip)) {
            dhcps_lease.enable = FALSE;
        } else {
            /*config ip information must be in the same segment as the local ip*/
            softap_ip >>= 8;
            if (((start_ip >> 8 != softap_ip) || (end_ip >> 8 != softap_ip))
                    || (end_ip - start_ip > DHCPS_MAX_LEASE)) {
                dhcps_lease.enable = FALSE;
            }
        }
    }

    if (dhcps_lease.enable == FALSE) {
        local_ip = softap_ip = htonl(ip);
        softap_ip &= 0xFFFFFF00;
        local_ip &= 0xFF;
        if (local_ip >= 0x80)
            local_ip -= DHCPS_MAX_LEASE;
        else
            local_ip ++;

        os_bzero(&dhcps_lease, sizeof(dhcps_lease));
        dhcps_lease.start_ip.addr = softap_ip | local_ip;
        dhcps_lease.end_ip.addr = softap_ip | (local_ip + DHCPS_MAX_LEASE - 1);
        dhcps_lease.start_ip.addr = htonl(dhcps_lease.start_ip.addr);
        dhcps_lease.end_ip.addr= htonl(dhcps_lease.end_ip.addr);
    }
//  dhcps_lease.start_ip.addr = htonl(dhcps_lease.start_ip.addr);
//  dhcps_lease.end_ip.addr= htonl(dhcps_lease.end_ip.addr);
//  os_printf("start_ip = 0x%x, end_ip = 0x%x\n",dhcps_lease.start_ip, dhcps_lease.end_ip);
}
///////////////////////////////////////////////////////////////////////////////////
void ICACHE_FLASH_ATTR dhcps_start(struct ip_info *info)
{
    // was: struct netif * apnetif = (struct netif *)eagle_lwip_getif(0x01);
    // but we are now in the lwip2 side so we deal with lwip2 structs
    // where netif->dhcps_pcb does not exist
    // we can use netif->state which is unused
    
    struct netif * apnetif = netif_ap;
    
    //uprint("dhcps_start: apnetif=%p state/dhcps_pcb=%p\n", apnetif, apnetif->state);
    
    //if(apnetif->dhcps_pcb != NULL) {
    //    udp_remove(apnetif->dhcps_pcb);
    //}
    if(apnetif->state != NULL) {
        udp_remove((struct udp_pcb*)apnetif->state);
    }

    pcb_dhcps = udp_new();
    if (pcb_dhcps == NULL || info ==NULL) {
        os_printf("dhcps_start(): could not obtain pcb\n");
    }

    //apnetif->dhcps_pcb = pcb_dhcps;
    apnetif->state = pcb_dhcps;

    
//  wrong: answer will go to sta  IP4_ADDR(&broadcast_dhcps, 255, 255, 255, 255);
//  good: going to ap IP4_ADDR(&broadcast_dhcps, 192, 168, 4, 255);
//  semi proper way:
    broadcast_dhcps = apnetif->ip_addr;
    ip_2_ip4(&broadcast_dhcps)->addr &= ip_2_ip4(&apnetif->netmask)->addr;
    ip_2_ip4(&broadcast_dhcps)->addr |= ~ip_2_ip4(&apnetif->netmask)->addr;
    //XXXFIXMEIPV6 broadcast address?

    server_address = info->ip;
    wifi_softap_init_dhcps_lease(server_address.addr);

    udp_bind(pcb_dhcps, IP_ADDR_ANY, DHCPS_SERVER_PORT);
    udp_recv(pcb_dhcps, handle_dhcp, NULL);
#if DHCPS_DEBUG
    os_printf("dhcps:dhcps_start->udp_recv function Set a receive callback handle_dhcp for UDP_PCB pcb_dhcps\n");
#endif

    wifi_set_ip_info(SOFTAP_IF, info); // added for lwip-git, not sure whether useful
    netif_ap->flags |= NETIF_FLAG_UP | NETIF_FLAG_LINK_UP; // added for lwip-git
    // useless and wrong. default should be sta. netif_set_default(netif_ap);
}

void ICACHE_FLASH_ATTR dhcps_stop(void)
{
    //struct netif * apnetif = (struct netif *)eagle_lwip_getif(0x01);
    struct netif * apnetif = netif_ap;

    udp_disconnect(pcb_dhcps);
//  dhcps_lease_flag = true;
    //if(apnetif->dhcps_pcb != NULL) {
    //    udp_remove(apnetif->dhcps_pcb);
    //    apnetif->dhcps_pcb = NULL;
    //}
    if(apnetif->state != NULL) {
        udp_remove((struct udp_pcb*)apnetif->state);
        apnetif->state = NULL;
    }

    //udp_remove(pcb_dhcps);
    list_node *pnode = NULL;
    list_node *pback_node = NULL;
    struct dhcps_pool* dhcp_node = NULL;
    struct ipv4_addr ip_zero;

    os_memset(&ip_zero,0x0,sizeof(ip_zero));
    pnode = plist;
    while (pnode != NULL) {
        pback_node = pnode;
        pnode = pback_node->pnext;
        node_remove_from_list(&plist, pback_node);
        dhcp_node = (struct dhcps_pool*)pback_node->pnode;
        //wifi_softap_dhcps_client_leave(dhcp_node->mac,&dhcp_node->ip,TRUE); // force to delete
        wifi_softap_set_station_info(dhcp_node->mac, &ip_zero);
        os_free(pback_node->pnode);
        pback_node->pnode = NULL;
        os_free(pback_node);
        pback_node = NULL;
    }
}

/******************************************************************************
 * FunctionName : wifi_softap_set_dhcps_lease
 * Description  : set the lease information of DHCP server
 * Parameters   : please -- Additional argument to set the lease information,
 *                          Little-Endian.
 * Returns      : true or false
*******************************************************************************/
bool ICACHE_FLASH_ATTR wifi_softap_set_dhcps_lease(struct dhcps_lease *please)
{
    struct ip_info info;
    uint32 softap_ip = 0;
    uint32 start_ip = 0;
    uint32 end_ip = 0;

    uint8 opmode = wifi_get_opmode();

    if (opmode == STATION_MODE || opmode == NULL_MODE) {
        return false;
    }

    if (please == NULL || wifi_softap_dhcps_status() == DHCP_STARTED)
        return false;

    if(please->enable) {
        os_bzero(&info, sizeof(struct ip_info));
        wifi_get_ip_info(SOFTAP_IF, &info);
        softap_ip = htonl(info.ip.addr);
        start_ip = htonl(please->start_ip.addr);
        end_ip = htonl(please->end_ip.addr);

        /*config ip information can't contain local ip*/
        if ((start_ip <= softap_ip) && (softap_ip <= end_ip))
            return false;

        /*config ip information must be in the same segment as the local ip*/
        softap_ip >>= 8;
        if ((start_ip >> 8 != softap_ip)
                || (end_ip >> 8 != softap_ip)) {
            return false;
        }

        if (end_ip - start_ip > DHCPS_MAX_LEASE)
            return false;

        os_bzero(&dhcps_lease, sizeof(dhcps_lease));
//      dhcps_lease.start_ip.addr = start_ip;
//      dhcps_lease.end_ip.addr = end_ip;
        dhcps_lease.start_ip.addr = please->start_ip.addr;
        dhcps_lease.end_ip.addr = please->end_ip.addr;
    }
    dhcps_lease.enable = please->enable;
//  dhcps_lease_flag = false;
    return true;
}

/******************************************************************************
 * FunctionName : wifi_softap_get_dhcps_lease
 * Description  : get the lease information of DHCP server
 * Parameters   : please -- Additional argument to get the lease information,
 *                          Little-Endian.
 * Returns      : true or false
*******************************************************************************/
bool ICACHE_FLASH_ATTR wifi_softap_get_dhcps_lease(struct dhcps_lease *please)
{
    uint8 opmode = wifi_get_opmode();

    if (opmode == STATION_MODE || opmode == NULL_MODE) {
        return false;
    }

    if (NULL == please)
        return false;

//  if (dhcps_lease_flag){
    if (dhcps_lease.enable == FALSE){
        if (wifi_softap_dhcps_status() == DHCP_STOPPED)
            return false;
    } else {
//      os_bzero(please, sizeof(dhcps_lease));
//      if (wifi_softap_dhcps_status() == DHCP_STOPPED){
//          please->start_ip.addr = htonl(dhcps_lease.start_ip.addr);
//          please->end_ip.addr = htonl(dhcps_lease.end_ip.addr);
//      }
    }

//  if (wifi_softap_dhcps_status() == DHCP_STARTED){
//      os_bzero(please, sizeof(dhcps_lease));
//      please->start_ip.addr = dhcps_lease.start_ip.addr;
//      please->end_ip.addr = dhcps_lease.end_ip.addr;
//  }
    please->start_ip.addr = dhcps_lease.start_ip.addr;
    please->end_ip.addr = dhcps_lease.end_ip.addr;
    return true;
}

static void ICACHE_FLASH_ATTR kill_oldest_dhcps_pool(void)
{
    list_node *pre = NULL, *p = NULL;
    list_node *minpre = NULL, *minp = NULL;
    struct dhcps_pool *pdhcps_pool = NULL, *pmin_pool = NULL;
    pre = plist;
    p = pre->pnext;
    minpre = pre;
    minp = p;
    while (p != NULL){
        pdhcps_pool = (struct dhcps_pool*)p->pnode;
        pmin_pool = (struct dhcps_pool*)minp->pnode;
        if (pdhcps_pool->lease_timer < pmin_pool->lease_timer){
            minp = p;
            minpre = pre;
        }
        pre = p;
        p = p->pnext;
    }
    minpre->pnext = minp->pnext;pdhcps_pool->state = DHCPS_STATE_OFFLINE;
    os_free(minp->pnode);
    minp->pnode = NULL;
    os_free(minp);
    minp = NULL;
}

void ICACHE_FLASH_ATTR dhcps_coarse_tmr(void)
{
    uint8 num_dhcps_pool = 0;
    list_node *pback_node = NULL;
    list_node *pnode = NULL;
    struct dhcps_pool *pdhcps_pool = NULL;
    pnode = plist;
    while (pnode != NULL) {
        pdhcps_pool = (struct dhcps_pool*)pnode->pnode;
        if ( pdhcps_pool->type == DHCPS_TYPE_DYNAMIC) {
            pdhcps_pool->lease_timer --;
        }
        if (pdhcps_pool->lease_timer == 0){
            pback_node = pnode;
            pnode = pback_node->pnext;
            node_remove_from_list(&plist,pback_node);
            os_free(pback_node->pnode);
            pback_node->pnode = NULL;
            os_free(pback_node);
            pback_node = NULL;
        } else {
            pnode = pnode ->pnext;
            num_dhcps_pool ++;
        }
    }

    if (num_dhcps_pool >= MAX_STATION_NUM)
        kill_oldest_dhcps_pool();
}

bool ICACHE_FLASH_ATTR wifi_softap_set_dhcps_offer_option(uint8 level, void* optarg)
{
    bool offer_flag = true;
    //uint8 option = 0;
    if (optarg == NULL && wifi_softap_dhcps_status() == false)
        return false;

    if (level <= OFFER_START || level >= OFFER_END)
        return false;

    switch (level){
        case OFFER_ROUTER:
            offer = (*(uint8 *)optarg) & 0x01;
            offer_flag = true;
            break;
        default :
            offer_flag = false;
            break;
    }
    return offer_flag;
}

bool ICACHE_FLASH_ATTR wifi_softap_set_dhcps_lease_time(uint32 minute)
{
    uint8 opmode = wifi_get_opmode();

    if (opmode == STATION_MODE || opmode == NULL_MODE) {
        return false;
    }

    if (wifi_softap_dhcps_status() == DHCP_STARTED) {
        return false;
    }

    if(minute == 0) {
        return false;
    }
    dhcps_lease_time = minute;
    return true;
}

bool ICACHE_FLASH_ATTR wifi_softap_reset_dhcps_lease_time(void)
{
    uint8 opmode = wifi_get_opmode();

    if (opmode == STATION_MODE || opmode == NULL_MODE) {
        return false;
    }

    if (wifi_softap_dhcps_status() == DHCP_STARTED) {
        return false;
    }
    dhcps_lease_time = DHCPS_LEASE_TIME_DEF;
    return true;
}

uint32 ICACHE_FLASH_ATTR wifi_softap_get_dhcps_lease_time(void) // minute
{
    return dhcps_lease_time;
}

void ICACHE_FLASH_ATTR wifi_softap_dhcps_client_leave(u8 *bssid, struct ipv4_addr *ip,bool force)
{
    struct dhcps_pool *pdhcps_pool = NULL;
    list_node *pback_node = NULL;

    if ((bssid == NULL) || (ip == NULL)) {
        return;
    }

    for (pback_node = plist; pback_node != NULL;pback_node = pback_node->pnext) {
        pdhcps_pool = (struct dhcps_pool*)pback_node->pnode;
        if (os_memcmp(pdhcps_pool->mac, bssid, sizeof(pdhcps_pool->mac)) == 0){
            if (os_memcmp(&pdhcps_pool->ip.addr, &ip->addr, sizeof(pdhcps_pool->ip.addr)) == 0) {
                if ((pdhcps_pool->type == DHCPS_TYPE_STATIC) || (force)) {
                    if(pback_node != NULL) {
                        node_remove_from_list(&plist,pback_node);
                        os_free(pback_node);
                        pback_node = NULL;
                    }

                    if (pdhcps_pool != NULL) {
                        os_free(pdhcps_pool);
                        pdhcps_pool = NULL;
                    }
                } else {
                    pdhcps_pool->state = DHCPS_STATE_OFFLINE;
                }

                struct ipv4_addr ip_zero;
                os_memset(&ip_zero,0x0,sizeof(ip_zero));
                wifi_softap_set_station_info(bssid, &ip_zero);
                break;
            }
        }
    }
}

uint32 ICACHE_FLASH_ATTR wifi_softap_dhcps_client_update(u8 *bssid, struct ipv4_addr *ip)
{
    struct dhcps_pool *pdhcps_pool = NULL;
    list_node *pback_node = NULL;
    list_node *pmac_node = NULL;
    list_node *pip_node = NULL;
    bool flag = FALSE;
    uint32 start_ip = dhcps_lease.start_ip.addr;
    uint32 end_ip = dhcps_lease.end_ip.addr;
    dhcps_type_t type = DHCPS_TYPE_DYNAMIC;
    if (bssid == NULL) {
        return IPADDR_ANY;
    }

    if (ip) {
        if (IPADDR_BROADCAST == ip->addr) {
            return IPADDR_ANY;
        } else if (IPADDR_ANY == ip->addr) {
            ip = NULL;
        } else {
            type = DHCPS_TYPE_STATIC;
        }
    }

    renew = FALSE;
    for (pback_node = plist; pback_node != NULL;pback_node = pback_node->pnext) {
        pdhcps_pool = (struct dhcps_pool*)pback_node->pnode;
        //os_printf("mac:"MACSTR"bssid:"MACSTR"\r\n",MAC2STR(pdhcps_pool->mac),MAC2STR(bssid));
        if (os_memcmp(pdhcps_pool->mac, bssid, sizeof(pdhcps_pool->mac)) == 0){
            pmac_node = pback_node;
            if (ip == NULL) {
                flag = TRUE;
                break;
            }
        }
        if (ip != NULL) {
            if (os_memcmp(&pdhcps_pool->ip.addr, &ip->addr, sizeof(pdhcps_pool->ip.addr)) == 0) {
                pip_node = pback_node;
            }
        } else if (flag == FALSE){
            if (os_memcmp(&pdhcps_pool->ip.addr, &start_ip, sizeof(pdhcps_pool->ip.addr)) != 0) {
                flag = TRUE;
            } else {
                start_ip = htonl((ntohl(start_ip) + 1));
            }
        }
    }

    if ((ip == NULL) && (flag == FALSE)) {
        if (plist == NULL) {
            if (start_ip <= end_ip) {
                flag = TRUE;
            } else {
                return IPADDR_ANY;
            }
        } else {
            if (start_ip > end_ip) {
                return IPADDR_ANY;
            }
            //start_ip = htonl((ntohl(start_ip) + 1));
            flag = TRUE;
        }
    }

    if (pmac_node != NULL) { // update new ip
        if (pip_node != NULL){
            pdhcps_pool = (struct dhcps_pool*)pip_node->pnode;

            if (pip_node != pmac_node) {
                if(pdhcps_pool->state != DHCPS_STATE_OFFLINE) { // ip is used
                    return IPADDR_ANY;
                }

                // mac exists and ip exists in other node,delete mac
                node_remove_from_list(&plist,pmac_node);
                os_free(pmac_node->pnode);
                pmac_node->pnode = NULL;
                os_free(pmac_node);
                pmac_node = pip_node;
                os_memcpy(pdhcps_pool->mac, bssid, sizeof(pdhcps_pool->mac));
            } else {
                renew = true;
                type = DHCPS_TYPE_DYNAMIC;
            }

            pdhcps_pool->lease_timer = DHCPS_LEASE_TIMER;
            pdhcps_pool->type = type;
            pdhcps_pool->state = DHCPS_STATE_ONLINE;

        } else {
            pdhcps_pool = (struct dhcps_pool*)pmac_node->pnode;
            if (ip != NULL) {
                pdhcps_pool->ip.addr = ip->addr;
            } else if (flag == TRUE) {
                pdhcps_pool->ip.addr = start_ip;
            } else {    // no ip to distribute
                return IPADDR_ANY;
            }

            node_remove_from_list(&plist,pmac_node);
            pdhcps_pool->lease_timer = DHCPS_LEASE_TIMER;
            pdhcps_pool->type = type;
            pdhcps_pool->state = DHCPS_STATE_ONLINE;
            node_insert_to_list(&plist,pmac_node);
        }
    } else { // new station
        if (pip_node != NULL) { // maybe ip has used
            pdhcps_pool = (struct dhcps_pool*)pip_node->pnode;
            if (pdhcps_pool->state != DHCPS_STATE_OFFLINE) {
                return IPADDR_ANY;
            }
            os_memcpy(pdhcps_pool->mac, bssid, sizeof(pdhcps_pool->mac));
            pdhcps_pool->lease_timer = DHCPS_LEASE_TIMER;
            pdhcps_pool->type = type;
            pdhcps_pool->state = DHCPS_STATE_ONLINE;
        } else {
            pdhcps_pool = (struct dhcps_pool *)os_zalloc(sizeof(struct dhcps_pool));
            if (ip != NULL) {
                pdhcps_pool->ip.addr = ip->addr;
            } else if (flag == TRUE) {
                pdhcps_pool->ip.addr = start_ip;
            } else {    // no ip to distribute
                os_free(pdhcps_pool);
                return IPADDR_ANY;
            }
            if (pdhcps_pool->ip.addr > end_ip) {
                os_free(pdhcps_pool);
                return IPADDR_ANY;
            }
            os_memcpy(pdhcps_pool->mac, bssid, sizeof(pdhcps_pool->mac));
            pdhcps_pool->lease_timer = DHCPS_LEASE_TIMER;
            pdhcps_pool->type = type;
            pdhcps_pool->state = DHCPS_STATE_ONLINE;
            pback_node = (list_node *)os_zalloc(sizeof(list_node ));
            pback_node->pnode = pdhcps_pool;
            pback_node->pnext = NULL;
            node_insert_to_list(&plist,pback_node);
        }
    }

    return pdhcps_pool->ip.addr;
}
