//
// Created by dongwenzhe on 2022/10/9.
//
#include <string>
#include <map>
using namespace std;

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <memory.h>
#include <cstdlib>
#include "opendhcpd.h"
#include "s2j/cJSON.h"
#include "misc.h"
#include <net/if_arp.h>
#include <uv.h>
#include "config.h"
#include "proto.h"
#include "user_errno.h"
#include "uthash/uthash.h"
#include "sds/sds.h"
#include "inet_misc.h"
#include "http_svr.h"

extern data2   cfig;
extern bool    kRunning;
extern dhcpMap dhcpCache;
extern time_t  t;
extern data71  lump;

typedef struct {
    char           iptvMAC[64];
    unsigned int   vni;
    int            isReport;
    UT_hash_handle hh;
} IPTV_DEV_SET, *PIPTV_DEV_SET;

static PIPTV_DEV_SET g_iptvNewDevs   = nullptr;
static PIPTV_DEV_SET g_iptvCacheDevs = nullptr;
static uv_rwlock_t   g_uvCacheLock;

static int dhcp_get_user_info(const char **pRsp, const char *pRequest) {
    char              logBuff[512];
    const char       *pStrContent;
    int               k;
    int               errCode;
    dhcpMap::iterator p;

    if (pRequest == nullptr || strlen(pRequest) == 0) {
        sprintf(logBuff, "Requeset Json");
        logDHCPMess(logBuff, 1);
        return ERR_INPUT_PARAMS;
    }

    pStrContent = proto_decode_context(pRequest, nullptr, nullptr, &errCode);

    if (pStrContent == nullptr) {
        sprintf(logBuff, "Requeset Json error %s", pRequest);
        logDHCPMess(logBuff, 1);
        return ERR_PROTO_DECODE;
    }

#ifdef JSON_SCHEMA_ON
    if (errCode == ERR_JSON_VALID_SCH) {
        *pRsp = pStrContent;
        return ERR_SUCCESS;
    }
#endif

    cJSON *pRoot = cJSON_Parse(pStrContent);
    free((void *)pStrContent);

    if (!pRoot) {
        return ERR_JSON_PRASE_OBJ;
    }

    cJSON *pUserMac = cJSON_GetObjectItem(pRoot, "userMac");

    if (!pUserMac) {
        cJSON_Delete(pRoot);
        return ERR_JSON_PRASE_OBJ;
    }

    cJSON *pRspMsg   = cJSON_CreateObject();
    cJSON *pMsgArray = cJSON_CreateArray();

    cJSON_AddItemToObject(pRspMsg, "userInfo", pMsgArray);

    k = cJSON_GetArraySize(pUserMac);

    // 传入数据 userMac 为空数组时,返回所有用户数据
    if (k == 0) {
        for (p = dhcpCache.begin(); p != dhcpCache.end(); p++) {
            char   tempbuff[512];
            data7 *dhcpEntry = p->second;

            if (!dhcpEntry) {
                continue;
            }

            cJSON *pRspItem = cJSON_CreateObject();
            cJSON_AddStringToObject(pRspItem, "userMac", dhcpEntry->mapname);

            cJSON_AddStringToObject(pRspItem, "ip", IP2String(tempbuff, dhcpEntry->ip));
            cJSON_AddStringToObject(pRspItem, "hostname", dhcpEntry->hostname);
            if (dhcpEntry->display && dhcpEntry->expiry >= t) {
                tm *ttm = localtime(&dhcpEntry->expiry);
                strftime(tempbuff, sizeof(tempbuff), "%d-%b-%y %X", ttm);
                cJSON_AddStringToObject(pRspItem, "leaseExpiry", tempbuff);
            } else {
                cJSON_AddStringToObject(pRspItem, "leaseExpiry", "Expiry");
            }
            cJSON_AddNumberToObject(pRspItem, "status", ERR_SUCCESS);
            cJSON_AddStringToObject(pRspItem, "message", getErrorEnumDesc(ERR_SUCCESS));

            cJSON_AddItemToArray(pMsgArray, pRspItem);
        }
    } else {
        for (int i = 0; i < k; i++) {
            char   tempbuff[512];
            cJSON *pItem    = cJSON_GetArrayItem(pUserMac, i);
            cJSON *pRspItem = cJSON_CreateObject();

            p = dhcpCache.find(pItem->valuestring);
            cJSON_AddStringToObject(pRspItem, "userMac", pItem->valuestring);

            if (p != dhcpCache.end()) {
                data7 *dhcpEntry = p->second;

                cJSON_AddStringToObject(pRspItem, "ip", IP2String(tempbuff, dhcpEntry->ip));
                cJSON_AddStringToObject(pRspItem, "hostname", dhcpEntry->hostname);
                if (dhcpEntry->display && dhcpEntry->expiry >= t) {
                    tm *ttm = localtime(&dhcpEntry->expiry);
                    strftime(tempbuff, sizeof(tempbuff), "%d-%b-%y %X", ttm);
                    cJSON_AddStringToObject(pRspItem, "leaseExpiry", tempbuff);
                } else {
                    cJSON_AddStringToObject(pRspItem, "leaseExpiry", "Expiry");
                }
                cJSON_AddNumberToObject(pRspItem, "status", ERR_SUCCESS);
                cJSON_AddStringToObject(pRspItem, "message", getErrorEnumDesc(ERR_SUCCESS));
            } else {
                cJSON_AddNumberToObject(pRspItem, "status", ERR_ITEM_UNEXISTS);
                cJSON_AddStringToObject(pRspItem, "message", getErrorEnumDesc(ERR_ITEM_UNEXISTS));
            }

            cJSON_AddItemToArray(pMsgArray, pRspItem);
        }
    }

    *pRsp = proto_create_new(pRspMsg, 200);
    cJSON_Delete(pRoot);
    return ERR_SUCCESS;
}

