/*********************************************************************************
 * PROJECT: MiMic
 * --------------------------------------------------------------------------------
 *
 * This file is part of MiMic
 * Copyright (C)2011-2013 Ryo Iizuka
 *
 * MiMic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by　the Free Software Foundation, either version 3 of the　License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * For further information please contact.
 *  http://nyatla.jp/
 *  <airmail(at)ebony.plala.or.jp> or <nyatla(at)nyatla.jp>
 *
 *********************************************************************************/
#include "NyLPC_cDhcpClient.h"
#include "../NyLPC_cNet.h"
#include <stdio.h>
#include <string.h>

struct NyLPC_TDhcpHeader
{
    NyLPC_TUInt8 op;
    NyLPC_TUInt8 htype;
    NyLPC_TUInt8 hlen;
    NyLPC_TUInt8 hops;
    NyLPC_TUInt32 xid;
    NyLPC_TUInt16 secs;
    NyLPC_TUInt16 flags;
    NyLPC_TUInt32 ciaddr;
    NyLPC_TUInt32 yiaddr;
    NyLPC_TUInt32 siaddr;
    NyLPC_TUInt32 giaddr;
    struct{
        struct NyLPC_TEthAddr emac;
        NyLPC_TChar padding[10];
    }chaddr;
    NyLPC_TChar sname[64];
    NyLPC_TChar file[128];
}PACK_STRUCT_END;

#define NyLPC_TDhcpHeader_BOOTREQUEST 1
#define NyLPC_TDhcpHeader_BOOTREPLY   2

#define DHCP_OPT_ID_ROUTER 3
#define DHCP_OPT_ID_SERVER_ID 54
#define DHCP_OPT_ID_NETMASK 1
#define DHCP_OPT_ID_MESSAGETYPE 53



/**
 * DHCPパケットから32bit値を読み出す。
 * @return
 * ネットワークオーダー
 */
static NyLPC_TBool getUInt32Option(const NyLPC_TUInt8* i_buf,NyLPC_TUInt16 len,NyLPC_TUInt8 i_id,NyLPC_TUInt32* o_v)
{
    const NyLPC_TUInt8* p=i_buf+sizeof(struct NyLPC_TDhcpHeader)+4;
    while(*p!=0x00 && p<(i_buf+len-5)){
        if(*p==i_id){
            if(*(p+1)==4){
                *o_v=*((NyLPC_TUInt32*)(p+2));
                return NyLPC_TBool_TRUE;
            }
        }else{
            p+=(*(p+1))+2;
        }
    }
    return NyLPC_TBool_FALSE;
}
static NyLPC_TBool getUInt8Option(const NyLPC_TUInt8* i_buf,NyLPC_TUInt16 len,NyLPC_TUInt8 i_id,NyLPC_TUInt8* o_v)
{
    const NyLPC_TUInt8* p=i_buf+sizeof(struct NyLPC_TDhcpHeader)+4;
    while(*p!=0x00 && p<(i_buf+len-5)){
        if(*p==i_id){
            if(*(p+1)==1){
                *o_v=*(p+2);
                return NyLPC_TBool_TRUE;
            }
        }else{
            p+=(*(p+1))+2;
        }
    }
    return NyLPC_TBool_FALSE;
}
static NyLPC_TBool NyLPC_TDhcpHeader_parseDHCPOFFER(const NyLPC_TUInt8* i_buf,NyLPC_TUInt16 i_len,NyLPC_TUInt32 i_xid,NyLPC_TcDhcpClient_t* i_inst)
{
    struct NyLPC_TDhcpHeader* p=(struct NyLPC_TDhcpHeader*)i_buf;
    //XIDのチェック
    if(p->xid!=NyLPC_HTONL(i_xid)){
        return NyLPC_TBool_FALSE;
    }
    //OFFERのclient IPアドレスをresultへ保存情報の保存
    i_inst->_result->ip_addr.v=p->yiaddr;
    //SERVER IDを保存
    if(!getUInt32Option(i_buf,i_len,DHCP_OPT_ID_SERVER_ID,&i_inst->_offerserver.v)){
        return NyLPC_TBool_FALSE;
    }
    return NyLPC_TBool_TRUE;
}

