/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nstackx_dev.h"
#include "nstackx_util.h"
#include "nstackx_error.h"
#include "nstackx_log.h"
#include "securec.h"

#define TAG "nStackXDev"

static int32_t GetConnectionTypeByDevName(const char *devName, uint32_t devNameLen, uint16_t *connectType)
{
    int32_t ret = NSTACKX_EFAILED;
    uint32_t p2pNameLen = (uint32_t)strlen(P2P_DEV_NAME_PRE);
    uint32_t wlanNameLen = (uint32_t)strlen(WLAN_DEV_NAME_PRE);

    if (devNameLen >= p2pNameLen && memcmp(devName, P2P_DEV_NAME_PRE, p2pNameLen) == 0) {
        *connectType = CONNECT_TYPE_P2P;
        ret = NSTACKX_EOK;
        LOGI(TAG, "connType is P2P(%hu)", *connectType);
    } else if (devNameLen >= wlanNameLen && memcmp(devName, WLAN_DEV_NAME_PRE, wlanNameLen) == 0) {
        *connectType = CONNECT_TYPE_WLAN;
        LOGI(TAG, "connType is WLAN(%hu)", *connectType);
        ret = NSTACKX_EOK;
    }
    return ret;
}

static int32_t GetInterfaceInfo(int32_t fd, int32_t option, struct ifreq *interface)
{
    if (interface == NULL) {
        return NSTACKX_EINVAL;
    }
    if (ioctl(fd, SIOCGIFFLAGS, (char*)interface) < 0) {
        LOGE(TAG, "ioctl fail, errno = %d", errno);
        return NSTACKX_EFAILED;
    }
    if (!((uint16_t)interface->ifr_flags & IFF_UP)) {
        LOGE(TAG, "interface is not up");
        return NSTACKX_EINVAL;
    }

    /* get IP of this interface */
    if (ioctl(fd, option, (char*)interface) < 0) {
        LOGE(TAG, "ioctl fail, errno = %d", errno);
        return NSTACKX_EFAILED;
    }
    return NSTACKX_EOK;
}

int32_t GetInterfaceIP(int32_t fd, struct ifreq *interface)
{
    return GetInterfaceInfo(fd, SIOCGIFADDR, interface);
}

static int32_t GetInterfaceNetMask(int32_t fd, struct ifreq *interface)
{
    return GetInterfaceInfo(fd, SIOCGIFNETMASK, interface);
}

int32_t GetInterfaceList(struct ifconf *ifc, struct ifreq *buf, uint32_t size)
{
    int32_t fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        return NSTACKX_EFAILED;
    }
    ifc->ifc_len = (int32_t)size;
    ifc->ifc_buf = (char*)buf;
    if (ioctl(fd, SIOCGIFCONF, (char*)ifc) < 0) {
        LOGE(TAG, "ioctl fail, errno = %d", errno);
        CloseSocketInner(fd);
        return NSTACKX_EFAILED;
    }
    return fd;
}

int32_t GetConnectionTypeByDev(const uint32_t sourceIp, uint16_t *connectType)
{
    struct ifreq buf[INTERFACE_MAX];
    struct ifconf ifc;

    uint32_t ethNameLen = (uint32_t)strlen(ETH_DEV_NAME_PRE);
    uint32_t wlanNameLen = (uint32_t)strlen(WLAN_DEV_NAME_PRE);
    int32_t fd = GetInterfaceList(&ifc, buf, sizeof(buf));
    if (fd < 0) {
        LOGE(TAG, "get interfacelist failed");
        return NSTACKX_EFAILED;
    }

    int32_t ifreqLen = (int32_t)sizeof(struct ifreq);
    int32_t interfaceNum = (int32_t)(ifc.ifc_len / ifreqLen);
    for (int32_t i = 0; i < interfaceNum && i < INTERFACE_MAX; i++) {
        LOGI(TAG, "ndevice name: %s", buf[i].ifr_name);
        uint32_t ifrNameLen = (uint32_t)strlen(buf[i].ifr_name);
        if (ifrNameLen < ethNameLen && ifrNameLen < wlanNameLen) {
            continue;
        }

        /* get IP of this interface */
        int32_t state = GetInterfaceIP(fd, &buf[i]);
        if (state == NSTACKX_EFAILED) {
            goto L_ERROR;
        } else if (state == NSTACKX_EINVAL) {
            continue;
        }
        if (sourceIp == ((struct sockaddr_in *)&(buf[i].ifr_addr))->sin_addr.s_addr) {
            if (GetConnectionTypeByDevName(buf[i].ifr_name, ifrNameLen, connectType) == NSTACKX_EOK) {
                break;
            }
        }
    }
    CloseSocketInner(fd);
    return NSTACKX_EOK;
L_ERROR:
    CloseSocketInner(fd);
    LOGE(TAG, "get connect type failed");
    return NSTACKX_EFAILED;
}