static int dchp_get_all_iptv_devs(const char **pRsp) {
    char          logBuff[512];
    PIPTV_DEV_SET pDev, pTemp;

    cJSON *pRspMsg   = cJSON_CreateObject();
    cJSON *pMsgArray = cJSON_CreateArray();

    cJSON_AddItemToObject(pRspMsg, "iptvDevs", pMsgArray);

    uv_rwlock_wrlock(&g_uvCacheLock);
    HASH_ITER(hh, g_iptvCacheDevs, pDev, pTemp) {
        cJSON *pRspItem = cJSON_CreateObject();
        cJSON_AddStringToObject(pRspItem, "mac", pDev->iptvMAC);
        cJSON_AddNumberToObject(pRspItem, "vni", pDev->vni);
        cJSON_AddBoolToObject(pRspItem, "reported", pDev->isReport);
        cJSON_AddItemToArray(pMsgArray, pRspItem);
    }

    HASH_ITER(hh, g_iptvNewDevs, pDev, pTemp) {
        cJSON *pRspItem = cJSON_CreateObject();
        cJSON_AddStringToObject(pRspItem, "mac", pDev->iptvMAC);
        cJSON_AddNumberToObject(pRspItem, "vni", pDev->vni);
        cJSON_AddBoolToObject(pRspItem, "reported", pDev->isReport);
        cJSON_AddItemToArray(pMsgArray, pRspItem);
    }
    uv_rwlock_wrunlock(&g_uvCacheLock);

    cJSON_AddNumberToObject(pRspMsg, "status", ERR_SUCCESS);
    cJSON_AddStringToObject(pRspMsg, "message", getErrorEnumDesc(ERR_SUCCESS));

    *pRsp = proto_create_new(pRspMsg, 200);
    return ERR_SUCCESS;
}

static int dhcp_get_all_user(const char **pRsp) {
    char              logBuff[512];
    data7            *dhcpEntry;
    dhcpMap::iterator p;

    cJSON *pRspMsg   = cJSON_CreateObject();
    cJSON *pMsgArray = cJSON_CreateArray();

    cJSON_AddItemToObject(pRspMsg, "users", pMsgArray);

    for (p = dhcpCache.begin(); kRunning && p != dhcpCache.end(); p++) {
        if ((dhcpEntry = p->second)) {
            cJSON_AddStringToObject(pMsgArray, "", dhcpEntry->mapname);
        }
    }

    cJSON_AddNumberToObject(pRspMsg, "status", ERR_SUCCESS);
    cJSON_AddStringToObject(pRspMsg, "message", getErrorEnumDesc(ERR_SUCCESS));

    *pRsp = proto_create_new(pRspMsg, 200);

    return ERR_SUCCESS;
}

#define VALUE_TO_DHCP_TLV(buf, val, tag)                      \
    do {                                                      \
        int valSize;                                          \
        int numbytes = myTokenize((buf), (val), "/,.", true); \
        if (numbytes <= 255) {                                \
            char *ptr = (buf);                                \
            valSize   = 0;                                    \
            for (; *ptr; ptr = myGetToken(ptr, 1)) {          \
                if (isInt(ptr)) {                             \
                    hoption[valSize] = STR2INT(ptr);          \
                    valSize++;                                \
                } else {                                      \
                    break;                                    \
                }                                             \
            }                                                 \
            memcpy((val), hoption, valSize);                  \
            if (buffSize > valSize + 2) {                     \
                *dp = (tag);                                  \
                dp++;                                         \
                *dp = valSize;                                \
                dp++;                                         \
                memcpy(dp, (val), valSize);                   \
                dp       += valSize;                          \
                buffSize -= (valSize + 2);                    \
            }                                                 \
        }                                                     \
    } while (0)