static NyLPC_TBool NyLPC_TDhcpHeader_parseDHCPACK(const NyLPC_TUInt8* i_buf,NyLPC_TUInt16 i_len,NyLPC_TUInt32 i_xid,NyLPC_TcIPv4Config_t* result)
{
    struct NyLPC_TDhcpHeader* p=(struct NyLPC_TDhcpHeader*)i_buf;
    //XIDのチェック
    if(p->xid!=NyLPC_HTONL(i_xid)){
        return NyLPC_TBool_FALSE;
    }
    if(!getUInt32Option(i_buf,i_len,DHCP_OPT_ID_ROUTER,&result->dr_addr.v)){
        result->dr_addr=NyLPC_TIPv4Addr_ZERO;
    }
    if(!getUInt32Option(i_buf,i_len,DHCP_OPT_ID_NETMASK,&result->netmask.v)){
        result->netmask=NyLPC_TIPv4Addr_ZERO;
    }
    result->ip_addr.v=p->yiaddr;
    return NyLPC_TBool_TRUE;
}

static void NyLPC_TDhcpHeader_setDHCPDISCOVER(char* i_buf,NyLPC_TUInt32 i_xid,const struct NyLPC_TEthAddr* emac,NyLPC_TUInt16* o_len)
{
    struct NyLPC_TDhcpHeader* p=(struct NyLPC_TDhcpHeader*)i_buf;
    memset(i_buf,0,sizeof(struct NyLPC_TDhcpHeader));
    p->op=NyLPC_TDhcpHeader_BOOTREQUEST;
    p->htype=1;
    p->hlen=6;
    p->xid=NyLPC_HTONL(i_xid);
    p->chaddr.emac=*emac;
    p->flags=NyLPC_HTONS(0x8000);
    memcpy(i_buf+sizeof(struct NyLPC_TDhcpHeader),
        "\x63\x82\x53\x63"      //4
        "\x35\x01\x01"          //3 MESSAGE TYPE
        "\x37\x03\x01\x03\x06"  //5 REQUEST LIST(1,3,6)
        "\x3d\x07\x01\x00\x00\x00\x00\x00\x00" //9 CLIENT INDIFIRE
        "\xff",4+3+5+9+1);
    //emacの上書き
    memcpy((i_buf+sizeof(struct NyLPC_TDhcpHeader)+4+3+5+3),emac->addr,6);
    //送信するパケットの長さ
    *o_len=sizeof(struct NyLPC_TDhcpHeader)+4+3+5+9+1;
    return;
}
static void NyLPC_TDhcpHeader_setDHCPREQUEST(char* i_buf,NyLPC_TUInt32 i_xid,const struct NyLPC_TIPv4Addr* i_sid,const struct NyLPC_TIPv4Addr* i_reqid,const struct NyLPC_TEthAddr* emac,NyLPC_TUInt16* o_len)
{
    struct NyLPC_TDhcpHeader* p=(struct NyLPC_TDhcpHeader*)i_buf;
    memset(i_buf,0,sizeof(struct NyLPC_TDhcpHeader));
    p->op=NyLPC_TDhcpHeader_BOOTREQUEST;
    p->htype=1;
    p->hlen=6;
    p->xid=NyLPC_HTONL(i_xid);
    p->chaddr.emac=*emac;
    p->flags=NyLPC_HTONS(0x8000);
    memcpy(i_buf+sizeof(struct NyLPC_TDhcpHeader),
        "\x63\x82\x53\x63"      //4
        "\x35\x01\x03"          //3 MESSAGE TYPE
        "\x37\x03\x01\x03\x06"  //5 REQUEST LIST(1,3,6)
        "\x3d\x07\x01\x00\x00\x00\x00\x00\x00" //9 CLIENT INDIFIRE
        "\x36\x04\x00\x00\x00\x00" // 6 SERVER ID
        "\x32\x04\x00\x00\x00\x00" // 6 Reqested IP
        "\xff",4+3+5+9+6+6+1);
    //emacの上書き
    memcpy((i_buf+sizeof(struct NyLPC_TDhcpHeader)+4+3+5+3),emac->addr,6);
    //sidの上書き
    memcpy((i_buf+sizeof(struct NyLPC_TDhcpHeader)+4+3+5+9+2),i_sid,4);
    //reqidの上書き
    memcpy((i_buf+sizeof(struct NyLPC_TDhcpHeader)+4+3+5+9+6+2),i_reqid,4);
    //送信するパケットの長さ
    *o_len=sizeof(struct NyLPC_TDhcpHeader)+4+3+5+9+6+6+1;
    return;
}



#define TcDhcpSock_ST_WAIT_OFFER    1
#define TcDhcpSock_ST_WAIT_OFFER_OK 2
#define TcDhcpSock_ST_WAIT_ACK 3
#define TcDhcpSock_ST_WAIT_ACK_OK 4
#define TcDhcpSock_ST_DONE_NG 3
#define TcDhcpSock_ST_DONE_OK 4