static int32_t FindDevByInterfaceIP(int32_t fd, struct ifconf ifc, struct ifreq buf[], uint32_t sourceIP)
{
    int32_t i;
    int32_t ifreqLen = (int32_t)sizeof(struct ifreq);
    int32_t interfaceNum = (int32_t)(ifc.ifc_len / ifreqLen);
    for (i = 0; i < interfaceNum && i < INTERFACE_MAX; i++) {
        /* get IP of this interface */
        int32_t state = GetInterfaceIP(fd, &buf[i]);
        if (state == NSTACKX_EFAILED) {
            return NSTACKX_EFAILED;
        } else if (state == NSTACKX_EINVAL) {
            continue;
        }
        if (sourceIP == ((struct sockaddr_in *)&(buf[i].ifr_addr))->sin_addr.s_addr) {
            return i;
        }
    }
    return NSTACKX_EFAILED;
}

int32_t GetInterfaceNameByIP(uint32_t sourceIP, char *interfaceName, size_t nameLen)
{
    struct ifreq buf[INTERFACE_MAX];
    struct ifconf ifc;
    int32_t devIndex;
    int32_t ret = NSTACKX_EOK;
    int32_t fd = GetInterfaceList(&ifc, buf, sizeof(buf));
    if (fd < 0) {
        LOGE(TAG, "can't GetInterfaceList");
        return NSTACKX_EFAILED;
    }
    devIndex = FindDevByInterfaceIP(fd, ifc, buf, sourceIP);
    CloseSocketInner(fd);
    if (devIndex >= 0) {
        if (strcpy_s(interfaceName, nameLen, buf[devIndex].ifr_name) != EOK) {
            LOGE(TAG, "strcpy failed");
            ret = NSTACKX_EFAILED;
        }
    }
    return ret;
}

static int32_t BindToDeviceInner(int32_t sockfd, const struct ifreq *ifBinding)
{
    struct ifreq ifr;

    if (ifBinding == NULL) {
        LOGE(TAG, "no right interface for binding");
        return NSTACKX_EFAILED;
    }
    if (strncpy_s(ifr.ifr_ifrn.ifrn_name, IFNAMSIZ, ifBinding->ifr_name, strlen(ifBinding->ifr_name)) != EOK) {
        LOGE(TAG, "strncpy fail");
        return NSTACKX_EFAILED;
    }
    if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, (char *)&ifr, sizeof(ifr)) < 0) {
        LOGE(TAG, "setsockopt fail, errno = %d", errno);
        return NSTACKX_EFAILED;
    }
    LOGI(TAG, "binding interface %s success", ifBinding->ifr_name);
    return NSTACKX_EOK;
}

/*
 * If localAddr isn't NULL, bind to interface correspond to ip,
 * otherwise, bind to interface which is choosed by strategy.
 */