static int dhcp_add_rangeset_to_options(POBJ_DHCP_RNG pRange) {
    int     ret;
    char    buff[1024];
    MYBYTE  hoption[256];
    MYBYTE *dp;
    MYWORD  buffSize   = sizeof(dhcp_packet) - sizeof(dhcp_header);
    char    value[256] = {0};
    MYBYTE *options    = nullptr;
    data20  optionData {};

    if (!pRange) {
        return -ERR_INPUT_PARAMS;
    }

    memset(&optionData, 0, sizeof(data20));

    optionData.rangeSetInd                = cfig.rangeCount;
    cfig.rangeSet[cfig.rangeCount].active = true;

    dp  = optionData.options;
    *dp = 0;
    dp++;

    //dhcp_range
    ret = addDHCPRange(pRange->rangAddr);
    if (ret != ERR_SUCCESS) {
        return ret;
    }

    if (strlen(pRange->subnet) != 0) {
        strcpy(value, pRange->subnet);
        VALUE_TO_DHCP_TLV(buff, value, DHCP_OPTION_NETMASK);
        optionData.mask = (*((MYDWORD *)value));
    }

    if (strlen(pRange->dnsSvr) != 0) {
        strcpy(value, pRange->dnsSvr);
        VALUE_TO_DHCP_TLV(buff, value, DHCP_OPTION_DNS);
    }

    if (strlen(pRange->gateway) != 0) {
        strcpy(value, pRange->gateway);
        VALUE_TO_DHCP_TLV(buff, value, DHCP_OPTION_ROUTER);
    }

    //lease
    if (pRange->lease != 0) {
        MYDWORD j;
        j = pRange->lease;
        if (buffSize > 6) {
            *dp = DHCP_OPTION_IPADDRLEASE;
            dp++;
            *dp = 4;
            dp++;
            dp += pUInt(dp, j);
        }
    }

    *dp = DHCP_OPTION_END;
    dp++;
    optionData.optionSize = (dp - optionData.options);

    if (optionData.optionSize > 3) {
        options = (MYBYTE *)calloc(1, optionData.optionSize);
        memcpy(options, optionData.options, optionData.optionSize);
    }

    cfig.dhcpRanges[cfig.rangeCount].rangeSetInd = optionData.rangeSetInd;
    cfig.dhcpRanges[cfig.rangeCount].options     = options;
    cfig.dhcpRanges[cfig.rangeCount].mask        = optionData.mask;
    if (!cfig.dhcpRanges[cfig.rangeCount].mask) {
        cfig.dhcpRanges[cfig.rangeCount].mask = cfig.mask;
    }
    cfig.rangeCount = (char)(cfig.rangeCount + 1);

    return ERR_SUCCESS;
}

static int add_dhcpd_rangeset(const char **pRsp, const char *pRequest) {
    char         logBuff[512];
    const char  *pStrContent;
    OBJ_DHCP_RNG range;
    cJSON       *pRspRoot;
    cJSON       *pExpandArray;
    int          errCode;

    if (pRequest == nullptr || strlen(pRequest) == 0) {
        sprintf(logBuff, "Requeset Json");
        logDHCPMess(logBuff, 1);
        return ERR_INPUT_PARAMS;
    }

    pStrContent = proto_decode_context(pRequest, nullptr, nullptr, &errCode);

    if (pStrContent == nullptr) {
        sprintf(logBuff, "Requeset Json error %s", pRequest);
        logDHCPMess(logBuff, 1);
        return ERR_PROTO_DECODE;
    }
    
#ifdef JSON_SCHEMA_ON
    if (errCode == ERR_JSON_VALID_SCH) {
        *pRsp = pStrContent;
        return ERR_SUCCESS;
    }
#endif

    cJSON *pRoot = cJSON_Parse(pStrContent);
    free((void *)pStrContent);

    if (!pRoot) {
        return ERR_JSON_PRASE_OBJ;
    }

    cJSON *prange_set = cJSON_GetObjectItem(pRoot, "rangeSet");

    pRspRoot     = cJSON_CreateObject();
    pExpandArray = cJSON_CreateArray();
    cJSON_AddItemToObject(pRspRoot, "rangeSet", pExpandArray);

    for (int i = 0; i < cJSON_GetArraySize(prange_set); i++) {
        int    ret;
        cJSON *pItem       = cJSON_GetArrayItem(prange_set, i);
        cJSON *pdhcp_range = cJSON_GetObjectItem(pItem, "dhcpRange");
        cJSON *pEx_range   = cJSON_CreateObject();

        if (!pdhcp_range) {
            continue;
        }

        cJSON_AddStringToObject(pEx_range, "dhcpRange", pdhcp_range->valuestring);

        cJSON *psubnet_mask   = cJSON_GetObjectItem(pItem, "netmask");
        cJSON *pdomain_server = cJSON_GetObjectItem(pItem, "domainServer");
        cJSON *pgateway       = cJSON_GetObjectItem(pItem, "gateway");
        cJSON *please_time    = cJSON_GetObjectItem(pItem, "leaseTime");

        memset(&range, 0, sizeof(OBJ_DHCP_RNG));

        strcpy(range.rangAddr, pdhcp_range->valuestring);
        if (psubnet_mask) {
            strcpy(range.subnet, psubnet_mask->valuestring);
        }

        if (pdomain_server) {
            strcpy(range.dnsSvr, pdomain_server->valuestring);
        }

        if (pgateway) {
            strcpy(range.gateway, pgateway->valuestring);
        }

        if (please_time) {
            range.lease = please_time->valueint;
        }

        //写入cfig
        ret = dhcp_add_rangeset_to_options(&range);
        if (ret != ERR_SUCCESS) {
            cJSON_AddNumberToObject(pEx_range, "status", ret);
            cJSON_AddStringToObject(pEx_range, "message", getErrorEnumDesc(ret));
            cJSON_AddItemToArray(pExpandArray, pEx_range);
            continue;
        }

        cJSON_AddNumberToObject(pEx_range, "status", ERR_SUCCESS);
        cJSON_AddStringToObject(pEx_range, "message", getErrorEnumDesc(ERR_SUCCESS));
        cJSON_AddItemToArray(pExpandArray, pEx_range);
    }

    *pRsp = proto_create_new(pRspRoot, 200);
    cJSON_Delete(pRoot);

    return ERR_SUCCESS;
}