#define DHCP_OPT_ID_MESSAGETYPE_ACK   5
#define DHCP_OPT_ID_MESSAGETYPE_OFFER 2

static NyLPC_TBool onPacket(NyLPC_TiUdpSocket_t* i_inst,const void* i_buf,const struct NyLPC_TIPv4RxInfo* i_info);

/**
 * DHCPソケットを作成します。
 */
NyLPC_TBool NyLPC_cDhcpClient_initialize(NyLPC_TcDhcpClient_t* i_inst)
{
    i_inst->_socket=NyLPC_cNet_createUdpSocketEx(68,NyLPC_TSocketType_UDP_NOBUF);
    if(i_inst->_socket==NULL){
        return NyLPC_TBool_FALSE;
    }
    i_inst->_socket->_tag=i_inst;
    NyLPC_iUdpSocket_setBroadcast(i_inst->_socket);
    NyLPC_iUdpSocket_setOnRxHandler(i_inst->_socket,onPacket);
    return NyLPC_TBool_TRUE;
}
void NyLPC_cDhcpClient_finalize(NyLPC_TcDhcpClient_t* i_inst)
{
    NyLPC_iUdpSocket_finalize(i_inst->_socket);
}
#define TIMEOUT_SOCKAPI_MS 1000
#define TIMEOUT_RECVMSG_MS 3000

/**
 * ネットワークを更新します。
 * emac/default_mssを設定したネットワークが必要です。
 */
static NyLPC_TBool NyLPC_cDhcpClient_dhcpRequest(NyLPC_TcDhcpClient_t* i_inst,NyLPC_TcIPv4Config_t* i_result)
{
    char* buf;
    NyLPC_TcStopwatch_t sw;
    NyLPC_TUInt16 s;
    NyLPC_TInt16 hint=sizeof(struct NyLPC_TDhcpHeader)+128;
    i_inst->txid+=(*(NyLPC_TUInt16*)(&(i_result->eth_mac.addr[2])))+(*(NyLPC_TUInt16*)(&(i_result->eth_mac.addr[4])));
    i_inst->_result=i_result;
    buf=NyLPC_iUdpSocket_allocSendBuf(i_inst->_socket,hint,&s,TIMEOUT_SOCKAPI_MS);
    if(buf==NULL || s<hint){
        return NyLPC_TBool_FALSE;
    }
    NyLPC_TDhcpHeader_setDHCPDISCOVER(buf,i_inst->txid,&i_inst->_result->eth_mac,&s);
    i_inst->_status=TcDhcpSock_ST_WAIT_OFFER;
    if(!NyLPC_iUdpSocket_psend(i_inst->_socket,&NyLPC_TIPv4Addr_BROADCAST,67,buf,s)){
        NyLPC_iUdpSocket_releaseSendBuf(i_inst->_socket,buf);
        return NyLPC_TBool_FALSE;
    }
    NyLPC_cStopwatch_initialize(&sw);
    NyLPC_cStopwatch_startExpire(&sw,TIMEOUT_RECVMSG_MS);
    while(i_inst->_status==TcDhcpSock_ST_WAIT_OFFER){
        if(NyLPC_cStopwatch_isExpired(&sw)){
            return NyLPC_TBool_FALSE;
        }
    }
    //レスポンスのチェック
    if(i_inst->_status!=TcDhcpSock_ST_WAIT_OFFER_OK)
    {
        return NyLPC_TBool_FALSE;
    }
    buf=NyLPC_iUdpSocket_allocSendBuf(i_inst->_socket,hint,&s,TIMEOUT_SOCKAPI_MS);
    if(buf==NULL || s<hint){
        return NyLPC_TBool_FALSE;
    }
    NyLPC_TDhcpHeader_setDHCPREQUEST(buf,i_inst->txid,&(i_inst->_offerserver),&(i_inst->_result->ip_addr),&i_inst->_result->eth_mac,&s);
    i_inst->_status=TcDhcpSock_ST_WAIT_ACK;
    if(!NyLPC_iUdpSocket_psend(i_inst->_socket,&NyLPC_TIPv4Addr_BROADCAST,67,buf,s)){
        NyLPC_iUdpSocket_releaseSendBuf(i_inst->_socket,buf);
        return NyLPC_TBool_FALSE;
    }
    NyLPC_cStopwatch_startExpire(&sw,TIMEOUT_RECVMSG_MS);
    while(i_inst->_status==TcDhcpSock_ST_WAIT_ACK){
        if(NyLPC_cStopwatch_isExpired(&sw)){
            return NyLPC_TBool_FALSE;
        }
    }
    //レスポンスのチェック
    if(i_inst->_status!=TcDhcpSock_ST_WAIT_ACK_OK)
    {
        return NyLPC_TBool_FALSE;
    }
    return NyLPC_TBool_TRUE;
}