int32_t BindToDevice(SocketDesc sockfd, const struct sockaddr_in *localAddr)
{
    struct ifreq buf[INTERFACE_MAX];
    struct ifconf ifc;
    struct ifreq *ifBinding = NULL;
    uint32_t ethNameLen = (uint32_t)strlen(ETH_DEV_NAME_PRE);
    uint32_t wlanNameLen = (uint32_t)strlen(WLAN_DEV_NAME_PRE);
    int32_t fd = GetInterfaceList(&ifc, buf, sizeof(buf));
    if (fd < 0) {
        return NSTACKX_EFAILED;
    }
    int32_t ifreqLen = (int32_t)sizeof(struct ifreq);
    int32_t interfaceNum = (int32_t)(ifc.ifc_len / ifreqLen);
    for (int32_t i = 0; i < interfaceNum && i < INTERFACE_MAX; i++) {
        LOGI(TAG, "device name: %s", buf[i].ifr_name);
        if (strlen(buf[i].ifr_name) < ethNameLen && strlen(buf[i].ifr_name) < wlanNameLen) {
            continue;
        }
        /* get IP of this interface */
        int32_t state = GetInterfaceIP(fd, &buf[i]);
        if (state == NSTACKX_EFAILED) {
            goto L_ERROR;
        } else if (state == NSTACKX_EINVAL) {
            continue;
        }
        if (localAddr != NULL) {
            /* find corresponding interface by ip */
            if (localAddr->sin_addr.s_addr == ((struct sockaddr_in *)&(buf[i].ifr_addr))->sin_addr.s_addr) {
                ifBinding = &buf[i];
                break;
            }
        } else {
            /* strategy: ethernet have higher priority */
            if (memcmp(buf[i].ifr_name, ETH_DEV_NAME_PRE, ethNameLen) == 0) {
                ifBinding = &buf[i];
                break;
            } else if (memcmp(buf[i].ifr_name, WLAN_DEV_NAME_PRE, wlanNameLen) == 0) {
                ifBinding = &buf[i];
            }
        }
    }
    CloseSocketInner(fd);
    return BindToDeviceInner(sockfd, ifBinding);
L_ERROR:
    LOGE(TAG, "ioctl fail, errno = %d", errno);
    CloseSocketInner(fd);
    return NSTACKX_EFAILED;
}

int32_t GetIfBroadcastIp(const char *ifName, char *ipString, size_t ipStringLen)
{
    struct ifreq buf[INTERFACE_MAX];
    struct ifconf ifc;
    uint8_t foundIp = NSTACKX_FALSE;

    if (ifName == NULL) {
        return NSTACKX_EFAILED;
    }

    int32_t fd = GetInterfaceList(&ifc, buf, sizeof(buf));
    if (fd < 0) {
        return NSTACKX_EFAILED;
    }

    int32_t ifreqLen = (int32_t)sizeof(struct ifreq);
    int32_t interfaceNum = (int32_t)(ifc.ifc_len / ifreqLen);
    for (int32_t i = 0; i < interfaceNum && i < INTERFACE_MAX; i++) {
        if (strlen(buf[i].ifr_name) < strlen(ifName)) {
            continue;
        }
        if (memcmp(buf[i].ifr_name, ifName, strlen(ifName)) != 0) {
            continue;
        }
        if (GetInterfaceInfo(fd, SIOCGIFBRDADDR, &buf[i]) != NSTACKX_EOK) {
            continue;
        }
        if (buf[i].ifr_addr.sa_family != AF_INET) {
            continue;
        }

        if (inet_ntop(AF_INET, &(((struct sockaddr_in *)&(buf[i].ifr_addr))->sin_addr), ipString,
            (socklen_t)ipStringLen) == NULL) {
            continue;
        }
        foundIp = NSTACKX_TRUE;
        break;
    }
    CloseSocketInner(fd);

    if (!foundIp) {
        return NSTACKX_EFAILED;
    }

    return NSTACKX_EOK;
}

static uint8_t IsValidInterface(const char *interfaceName)
{
    if (interfaceName == NULL) {
        return NSTACKX_FALSE;
    }
    uint32_t targetDevLen = (uint32_t)strlen(P2P_DEV_NAME_PRE);
    if (strlen(interfaceName) >= targetDevLen && memcmp(interfaceName, P2P_DEV_NAME_PRE, targetDevLen) == 0) {
        return NSTACKX_TRUE;
    }
    targetDevLen = (uint32_t)strlen(ETH_DEV_NAME_PRE);
    if (strlen(interfaceName) >= targetDevLen && memcmp(interfaceName, ETH_DEV_NAME_PRE, targetDevLen) == 0) {
        return NSTACKX_TRUE;
    }
    targetDevLen = (uint32_t)strlen(WLAN_DEV_NAME_PRE);
    if (strlen(interfaceName) >= targetDevLen && memcmp(interfaceName, WLAN_DEV_NAME_PRE, targetDevLen) == 0) {
        return NSTACKX_TRUE;
    }
    return NSTACKX_FALSE;
}