typedef struct {
    unsigned int   key;
    unsigned int   value;
    UT_hash_handle hh;
} HASH_MAP, *PHASH_MAP;

static int delete_dhcpd_rangeset(const char **pRsp, const char *pRequest) {
    char        logBuff[512];
    const char *pStrContent;
    char       *fp;
    cJSON      *pRspRoot;
    cJSON      *pdelArray;
    data13      dhcpRanges[MAX_DHCP_RANGES];
    PHASH_MAP   delMap   = nullptr;
    int         resCount = 0;
    int         errCode;

    if (pRequest == nullptr || strlen(pRequest) == 0) {
        sprintf(logBuff, "Requeset Json");
        logDHCPMess(logBuff, 1);
        return ERR_INPUT_PARAMS;
    }

    pStrContent = proto_decode_context(pRequest, nullptr, nullptr, &errCode);
    if (pStrContent == nullptr) {
        sprintf(logBuff, "Requeset Json error %s", pRequest);
        logDHCPMess(logBuff, 1);
        return ERR_PROTO_DECODE;
    }

#ifdef JSON_SCHEMA_ON
    if (errCode == ERR_JSON_VALID_SCH) {
        *pRsp = pStrContent;
        return ERR_SUCCESS;
    }
#endif

    cJSON *pRoot = cJSON_Parse(pStrContent);
    free((void *)pStrContent);
    if (!pRoot) {
        return ERR_JSON_PRASE_OBJ;
    }

    cJSON *pdhcp_range = cJSON_GetObjectItem(pRoot, "dhcpRange");

    pRspRoot  = cJSON_CreateObject();
    pdelArray = cJSON_CreateArray();
    cJSON_AddItemToObject(pRspRoot, "rangeSet", pdelArray);

    for (int i = 0; i < cJSON_GetArraySize(pdhcp_range); i++) {
        cJSON *pdel_Item = cJSON_CreateObject();
        cJSON *pdelRange = cJSON_GetArrayItem(pdhcp_range, i);
        char   del_range[256];

        if (!pdelRange) {
            continue;
        }

        strcpy(del_range, pdelRange->valuestring);

        MYDWORD st_addr;
        MYDWORD en_addr;
        char    start[128];
        char    end[128];
        mySplit(start, end, del_range, '-');
        st_addr = htonl(inet_addr(start));
        en_addr = htonl(inet_addr(end));

        if (st_addr > en_addr) {
            cJSON_AddStringToObject(pdel_Item, "dhcpRange", del_range);
            cJSON_AddNumberToObject(pdel_Item, "status", ERR_INPUT_PARAMS);
            cJSON_AddStringToObject(pdel_Item, "message", getErrorEnumDesc(ERR_INPUT_PARAMS));
            cJSON_AddItemToArray(pdelArray, pdel_Item);
            break;
        } else {
            cJSON_Delete(pdel_Item);
        }

        PHASH_MAP s;
        HASH_FIND_INT(delMap, &st_addr, s);
        if (s == nullptr) {
            s = (PHASH_MAP)malloc(sizeof(HASH_MAP));
            s->key   = st_addr;
            s->value = en_addr;
            HASH_ADD_INT(delMap, key, s);
        }
    }

    for (int i = 0; i < cfig.rangeCount; i++) {
        cJSON    *pdel_Item = cJSON_CreateObject();
        PHASH_MAP delRange;
        char      del_range[256];
        char      saddr[128];
        char      eaddr[128];
        memset(del_range, 0, 256);

        HASH_FIND_INT(delMap, &cfig.dhcpRanges[i].rangeStart, delRange);
        if (delRange != nullptr) {
            IP2String(saddr, ntohl(delRange->key));
            IP2String(eaddr, ntohl(delRange->value));

            sprintf(del_range, "%s-%s", saddr, eaddr);
            cJSON_AddStringToObject(pdel_Item, "dhcpRange", del_range);
            //Determine whether the input is correct or not
            if (delRange->value == cfig.dhcpRanges[i].rangeEnd) {
                cJSON_AddNumberToObject(pdel_Item, "status", ERR_SUCCESS);
                cJSON_AddStringToObject(pdel_Item, "message", getErrorEnumDesc(ERR_SUCCESS));
                cJSON_AddItemToArray(pdelArray, pdel_Item);

            } else {
                memcpy(&dhcpRanges[resCount], &cfig.dhcpRanges[i], sizeof(struct data13));
                resCount++;
                cJSON_AddNumberToObject(pdel_Item, "status", ERR_INPUT_PARAMS);
                cJSON_AddStringToObject(pdel_Item, "message", getErrorEnumDesc(ERR_INPUT_PARAMS));
                cJSON_AddItemToArray(pdelArray, pdel_Item);
            }

            HASH_DEL(delMap, delRange);
            free(delRange);

            free(cfig.dhcpRanges[i].options);
            free(cfig.dhcpRanges[i].expiry);
            free(cfig.dhcpRanges[i].dhcpEntry);
            free(*(cfig.dhcpRanges[i].dhcpEntry));
        } else {
            memcpy(&dhcpRanges[resCount], &cfig.dhcpRanges[i], sizeof(struct data13));
            resCount++;

            cJSON_Delete(pdel_Item);
        }
    }
    //The input parameter does not exist
    do {
        PHASH_MAP s;
        PHASH_MAP tmp;
        char      saddr[128];
        char      eaddr[128];
        char      del_range[256];
        memset(del_range, 0, 256);

        HASH_ITER(hh, delMap, s, tmp) {
            cJSON *pdel_Item = cJSON_CreateObject();
            IP2String(saddr, ntohl(s->key));
            IP2String(eaddr, ntohl(s->value));

            sprintf(del_range, "%s-%s", saddr, eaddr);
            cJSON_AddStringToObject(pdel_Item, "dhcpRange", del_range);

            cJSON_AddNumberToObject(pdel_Item, "status", ERR_ITEM_UNEXISTS);
            cJSON_AddStringToObject(pdel_Item, "message", getErrorEnumDesc(ERR_ITEM_UNEXISTS));
            cJSON_AddItemToArray(pdelArray, pdel_Item);

            HASH_DEL(delMap, s);
            free(s);
        }
    } while (0);

    //Rewrite cfig.dhcpRanges
    for (int i = 0; i < cfig.rangeCount; i++) {
        if (i < resCount) {
            memcpy(&cfig.dhcpRanges[i], &dhcpRanges[i], sizeof(struct data13));
        } else {
            memset(&cfig.dhcpRanges[i], 0, sizeof(struct data13));
        }
    }

    cfig.rangeCount = (char)resCount;

    *pRsp = proto_create_new(pRspRoot, 200);
    cJSON_Delete(pRoot);

    return ERR_SUCCESS;
}