/**
 * NyLPC_TcIPv4Config_tをDHCPで更新します。
 * この関数をコールする時は、サービスは停止中でなければなりません。
 * @param i_cfg
 * 更新するi_cfg構造体。
 * emac,default_mssは設定済である必要があります。他のフィールド値は不定で構いません。
 * 更新されるフィールドは、ip,netmast,default_rootの3つです。
 * @return
 * 更新に成功した場合TRUE
 */
NyLPC_TBool NyLPC_cDhcpClient_requestAddr(NyLPC_TcDhcpClient_t* i_inst,NyLPC_TcIPv4Config_t* i_cfg,NyLPC_TInt16 i_repeat)
{
    NyLPC_TInt16 i;
    NyLPC_TBool ret=NyLPC_TBool_FALSE;
    NyLPC_TcIPv4Config_t c2;
    //工場出荷時設定でリセットしてIPを0に
    NyLPC_cIPv4Config_initialzeCopy(&c2,i_cfg);
    NyLPC_cIPv4Config_setIp(&c2,&NyLPC_TIPv4Addr_ZERO,&NyLPC_TIPv4Addr_ZERO);
    NyLPC_cIPv4Config_setDefaultRoute(&c2,&NyLPC_TIPv4Addr_ZERO);
    //netを開始
    NyLPC_cNet_start(&c2);
    for(i=i_repeat-1;i>=0;i--){
        ret=NyLPC_cDhcpClient_dhcpRequest(i_inst,i_cfg);
        if(ret){
            break;
        }
    }
    NyLPC_cNet_stop();
    NyLPC_cIPv4Config_finalize(&c2);
    return ret;
}



static NyLPC_TBool onPacket(NyLPC_TiUdpSocket_t* i_inst,const void* i_buf,const struct NyLPC_TIPv4RxInfo* i_info)
{
    NyLPC_TUInt8 mt;//message type
    NyLPC_TcDhcpClient_t* inst=(NyLPC_TcDhcpClient_t*)i_inst->_tag;
    struct NyLPC_TDhcpHeader* dnsh=(struct NyLPC_TDhcpHeader*)i_buf;
    if(i_info->size<sizeof(struct NyLPC_TDhcpHeader)+1){
        return NyLPC_TBool_FALSE;//DROP
    }
    switch(inst->_status)
    {
    case TcDhcpSock_ST_WAIT_ACK:
        if(dnsh->op!=NyLPC_TDhcpHeader_BOOTREPLY){
            return NyLPC_TBool_FALSE;
            }
        if(!getUInt8Option(i_buf,i_info->size,DHCP_OPT_ID_MESSAGETYPE,&mt)){
            return NyLPC_TBool_FALSE;
        }
        if(mt!=DHCP_OPT_ID_MESSAGETYPE_ACK){
            return NyLPC_TBool_FALSE;
        }
        if(!NyLPC_TDhcpHeader_parseDHCPACK(i_buf,i_info->size,inst->txid,inst->_result)){
            return NyLPC_TBool_FALSE;
        }
        inst->_status=TcDhcpSock_ST_WAIT_ACK_OK;
        break;
    case TcDhcpSock_ST_WAIT_OFFER:
        if(dnsh->op!=NyLPC_TDhcpHeader_BOOTREPLY){
            return NyLPC_TBool_FALSE;
            }
        if(!getUInt8Option(i_buf,i_info->size,DHCP_OPT_ID_MESSAGETYPE,&mt)){
            return NyLPC_TBool_FALSE;
        }
        if(mt!=DHCP_OPT_ID_MESSAGETYPE_OFFER){
            return NyLPC_TBool_FALSE;
        }
        if(!NyLPC_TDhcpHeader_parseDHCPOFFER(i_buf,i_info->size,inst->txid,inst)){
            return NyLPC_TBool_FALSE;
        }
        inst->_status=TcDhcpSock_ST_WAIT_OFFER_OK;
        break;
    }
    return NyLPC_TBool_FALSE;

}