int32_t GetTargetInterface(const struct sockaddr_in *dstAddr, struct ifreq *localDev)
{
    struct ifreq buf[INTERFACE_MAX];
    struct ifconf ifc;
    uint32_t localIp;
    uint32_t netMask;
    int32_t fd = GetInterfaceList(&ifc, buf, sizeof(buf));
    if (fd < 0) {
        return NSTACKX_EFAILED;
    }
    int32_t ifreqLen = (int32_t)sizeof(struct ifreq);
    int32_t interfaceNum = (int32_t)(ifc.ifc_len / ifreqLen);
    for (int32_t i = 0; i < interfaceNum && i < INTERFACE_MAX; i++) {
        if (!IsValidInterface(buf[i].ifr_name)) {
            continue;
        }
        /* get IP of this interface */
        int32_t state = GetInterfaceIP(fd, &buf[i]);
        if (state == NSTACKX_EFAILED) {
            goto L_ERROR;
        } else if (state == NSTACKX_EINVAL) {
            continue;
        }
        localIp = ((struct sockaddr_in *)(&buf[i].ifr_addr))->sin_addr.s_addr;
        state = GetInterfaceNetMask(fd, &buf[i]);
        if (state == NSTACKX_EFAILED) {
            goto L_ERROR;
        } else if (state == NSTACKX_EINVAL) {
            continue;
        }
        netMask = ((struct sockaddr_in *)&(buf[i].ifr_netmask))->sin_addr.s_addr;
        /* if localIp and dstIp are in the same LAN, fetch the interface name of thie localIp and return */
        if ((dstAddr->sin_addr.s_addr & netMask) == (localIp & netMask)) {
            if (strncpy_s(localDev->ifr_ifrn.ifrn_name, IFNAMSIZ, buf[i].ifr_name, strlen(buf[i].ifr_name)) != EOK) {
                LOGE(TAG, "ifreq name copy failed");
                goto L_ERROR;
            }
            CloseSocketInner(fd);
            return NSTACKX_EOK;
        }
    }
L_ERROR:
    CloseSocketInner(fd);
    return NSTACKX_EFAILED;
}

void BindToDevInTheSameLan(SocketDesc sockfd, const struct sockaddr_in *sockAddr)
{
    struct ifreq localInterface;
    if (sockfd < 0) {
        return;
    }
    (void)memset_s(&localInterface, sizeof(localInterface), 0, sizeof(localInterface));
    if (GetTargetInterface(sockAddr, &localInterface) != NSTACKX_EOK) {
        LOGE(TAG, "get target interface fail");
        return;
    }
    if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, (char *)&localInterface, sizeof(localInterface)) < 0) {
        LOGE(TAG, "bind to device fail, errno = %d", errno);
        return;
    }
    LOGI(TAG, "bind to %s successfully", localInterface.ifr_name);
}

int32_t BindToTargetDev(SocketDesc sockfd, const char *targetInterfaceName)
{
    struct ifreq buf[INTERFACE_MAX];
    struct ifconf ifc;
    int32_t ret = NSTACKX_EFAILED;
    int32_t fd = GetInterfaceList(&ifc, buf, sizeof(buf));
    if (fd < 0) {
        return NSTACKX_EFAILED;
    }
    int32_t ifreqLen = (int32_t)sizeof(struct ifreq);
    int32_t interfaceNum = (int32_t)(ifc.ifc_len / ifreqLen);
    for (int32_t i = 0; i < interfaceNum && i < INTERFACE_MAX; i++) {
        /* get IP of this interface */
        int32_t state = GetInterfaceIP(fd, &buf[i]);
        if (state == NSTACKX_EFAILED) {
            break;
        } else if (state == NSTACKX_EINVAL) {
            continue;
        }
        if (strlen(buf[i].ifr_name) == strlen(targetInterfaceName) &&
            strcmp(buf[i].ifr_name, targetInterfaceName) == 0) {
            ret = BindToDeviceInner(sockfd, &buf[i]);
            break;
        }
    }
    CloseSocketInner(fd);
    return ret;
}