static int query_dhcpd_rangeset(const char **pRsp) {
    char logBuff[512];

    cJSON *pRspMsg   = cJSON_CreateObject();
    cJSON *pMsgArray = cJSON_CreateArray();
    cJSON_AddItemToObject(pRspMsg, "rangeSet", pMsgArray);

    for (char rangeInd = 0; rangeInd < cfig.rangeCount; rangeInd++) {
        char         addrStart[64];
        char         addrEnd[64];
        char         rangeSet[128];
        char         rangeMask[128];
        char         domainServer[128];
        char         gateway[128];
        cJSON       *pRangeItem = cJSON_CreateObject();
        unsigned int lease;

        memset(domainServer, 0, 128);
        IP2String(addrStart, ntohl(cfig.dhcpRanges[rangeInd].rangeStart));
        IP2String(addrEnd, ntohl(cfig.dhcpRanges[rangeInd].rangeEnd));
        IP2String(rangeMask, cfig.dhcpRanges[rangeInd].mask);

        sprintf(rangeSet, "%s-%s", addrStart, addrEnd);
        cJSON_AddStringToObject(pRangeItem, "dhcpRange", rangeSet);
        cJSON_AddStringToObject(pRangeItem, "netmask", rangeMask);

        MYBYTE *opPointer = cfig.dhcpRanges[rangeInd].options;
        data3   op {};
        if (opPointer) {
            opPointer++;
            while (*opPointer && *opPointer != DHCP_OPTION_END) {
                unsigned int  tmpVal = 0;
                unsigned char dnsSize;
                unsigned char offset = 0;
                char          dns_op[64];

                op.opt_code = *opPointer;
                opPointer++;
                op.size = *opPointer;
                opPointer++;

                memcpy(op.value, opPointer, op.size);
                if (op.opt_code == DHCP_OPTION_DNS) {
                    dnsSize = op.size;
                    do {
                        tmpVal = fIP(op.value + offset);
                        IP2String(dns_op, htonl(ntohl(tmpVal)));
                        sprintf(domainServer, "%s%s", domainServer, dns_op);
                        if (dnsSize != 4) {
                            sprintf(domainServer, "%s,", domainServer);
                        }
                        dnsSize -= 4;
                        offset  = op.size - dnsSize;
                    } while (dnsSize != 0);

                    cJSON_AddStringToObject(pRangeItem, "domainServer", domainServer);
                } else if (op.opt_code == DHCP_OPTION_ROUTER) {
                    tmpVal = fIP(op.value);
                    IP2String(gateway, htonl(ntohl(tmpVal)));

                    cJSON_AddStringToObject(pRangeItem, "gateway", gateway);
                } else if (op.opt_code == DHCP_OPTION_IPADDRLEASE) {
                    lease = fUInt(op.value);

                    cJSON_AddNumberToObject(pRangeItem, "leaseTime", lease);
                }
                opPointer += op.size;
            }
        }

        cJSON_AddItemToArray(pMsgArray, pRangeItem);
    }
    cJSON_AddNumberToObject(pRspMsg, "status", ERR_SUCCESS);
    cJSON_AddStringToObject(pRspMsg, "message", getErrorEnumDesc(ERR_SUCCESS));

    *pRsp = proto_create_new(pRspMsg, 200);

    return ERR_SUCCESS;
}

#pragma clang diagnostic push
#pragma ide diagnostic   ignored "cert-err34-c"

int getHwAddr(char *buff, char *mac) {
    if (buff == nullptr || mac == nullptr) {
        return -1;
    }

    int          i;
    unsigned int p[6];

    if (sscanf(mac, "%x:%x:%x:%x:%x:%x", &p[0], &p[1], &p[2], &p[3], &p[4], &p[5]) < 6) {
        return -1;
    }

    for (i = 0; i < 6; i++) {
        buff[i] = (char)p[i];
    }

    return 0;
}

