/*
 *
 *    Copyright (c) 2020-2022 Project CHIP Authors
 *    Copyright (c) 2019 Nest Labs, Inc.
 *    All rights reserved.
 *
 *    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.
 */

/**
 *    @file
 *          Utility functions for working with OpenThread.
 */

#include "OpenThreadUtils.h"

#include <inet/IPAddress.h>
#include <lib/core/CHIPEncoding.h>
#include <lib/core/ErrorStr.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>

#include <openthread/error.h>

#include <cstdio>

namespace chip {
namespace DeviceLayer {
namespace Internal {

/**
 * Map an OpenThread error into the CHIP error space.
 */
CHIP_ERROR MapOpenThreadError(otError otErr)
{
    return (otErr == OT_ERROR_NONE) ? CHIP_NO_ERROR : CHIP_ERROR(ChipError::Range::kOpenThread, static_cast<unsigned int>(otErr));
}

/**
 * Given an CHIP error value that represents an OpenThread error, returns a
 * human-readable NULL-terminated C string describing the error.
 *
 * @param[in] buf                   Buffer into which the error string will be placed.
 * @param[in] bufSize               Size of the supplied buffer in bytes.
 * @param[in] err                   The error to be described.
 *
 * @return true                     If a description string was written into the supplied buffer.
 * @return false                    If the supplied error was not an OpenThread error.
 *
 */
bool FormatOpenThreadError(char * buf, uint16_t bufSize, CHIP_ERROR err)
{
    if (!err.IsRange(ChipError::Range::kOpenThread))
    {
        return false;
    }

#if CHIP_CONFIG_SHORT_ERROR_STR
    const char * desc = NULL;
#else  // CHIP_CONFIG_SHORT_ERROR_STR
    otError otErr     = (otError) err.GetValue();
    const char * desc = otThreadErrorToString(otErr);
#endif // CHIP_CONFIG_SHORT_ERROR_STR

    chip::FormatError(buf, bufSize, "OpenThread", err, desc);

    return true;
}

/**
 * Register a text error formatter for OpenThread errors.
 */
void RegisterOpenThreadErrorFormatter()
{
    static ErrorFormatter sOpenThreadErrorFormatter = { FormatOpenThreadError, NULL };

    RegisterErrorFormatter(&sOpenThreadErrorFormatter);
}

void LogOpenThreadStateChange(otInstance * otInst, uint32_t flags)
{
#if CHIP_DETAIL_LOGGING

    const uint32_t kParamsChanged = (OT_CHANGED_THREAD_NETWORK_NAME | OT_CHANGED_THREAD_PANID | OT_CHANGED_THREAD_EXT_PANID |
                                     OT_CHANGED_THREAD_CHANNEL | OT_CHANGED_NETWORK_KEY | OT_CHANGED_PSKC);
    static char strBuf[64];

    ChipLogDetail(DeviceLayer, "OpenThread State Changed (Flags: 0x%08" PRIx32 ")", flags);
    if ((flags & OT_CHANGED_THREAD_ROLE) != 0)
    {
        ChipLogDetail(DeviceLayer, "   Device Role: %s", OpenThreadRoleToStr(otThreadGetDeviceRole(otInst)));
    }
    if ((flags & kParamsChanged) != 0)
    {
        ChipLogDetail(DeviceLayer, "   Network Name: %s", otThreadGetNetworkName(otInst));
        ChipLogDetail(DeviceLayer, "   PAN Id: 0x%04X", otLinkGetPanId(otInst));
        {
            const otExtendedPanId * exPanId = otThreadGetExtendedPanId(otInst);
            snprintf(strBuf, sizeof(strBuf), "0x%02X%02X%02X%02X%02X%02X%02X%02X", exPanId->m8[0], exPanId->m8[1], exPanId->m8[2],
                     exPanId->m8[3], exPanId->m8[4], exPanId->m8[5], exPanId->m8[6], exPanId->m8[7]);
            ChipLogDetail(DeviceLayer, "   Extended PAN Id: %s", strBuf);
        }
        ChipLogDetail(DeviceLayer, "   Channel: %d", otLinkGetChannel(otInst));
        {
            const otMeshLocalPrefix * otMeshPrefix = otThreadGetMeshLocalPrefix(otInst);
            chip::Inet::IPAddress meshPrefix;
            memset(meshPrefix.Addr, 0, sizeof(meshPrefix.Addr));
            memcpy(meshPrefix.Addr, otMeshPrefix->m8, sizeof(otMeshPrefix->m8));
            meshPrefix.ToString(strBuf);
            ChipLogDetail(DeviceLayer, "   Mesh Prefix: %s/64", strBuf);
        }
#if CHIP_CONFIG_SECURITY_TEST_MODE
        {
            otNetworkKey otKey;
            otThreadGetNetworkKey(otInst, &otKey);
            for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++)
                snprintf(&strBuf[i * 2], 3, "%02X", otKey.m8[i]);
            ChipLogDetail(DeviceLayer, "   Network Key: %s", strBuf);
        }
#endif // CHIP_CONFIG_SECURITY_TEST_MODE
    }
    if ((flags & OT_CHANGED_THREAD_PARTITION_ID) != 0)
    {
        ChipLogDetail(DeviceLayer, "   Partition Id: 0x%" PRIX32, otThreadGetPartitionId(otInst));
    }
    if ((flags & (OT_CHANGED_IP6_ADDRESS_ADDED | OT_CHANGED_IP6_ADDRESS_REMOVED)) != 0)
    {
        ChipLogDetail(DeviceLayer, "   Thread Unicast Addresses:");
        for (const otNetifAddress * addr = otIp6GetUnicastAddresses(otInst); addr != NULL; addr = addr->mNext)
        {
            chip::Inet::IPAddress ipAddr;
            memcpy(ipAddr.Addr, addr->mAddress.mFields.m32, sizeof(ipAddr.Addr));

            ipAddr.ToString(strBuf);

            ChipLogDetail(DeviceLayer, "        %s/%d%s%s%s", strBuf, addr->mPrefixLength, addr->mValid ? " valid" : "",
                          addr->mPreferred ? " preferred" : "", addr->mRloc ? " rloc" : "");
        }
    }

#endif // CHIP_DETAIL_LOGGING
}

void LogOpenThreadPacket(const char * titleStr, otMessage * pkt)
{
#if CHIP_DETAIL_LOGGING

    char srcStr[50], destStr[50], typeBuf[20];
    const char * type = typeBuf;
    Inet::IPAddress addr;
    uint8_t headerData[44];
    uint16_t pktLen;

    const uint8_t & IPv6_NextHeader     = headerData[6];
    const uint8_t * const IPv6_SrcAddr  = headerData + 8;
    const uint8_t * const IPv6_DestAddr = headerData + 24;
    const uint8_t * const IPv6_SrcPort  = headerData + 40;
    const uint8_t * const IPv6_DestPort = headerData + 42;
    const uint8_t & ICMPv6_Type         = headerData[40];
    const uint8_t & ICMPv6_Code         = headerData[41];

    constexpr uint8_t kIPProto_UDP    = 17;
    constexpr uint8_t kIPProto_TCP    = 6;
    constexpr uint8_t kIPProto_ICMPv6 = 58;

    constexpr uint8_t kICMPType_EchoRequest  = 128;
    constexpr uint8_t kICMPType_EchoResponse = 129;

    pktLen = otMessageGetLength(pkt);

    if (pktLen >= sizeof(headerData))
    {
        otMessageRead(pkt, 0, headerData, sizeof(headerData));

        memcpy(addr.Addr, IPv6_SrcAddr, 16);
        addr.ToString(srcStr);

        memcpy(addr.Addr, IPv6_DestAddr, 16);
        addr.ToString(destStr);

        if (IPv6_NextHeader == kIPProto_UDP)
        {
            type = "UDP";
        }
        else if (IPv6_NextHeader == kIPProto_TCP)
        {
            type = "TCP";
        }
        else if (IPv6_NextHeader == kIPProto_ICMPv6)
        {
            if (ICMPv6_Type == kICMPType_EchoRequest)
            {
                type = "ICMPv6 Echo Request";
            }
            else if (ICMPv6_Type == kICMPType_EchoResponse)
            {
                type = "ICMPv6 Echo Response";
            }
            else
            {
                snprintf(typeBuf, sizeof(typeBuf), "ICMPv6 %u,%u", ICMPv6_Type, ICMPv6_Code);
            }
        }
        else
        {
            snprintf(typeBuf, sizeof(typeBuf), "IP proto %u", IPv6_NextHeader);
        }

        if (IPv6_NextHeader == kIPProto_UDP || IPv6_NextHeader == kIPProto_TCP)
        {
            snprintf(srcStr + strlen(srcStr), 13, ", port %u", Encoding::BigEndian::Get16(IPv6_SrcPort));
            snprintf(destStr + strlen(destStr), 13, ", port %u", Encoding::BigEndian::Get16(IPv6_DestPort));
        }

        ChipLogDetail(DeviceLayer, "%s: %s, len %u", StringOrNullMarker(titleStr), type, pktLen);
        ChipLogDetail(DeviceLayer, "    src  %s", srcStr);
        ChipLogDetail(DeviceLayer, "    dest %s", destStr);
    }
    else
    {
        ChipLogDetail(DeviceLayer, "%s: %s, len %u", StringOrNullMarker(titleStr), "(decode error)", pktLen);
    }

#endif // CHIP_DETAIL_LOGGING
}

bool IsOpenThreadMeshLocalAddress(otInstance * otInst, const Inet::IPAddress & addr)
{
    const otMeshLocalPrefix * otMeshPrefix = otThreadGetMeshLocalPrefix(otInst);

    return otMeshPrefix != NULL && memcmp(otMeshPrefix->m8, addr.Addr, OT_MESH_LOCAL_PREFIX_SIZE) == 0;
}

const char * OpenThreadRoleToStr(otDeviceRole role)
{
    switch (role)
    {
    case OT_DEVICE_ROLE_DISABLED:
        return "DISABLED";
    case OT_DEVICE_ROLE_DETACHED:
        return "DETACHED";
    case OT_DEVICE_ROLE_CHILD:
        return "CHILD";
    case OT_DEVICE_ROLE_ROUTER:
        return "ROUTER";
    case OT_DEVICE_ROLE_LEADER:
        return "LEADER";
    default:
        return "(unknown)";
    }
}

} // namespace Internal
} // namespace DeviceLayer
} // namespace chip
