/*
 * Copyright (c) 2021-2023 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_dfinder_hidump.h"
#include <inttypes.h>
#include "nstackx_dfinder_log.h"
#include "nstackx_dfinder_mgt_msg_log.h"
#include "nstackx_util.h"
#include "nstackx_getopt.h"
#include "nstackx_statistics.h"
#include "nstackx_common.h"
#include "nstackx_device.h"
#include "nstackx_event.h"
#include "nstackx_error.h"
#include "securec.h"
#include "nstackx_device_local.h"
#include "nstackx_device_remote.h"

#ifdef NSTACKX_DFINDER_HIDUMP

#define TAG "nStackXDFinder"
#define CRLF "\r\n"

#define DUMP_BUF_LEN (2048U)
#define DUMP_LARGE_BUF_LEN (409600U)
#define DFINDER_DUMP_MAX_ARGC (20U)
#define DFINDER_DUMP_STRTOL_BASE 10
static const char *g_dfinderDumpOpts = "fhlrsm:";

typedef struct {
    char *buf;
    uint32_t size;
    int err;
    sem_t wait;
} DumpMsg;

static DumpMsg *CreateDumpMsg(char *buf, uint32_t size)
{
    DumpMsg *msg = (DumpMsg *)malloc(sizeof(DumpMsg));
    if (msg == NULL) {
        DFINDER_LOGE(TAG, "malloc dump msg failed");
        return NULL;
    }

    if (SemInit(&(msg->wait), 0, 0)) {
        DFINDER_LOGE(TAG, "init msg wait failed");
        free(msg);
        return NULL;
    }

    msg->err = NSTACKX_EOK;
    msg->buf = buf;
    msg->size = size;
    return msg;
}

static void DestroyDumpMsg(DumpMsg *msg)
{
    SemDestroy(&(msg->wait));
    free(msg);
}

static int PostDumpMsg(char *buf, uint32_t size, EventHandle handle)
{
    int ret = NSTACKX_EFAILED;
    DumpMsg *msg = CreateDumpMsg(buf, size);
    if (msg == NULL) {
        DFINDER_LOGE(TAG, "create dump msg failed");
        return ret;
    }

    if (PostEvent(GetEventNodeChain(), GetEpollFD(), handle, msg) != NSTACKX_EOK) {
        DestroyDumpMsg(msg);
        return ret;
    }

    SemWait(&(msg->wait));
    ret = msg->err;
    DestroyDumpMsg(msg);
    return ret;
}

typedef int (*DumpFunc)(char *buf, uint32_t size);
static int Dump(void *softObj, uint32_t size, DFinderDumpFunc dump, DumpFunc func)
{
    if (size == 0 || size > DUMP_LARGE_BUF_LEN) {
        return NSTACKX_EFAILED;
    }

    char *buf = (char *)calloc(size, sizeof(char));
    if (buf == NULL) {
        DFINDER_LOGE(TAG, "dump malloc failed");
        return NSTACKX_EFAILED;
    }

    if (func(buf, size) != NSTACKX_EOK) {
        DFINDER_LOGE(TAG, "dump func exec failed");
        free(buf);
        return NSTACKX_EFAILED;
    }

    dump(softObj, buf, strlen(buf) + 1);
    free(buf);
    return NSTACKX_EOK;
}

static int DumpStatisticsInfo(char *buf, uint32_t size)
{
    int ret;
    uint32_t index = 0;
    const uint64_t *stat = GetStatistics();
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, CRLF"DFinder statistics:"CRLF);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "INVALID_OPT_AND_PAYLOAD: %" PRIu64 CRLF,
        stat[STATS_INVALID_OPT_AND_PAYLOAD]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "DECODE_FAILED: %" PRIu64 CRLF, stat[STATS_DECODE_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "ENCODE_FAILED: %" PRIu64 CRLF, stat[STATS_ENCODE_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "CREATE_HEADER_FAILED: %" PRIu64 CRLF, stat[STATS_CREATE_HEADER_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "BUILD_PKT_FAILED: %" PRIu64 CRLF, stat[STATS_BUILD_PKT_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "SOCKET_ERROR: %" PRIu64 CRLF, stat[STATS_SOCKET_ERROR]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "EPOLL_ERROR: %" PRIu64 CRLF, stat[STATS_EPOLL_ERROR]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "CREATE_SERVER_FAILED: %" PRIu64 CRLF, stat[STATS_CREATE_SERVER_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "CREATE_CLIENT_FAILED: %" PRIu64 CRLF, stat[STATS_CREATE_CLIENT_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "DROP_LOOPBACK_PKT: %" PRIu64 CRLF, stat[STATS_DROP_LOOPBACK_PKT]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "SEND_MSG_FAILED: %" PRIu64 CRLF, stat[STATS_SEND_MSG_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "SEND_REQUEST_FAILED: %" PRIu64 CRLF, stat[STATS_SEND_REQUEST_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "DROP_MSG_ID: %" PRIu64 CRLF, stat[STATS_DROP_MSG_ID]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "HANDLE_SERVICE_MSG_FAILED: %" PRIu64 CRLF,
        stat[STATS_HANDLE_SERVICE_MSG_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "HANDLE_DEVICE_DISCOVER_MSG_FAILED: %" PRIu64 CRLF,
        stat[STATS_HANDLE_DEVICE_DISCOVER_MSG_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "INVALID_RESPONSE_MSG: %" PRIu64 CRLF, stat[STATS_INVALID_RESPONSE_MSG]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "POST_SD_REQUEST_FAILED: %" PRIu64 CRLF,
        stat[STATS_POST_SD_REQUEST_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "ABORT_SD: %" PRIu64 CRLF, stat[STATS_ABORT_SD]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "START_SD_FAILED: %" PRIu64 CRLF, stat[STATS_START_SD_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "CREATE_SERVICE_MSG_FAILED: %" PRIu64 CRLF,
        stat[STATS_CREATE_SERVICE_MSG_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "SEND_SD_RESPONSE_FAILED: %" PRIu64 CRLF,
        stat[STATS_SEND_SD_RESPONSE_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "BACKUP_DEVICE_DB_FAILED: %" PRIu64 CRLF,
        stat[STATS_BACKUP_DEVICE_DB_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "UPDATE_DEVICE_DB_FAILED: %" PRIu64 CRLF,
        stat[STATS_UPDATE_DEVICE_DB_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "CREATE_CONTEX_FAILED: %" PRIu64 CRLF, stat[STATS_CREATE_CONTEX_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "CREATE_SESSION_FAILED: %" PRIu64 CRLF,
        stat[STATS_CREATE_SESSION_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "PREPARE_SD_MSG_FAILED: %" PRIu64 CRLF,
        stat[STATS_PREPARE_SD_MSG_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "PARSE_SD_MSG_FAILED: %" PRIu64 CRLF, stat[STATS_PARSE_SD_MSG_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "ALLOC_RECORD_FAILED: %" PRIu64 CRLF, stat[STATS_ALLOC_RECORD_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "FREE_RECORD_FAILED: %" PRIu64 CRLF, stat[STATS_FREE_RECORD_FAILED]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "OVER_DEVICE_LIMIT: %" PRIu64 CRLF, stat[STATS_OVER_DEVICE_LIMIT]);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "COAP_RESOURCE_INIT_FAILED: %" PRIu64 CRLF,
        stat[STATS_COAP_RESOURCE_INIT_FAILED]);
    return NSTACKX_EOK;
}

static void DumpStatisticsInner(void *arg)
{
    DumpMsg *msg = (DumpMsg *)arg;
    msg->err = DumpStatisticsInfo(msg->buf, msg->size);
    SemPost(&(msg->wait));
}

static int DumpStatistics(char *buf, uint32_t size)
{
    return PostDumpMsg(buf, size, DumpStatisticsInner);
}

int DFinderDumpIface(const struct DumpIfaceInfo *info, char *buf, int size)
{
    int index = 0;
    int ret;
    if (info == NULL || info->addr == NULL) {
        DFINDER_LOGE(TAG, "info is err");
        return NSTACKX_EFAILED;
    }
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "network name:%s"CRLF, info->ifname);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "if state:%hhu"CRLF, info->state);

    struct sockaddr_in addr;
    struct sockaddr_in6 addr6;
    struct sockaddr *ptr = (struct sockaddr *)&addr;
    size_t len = sizeof(struct sockaddr_in);
    char ipStr[NSTACKX_MAX_IP_STRING_LEN] = {0};
    if (info->af == AF_INET) {
        (void)memset_s(&addr, sizeof(struct sockaddr_in), 0, sizeof(struct sockaddr_in));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = info->addr->in.s_addr;
    } else {
        (void)memset_s(&addr6, sizeof(struct sockaddr_in6), 0, sizeof(struct sockaddr_in6));
        addr6.sin6_family = AF_INET6;
        addr6.sin6_addr = info->addr->in6;
        ptr = (struct sockaddr *)&addr6;
        len = sizeof(struct sockaddr_in6);
    }

    ret = IpAddrAnonymousFormat(ipStr, NSTACKX_MAX_IP_STRING_LEN, ptr, len);
    if (ret < 0) {
        return ret;
    }
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "ip: %s"CRLF, ipStr);

    return index;
}

#define DFINDER_DEVICE_ID_ANONY_LEN 6
#define DFINDER_DEVICE_ID_ANONY_REMOTE_LEN 15
int DumpDeviceInfo(const DeviceInfo *info, char *buf, int size, uint8_t remote)
{
    int ret;
    int i;
    int index = 0;
    size_t len;
    char deviceid[DFINDER_DEVICE_ID_ANONY_REMOTE_LEN + 1] = {0};
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "device name:%s"CRLF, info->deviceName);

    len = strlen(info->deviceId);
    if (len > 0) {
        size_t anonyLen = remote == NSTACKX_TRUE ? DFINDER_DEVICE_ID_ANONY_REMOTE_LEN : DFINDER_DEVICE_ID_ANONY_LEN;
        len = len > anonyLen ? anonyLen : len;
        ret = memcpy_s(deviceid, sizeof(deviceid), info->deviceId, len);
        if (ret != EOK) {
            DFINDER_LOGE(TAG, "memcpy_s failed");
            return NSTACKX_EFAILED;
        }
        DUMP_MSG_ADD_CHECK(ret, buf, index, size, "device id:%s******"CRLF, deviceid);
    }

    for (i = 0; i < NSTACKX_MAX_CAPABILITY_NUM; i++) {
        DUMP_MSG_ADD_CHECK(ret, buf, index, size, "cap %d:%u"CRLF, i, info->capabilityBitmap[i]);
    }

    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "mode:%hhu"CRLF"businessType:%hhu"CRLF, info->mode, info->businessType);
    if (remote == NSTACKX_TRUE) {
        DUMP_MSG_ADD_CHECK(ret, buf, index, size, "discoveryType:%hhu"CRLF, info->discoveryType);
    }
    return index;
}

static void DumpLocalDeviceInfoInner(void *arg)
{
    int ret;
    DumpMsg *msg = (DumpMsg *)arg;
    ret = DumpDeviceInfo(GetLocalDeviceInfo(), msg->buf, msg->size, NSTACKX_FALSE);
    if (ret < 0) {
        msg->err = ret;
    } else {
        ret = LocalIfaceDump(&msg->buf[ret], msg->size - ret);
        if (ret < 0) {
            msg->err = ret;
        } else {
            msg->err = NSTACKX_EOK;
        }
    }
    SemPost(&(msg->wait));
}

static int DumpLocalDeviceInfo(char *buf, uint32_t size)
{
    return PostDumpMsg(buf, size, DumpLocalDeviceInfoInner);
}

#ifdef DFINDER_SAVE_DEVICE_LIST
static void DumpRemoteDeviceInfoInner(void *arg)
{
    DumpMsg *msg = (DumpMsg *)arg;
    int ret = DumpRemoteDevice(msg->buf, msg->size);
    if (ret < 0) {
        msg->err = NSTACKX_EFAILED;
    } else {
        msg->err = NSTACKX_EOK;
    }
    SemPost(&(msg->wait));
}

static int DumpRemoteDeviceInfo(char *buf, uint32_t size)
{
    return PostDumpMsg(buf, size, DumpRemoteDeviceInfoInner);
}
#else
static int DumpRemoteDeviceInfo(char *buf, uint32_t size)
{
    (void)buf;
    (void)size;
    return NSTACKX_EFAILED;
}
#endif

static int DumpCapFilterInfoImp(char *buf, uint32_t size)
{
    int i;
    int ret;
    uint32_t index = 0;
    uint32_t bitmapNum;
    uint32_t *bitmap = GetFilterCapability(&bitmapNum);

    for (i = 0; i < NSTACKX_MAX_CAPABILITY_NUM; i++) {
        DUMP_MSG_ADD_CHECK(ret, buf, index, size, "filter cap %d:%u"CRLF, i, bitmap[i]);
    }

    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "filter num:%u"CRLF, bitmapNum);
    return NSTACKX_EOK;
}

static void DumpCapFilterInfoInner(void *arg)
{
    DumpMsg *msg = (DumpMsg *)arg;
    msg->err = DumpCapFilterInfoImp(msg->buf, msg->size);
    SemPost(&(msg->wait));
}

static int DumpCapFilterInfo(char *buf, uint32_t size)
{
    return PostDumpMsg(buf, size, DumpCapFilterInfoInner);
}

static int DumpHelp(char *buf, uint32_t size)
{
    int ret;
    uint32_t index = 0;
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, CRLF"Usage: dfinder <opt>"CRLF);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "       -h         show this help"CRLF);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "       -l         show local device info"CRLF);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "       -r         show remote device info"CRLF);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "       -f         show capability filter info"CRLF);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "       -s         show statistics info"CRLF);
    DUMP_MSG_ADD_CHECK(ret, buf, index, size, "       -m <0/1>   enable control message log"CRLF);
    return NSTACKX_EOK;
}

static int32_t EnableMgtMsgLog(const char *optMsg, void *softObj, DFinderDumpFunc dump)
{
    int32_t enable;

    if ((optMsg == NULL) || (strlen(optMsg) != 1) ||
        ((optMsg[0] != '0') && (optMsg[0] != '1'))) {
        const char *errMsg = "invalid parameter";
        dump(softObj, errMsg, strlen(errMsg));
        (void)Dump(softObj, DUMP_BUF_LEN, dump, DumpHelp);
        return NSTACKX_EFAILED;
    }

    enable = (int32_t)strtol(optMsg, NULL, DFINDER_DUMP_STRTOL_BASE);
#ifdef DFINDER_MGT_MSG_LOG
    (void)DFinderSetMgtMsgLog(enable);
    if (enable == 0) {
        const char *disableMsg = "disable control message log";
        dump(softObj, disableMsg, strlen(disableMsg));
    } else {
        const char *enableMsg = "enable control message log";
        dump(softObj, enableMsg, strlen(enableMsg));
    }
    return NSTACKX_EOK;
#else
    const char *unsupportMsg = "the command is unsupported";
    dump(softObj, unsupportMsg, strlen(unsupportMsg));
    return NSTACKX_EFAILED;
#endif
}

int DFinderDump(const char **argv, uint32_t argc, void *softObj, DFinderDumpFunc dump)
{
    int32_t opt;
    NstackGetOptMsg optMsg;
    int32_t ret = NstackInitGetOptMsg(&optMsg);
    if (ret != NSTACKX_EOK) {
        return ret;
    }

    if (argc == 1) {
        (void)Dump(softObj, DUMP_BUF_LEN, dump, DumpHelp);
        return NSTACKX_EOK;
    }

    ret = NSTACKX_EFAILED;
    while ((opt = NstackGetOpt(&optMsg, argc, argv, g_dfinderDumpOpts)) != NSTACK_GETOPT_END_OF_STR) {
        switch (opt) {
            case 'h':
                ret = Dump(softObj, DUMP_BUF_LEN, dump, DumpHelp);
                break;
            case 's':
                ret = Dump(softObj, DUMP_BUF_LEN, dump, DumpStatistics);
                break;
            case 'l':
                ret = Dump(softObj, DUMP_BUF_LEN, dump, DumpLocalDeviceInfo);
                break;
            case 'r':
                ret = Dump(softObj, DUMP_LARGE_BUF_LEN, dump, DumpRemoteDeviceInfo);
                break;
            case 'f':
                ret = Dump(softObj, DUMP_BUF_LEN, dump, DumpCapFilterInfo);
                break;
            case 'm':
                ret = EnableMgtMsgLog(NstackGetOptArgs(&optMsg), softObj, dump);
                break;
            default:
                ret = NSTACKX_EFAILED;
                (void)Dump(softObj, DUMP_BUF_LEN, dump, DumpHelp);
                DFINDER_LOGE(TAG, "unknown option");
                break;
        }

        if (ret != NSTACKX_EOK) {
            break;
        }
    }

    return ret;
}

#endif /* NSTACKX_DFINDER_HIDUMP */