#pragma clang diagnostic pop

int arpSet(const char *ifname, char *ipStr, char *mac) {
    if (ifname == nullptr || ipStr == nullptr || mac == nullptr) {
        LOG_MOD(error, ZLOG_MOD_OPENDHCPD, "para is null.\n");
        return -1;
    }

    struct arpreq       req {};
    struct sockaddr_in *sin;
    int                 ret;
    int                 sock_fd;

    memset(&req, 0, sizeof(struct arpreq));
    sin                  = (struct sockaddr_in *)&req.arp_pa;
    sin->sin_family      = AF_INET;
    sin->sin_addr.s_addr = inet_addr(ipStr);
    //arp_dev长度为[16],注意越界
    strncpy(req.arp_dev, ifname, 15);
    req.arp_flags = ATF_PERM | ATF_COM;

    if (getHwAddr((char *)req.arp_ha.sa_data, mac) < 0) {
        LOG_MOD(error, ZLOG_MOD_OPENDHCPD, "get mac error.\n");
        return -1;
    }

    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        LOG_MOD(error, ZLOG_MOD_OPENDHCPD, "get socket error.\n");
        return -1;
    }

    ret = ioctl(sock_fd, SIOCSARP, &req);
    if (ret < 0) {
        LOG_MOD(error, ZLOG_MOD_OPENDHCPD, "ioctl error.\n");
        close(sock_fd);
        return -1;
    }

    close(sock_fd);
    return 0;
}

sockaddr_in get_cliAddr(char *nicif, char *tempbuff, data9 *req) {
    arpSet(nicif, IP2String(tempbuff, req->dhcpp.header.bp_yiaddr), req->chaddr);

    sockaddr_in cliAddr {};
    memcpy(&cliAddr, &req->remote, sizeof(sockaddr_in));
    cliAddr.sin_addr.s_addr = inet_addr(IP2String(tempbuff, req->dhcpp.header.bp_yiaddr));
    return cliAddr;
}

unsigned int opendhcp_set_lease_time() {
    return config_get_dhcp_server_lease_time();
}

/**
 * 添加配置文件监听接口
 * @return
 */
int opendhcp_add_listener() {
    int      i;
    c_vector listen_ip = config_get_dhcp_listen_on();

    for (i = 0; listen_ip && i < vect_size(listen_ip); i++) {
        const char *pIp = (const char *)vect_get_at(listen_ip, i);
        if (pIp && strlen(pIp) > 0) {
            MYDWORD addr = inet_addr(pIp);
            addServer(cfig.specifiedServers, MAX_SERVERS, addr);
        }
    }

    return i;
}

static void on_http_response_cb(void        *pData,
                                unsigned int size,
                                const char  *pReqUrl,
                                const char  *pDlPath,
                                const char  *pTaskUuid,
                                int          iFinished,
                                void        *pUserData) {
    if (iFinished == 0) {
        LOG_MOD(debug, ZLOG_MOD_OPENDHCPD, "Request(%s): [%s] Response: [%u] OK:\n", pTaskUuid, pReqUrl, size);
    } else if (iFinished == 1) {
        LOG_MOD(error, ZLOG_MOD_OPENDHCPD, "Request(%s): [%s] Response: [%u] Error\n", pTaskUuid, pReqUrl, size);
    } else {
        LOG_MOD(error, ZLOG_MOD_OPENDHCPD, "Download Error Code: %d\n", iFinished);
    }

    free(pUserData);
}

void iptvCacheCb(void *UNUSED(pArg)) {
    do {
        bool          isReport = false;
        const char   *pUrl     = config_get_agent_iptv_report_url();
        PIPTV_DEV_SET report   = nullptr;
        PIPTV_DEV_SET pDev, pTmp = nullptr;
        uv_rwlock_wrlock(&g_uvCacheLock);
        HASH_ITER(hh, g_iptvNewDevs, pDev, pTmp) {
            PIPTV_DEV_SET pTemp;
            const char   *pDevMac = strdup(pDev->iptvMAC);
            HASH_FIND_STR(g_iptvCacheDevs, pDevMac, pTemp);
            // 新发现设备没有被上报过
            if (!pTemp) {
                auto pCacheDev = (PIPTV_DEV_SET)malloc(sizeof(IPTV_DEV_SET));
                memcpy(pCacheDev, pDev, sizeof(IPTV_DEV_SET));
                HASH_ADD_STR(report, iptvMAC, pCacheDev);

                isReport = true;
                // 添加到缓存列表供后续查询
                HASH_ADD_STR(g_iptvCacheDevs, iptvMAC, pCacheDev);
                LOG_MOD(debug,
                        ZLOG_MOD_OPENDHCPD,
                        "Add IPTV device %s vni %d to cache\n",
                        pCacheDev->iptvMAC,
                        pCacheDev->vni);
            }

            HASH_DEL(g_iptvNewDevs, pDev);
            free(pDev);
            free((void *)pDevMac);
        }
        uv_rwlock_wrunlock(&g_uvCacheLock);

        if (isReport && pUrl && strlen(pUrl) > 0) {
            cJSON *pRspMsg   = cJSON_CreateObject();
            cJSON *pMsgArray = cJSON_CreateArray();
            cJSON_AddItemToObject(pRspMsg, "iptvDevs", pMsgArray);

            HASH_ITER(hh, report, pDev, pTmp) {
                cJSON *pRspItem = cJSON_CreateObject();
                cJSON_AddStringToObject(pRspItem, "mac", pDev->iptvMAC);
                cJSON_AddNumberToObject(pRspItem, "vni", pDev->vni);
                pDev->isReport = 1;
                cJSON_AddItemToArray(pMsgArray, pRspItem);
            }

            const char *pStrPro = proto_create_new(pRspMsg, 200);

            // Report new IPTV device MAC
            inet_http_post_async(pUrl, pStrPro, on_http_response_cb, (void *)pStrPro);
        }

        uv_sleep(10);
    } while (true);
}

/**
 * 增加 DHCP Server HTTP服务接口
 */
#ifdef HTTPSERVER_ON
#define HTTP_HEAD_CONTENT_TYPE_JSON "Content-Type: application/json\r\n"
#define HTTP_HEAD_CONTENT_TYPE_HTML "Content-Type: text/html\r\n"

static void on_get_userinfo(struct mg_http_message *h, void *user_data, PHTTP_RSP_CTX pCtx) {
    pCtx->pRspHeads = HTTP_HEAD_CONTENT_TYPE_JSON;

    pCtx->errCode = dhcp_get_user_info((const char **)&pCtx->pRspData, h->body.ptr);
    if (pCtx->errCode != ERR_SUCCESS) {
        pCtx->httpCode = 500;
        free(pCtx->pRspData);
        return;
    }

    pCtx->httpCode = 200;
}

static void on_del_rangeset(struct mg_http_message *h, void *user_data, PHTTP_RSP_CTX pCtx) {
    pCtx->pRspHeads = HTTP_HEAD_CONTENT_TYPE_JSON;

    pCtx->errCode = delete_dhcpd_rangeset((const char **)&pCtx->pRspData, h->body.ptr);
    if (pCtx->errCode != ERR_SUCCESS) {
        pCtx->httpCode = 500;
        free(pCtx->pRspData);
        return;
    }

    pCtx->httpCode = 200;
}

static void on_add_rangeset(struct mg_http_message *h, void *user_data, PHTTP_RSP_CTX pCtx) {
    pCtx->pRspHeads = HTTP_HEAD_CONTENT_TYPE_JSON;

    pCtx->errCode = add_dhcpd_rangeset((const char **)&pCtx->pRspData, h->body.ptr);
    if (pCtx->errCode != ERR_SUCCESS) {
        pCtx->httpCode = 500;
        free(pCtx->pRspData);
        return;
    }

    pCtx->httpCode = 200;
}

static void on_get_rangeset(struct mg_http_message *h, void *user_data, PHTTP_RSP_CTX pCtx) {
    pCtx->pRspHeads = HTTP_HEAD_CONTENT_TYPE_JSON;

    pCtx->errCode = query_dhcpd_rangeset((const char **)&pCtx->pRspData);
    if (pCtx->errCode != ERR_SUCCESS) {
        pCtx->httpCode = 500;
        free(pCtx->pRspData);
        return;
    }

    pCtx->httpCode = 200;
}

static void on_get_iptv_devs(struct mg_http_message *h, void *user_data, PHTTP_RSP_CTX pCtx) {
    pCtx->pRspHeads = HTTP_HEAD_CONTENT_TYPE_JSON;

    pCtx->errCode = dchp_get_all_iptv_devs((const char **)&pCtx->pRspData);

    if (pCtx->errCode != ERR_SUCCESS) {
        pCtx->httpCode = 500;
        free(pCtx->pRspData);
        return;
    }

    pCtx->httpCode = 200;
}

static void on_dhcpd_info(struct mg_http_message *h, void *user_data, PHTTP_RSP_CTX pCtx) {
    pCtx->pRspHeads = HTTP_HEAD_CONTENT_TYPE_HTML;

    pCtx->errCode = prepareUserHtmlRespStatus((const char **)&pCtx->pRspData);

    if (pCtx->errCode != ERR_SUCCESS) {
        pCtx->httpCode = 500;
        free(pCtx->pRspData);
        return;
    }

    pCtx->httpCode = 200;
}

static void on_get_alluser(struct mg_http_message *h, void *user_data, PHTTP_RSP_CTX pCtx) {
    pCtx->pRspHeads = HTTP_HEAD_CONTENT_TYPE_JSON;

    pCtx->errCode = dhcp_get_all_user((const char **)&pCtx->pRspData);
    if (pCtx->errCode != ERR_SUCCESS) {
        pCtx->httpCode = 500;
        free(pCtx->pRspData);
        return;
    }

    pCtx->httpCode = 200;
}

typedef struct HTTP_ROUTE_INFO {
    char                routeName[MAX_URI];
    HTTP_METHOD         method;
    HTTP_ROUTE_PRIORITY priority;
    HTTP_REQUEST_CB     cb;
    void               *pUserData;
} HTTP_ROUTE_INFO, *PHTTP_ROUTE_INFO;

static HTTP_ROUTE_INFO g_routeTable[] = {
    {"/",                      GET,  PRI_LOWEASE, on_dhcpd_info,    nullptr},
    {"/dhcp/info/getuser",     POST, PRI_NORMAL,  on_get_userinfo,  nullptr},
    {"/dhcp/info/allusers",    GET,  PRI_NORMAL,  on_get_alluser,   nullptr},
    {"/dhcp/config/rangeset",  POST, PRI_NORMAL,  on_add_rangeset,  nullptr},
    {"/dhcp/delete/rangeset",  POST, PRI_NORMAL,  on_del_rangeset,  nullptr},
    {"/dhcp/query/rangeset",   GET,  PRI_NORMAL,  on_get_rangeset,  nullptr},
    {"/dhcp/query/iptvdevice", GET,  PRI_NORMAL,  on_get_iptv_devs, nullptr},
};

void opendhcp_init_http_server() {
    static int         added = FALSE;
    static uv_thread_t uvThread;

    if (!added) {
        uv_rwlock_init(&g_uvCacheLock);
        uv_thread_create(&uvThread, iptvCacheCb, nullptr);
        for (auto &i : g_routeTable) {
            PHTTP_ROUTE_INFO p = &i;
            http_add_route(p->routeName, p->method, p->priority, p->cb, p->pUserData);
        }
        added = TRUE;
#ifdef USERVNI_ON
        LOG_MOD(info, ZLOG_MOD_OPENDHCPD, "User VxLan Id: [%d]\n", cfg_get_user_vni_id());
#endif
    }
}
#endif
/**
 * 增加 DHCP 主、从服务器配置
 */
void opendhcp_set_replication_svr() {
    c_vector replication = config_get_dhcp_replication_svr();

    if (replication && vect_size(replication) == 2) {
        cfig.zoneServers[0] = inet_addr((const char *)vect_get_at(replication, 0));
        cfig.zoneServers[1] = inet_addr((const char *)vect_get_at(replication, 1));
    }
}

/**
 *  添加配置文件中的 DHCP 地址池池配置到服务中
 */
void opendhcp_add_ip_pool_set() {
    c_vector pool = config_get_dhcp_server_range_set();

    for (int i = 0; (pool && i < vect_size(pool)); i++) {
        auto pRange = (POBJ_DHCP_RNG)vect_get_at(pool, i);

        if (pRange) {
            if (dhcp_add_rangeset_to_options(pRange) != ERR_SUCCESS) {
                LOG_MOD(error, ZLOG_MOD_OPENDHCPD, "Add rangeset(\"%s\") failed!\n", pRange->rangAddr);
            }
        }
    }
}

int process_iptv_multicast(const unsigned char *p, int size, const char *mac) {
    char ipTvId[16] = {0};
    memcpy(ipTvId, &p[4], 10);

    if (strcmp(ipTvId, "JSCMCC-OTT") == 0) {
        PIPTV_DEV_SET pTmp;
        HASH_FIND_STR(g_iptvNewDevs, mac, pTmp);
        if (!pTmp) {
            auto pDev = (PIPTV_DEV_SET)malloc(sizeof(IPTV_DEV_SET));

            if (pDev) {
                memset(pDev, 0, sizeof(IPTV_DEV_SET));

                strcpy(pDev->iptvMAC, mac);
#ifdef USERVNI_ON
                pDev->vni = cfg_get_user_vni_id();
#else
                pDev->vni = 0;
#endif
                pDev->isReport = 0;
                uv_rwlock_wrlock(&g_uvCacheLock);
                HASH_ADD_STR(g_iptvNewDevs, iptvMAC, pDev);
                uv_rwlock_wrunlock(&g_uvCacheLock);
            }
        }

        LOG_MOD(debug, ZLOG_MOD_OPENDHCPD, "Found IPTV %s\n", mac);
        return 0;
    }

    return 1;
}

/**
 *  增加MAC地址黑名单
 */
void opendhcp_add_mac_filter() {
    c_vector pool = config_get_dhcp_mac_filter();
    data20   optionData {};
    optionData.options[0] = 0;
    optionData.options[1] = DHCP_OPTION_END;
    sds add               = sdsempty();
    sds err               = sdsempty();

    for (int i = 0; (pool && i < vect_size(pool)); i++) {
        auto pRange = (char *)vect_get_at(pool, i);

        if (pRange) {
            data7 *dhcpEntry;

            memset(&lump, 0, sizeof(data71));
            lump.mapname    = pRange;
            lump.optionSize = 2;
            lump.options    = optionData.options;
            dhcpEntry       = createCache(&lump);

            if (dhcpEntry) {
                dhcpEntry->ip                 = 0;
                dhcpEntry->rangeInd           = -1;
                dhcpEntry->fixed              = 1;
                dhcpEntry->expiry             = 0;
                dhcpCache[dhcpEntry->mapname] = dhcpEntry;
                add                           = sdscatfmt(add, "\"%s\", ", pRange);
            } else {
                err = sdscatfmt(err, "\"%s\", ", pRange);
            }
        }
    }

    if (sdslen(add) > 0) {
        sdsrange(add, 0, -3);
        LOG_MOD(debug, ZLOG_MOD_OPENDHCPD, "Add MAC [%s] for black list\n", add);
    }

    if (sdslen(err) > 0) {
        sdsrange(err, 0, -2);
        LOG_MOD(debug, ZLOG_MOD_OPENDHCPD, "Add MAC [%s] for black list error\n", err);
    }

    sdsfree(add);
    sdsfree(err);
}