/*
 *
 *    Copyright (c) 2021-2022 Project CHIP Authors
 *    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
 *      This file implements an object for a Matter User Directed Commissioning unsolicited
 *      recipient (server).
 *
 */

#include "UserDirectedCommissioning.h"
#include <lib/core/CHIPSafeCasts.h>
#include <system/TLVPacketBufferBackingStore.h>
#include <transport/raw/Base.h>

#include <unistd.h>

namespace chip {
namespace Protocols {
namespace UserDirectedCommissioning {

void UserDirectedCommissioningServer::OnMessageReceived(const Transport::PeerAddress & source, System::PacketBufferHandle && msg,
                                                        Transport::MessageTransportContext * ctxt)
{
    char addrBuffer[chip::Transport::PeerAddress::kMaxToStringSize];
    source.ToString(addrBuffer);
    ChipLogProgress(AppServer, "UserDirectedCommissioningServer::OnMessageReceived from %s", addrBuffer);

    PacketHeader packetHeader;

    ReturnOnFailure(packetHeader.DecodeAndConsume(msg));

    if (packetHeader.IsEncrypted())
    {
        ChipLogError(AppServer, "UDC encryption flag set - ignoring");
        return;
    }

    PayloadHeader payloadHeader;
    ReturnOnFailure(payloadHeader.DecodeAndConsume(msg));

    ChipLogProgress(AppServer, "IdentityDeclaration DataLength()=%" PRIu32, static_cast<uint32_t>(msg->DataLength()));

    uint8_t udcPayload[IdentificationDeclaration::kUdcTLVDataMaxBytes];
    size_t udcPayloadLength = std::min<size_t>(msg->DataLength(), sizeof(udcPayload));
    msg->Read(udcPayload, udcPayloadLength);

    IdentificationDeclaration id;
    id.ReadPayload(udcPayload, sizeof(udcPayload));

    if (id.GetCancelPasscode())
    {
        HandleUDCCancel(id);
        return;
    }

    if (id.GetCommissionerPasscodeReady())
    {
        HandleUDCCommissionerPasscodeReady(id);
        return;
    }

    HandleNewUDC(source, id);
}

void UserDirectedCommissioningServer::HandleNewUDC(const Transport::PeerAddress & source, IdentificationDeclaration & id)
{
    char * instanceName = (char *) id.GetInstanceName();
    ChipLogProgress(AppServer, "HandleNewUDC instance=%s ", id.GetInstanceName());

    UDCClientState * client = mUdcClients.FindUDCClientState(instanceName);
    if (client == nullptr)
    {
        ChipLogProgress(AppServer, "UDC new instance state received");

        id.DebugLog();

        CHIP_ERROR err;
        err = mUdcClients.CreateNewUDCClientState(instanceName, &client);
        if (err != CHIP_NO_ERROR)
        {
            ChipLogError(AppServer, "UDC error creating new connection state");
            return;
        }

        if (id.HasDiscoveryInfo())
        {
            // if we received mDNS info, skip the commissionable lookup
            ChipLogDetail(AppServer, "UDC discovery info provided");
            mUdcClients.MarkUDCClientActive(client);

            client->SetUDCClientProcessingState(UDCClientProcessingState::kPromptingUser);
            client->SetPeerAddress(source);

            id.UpdateClientState(client);

            // Call the registered mUserConfirmationProvider, if any.
            if (mUserConfirmationProvider != nullptr)
            {
                mUserConfirmationProvider->OnUserDirectedCommissioningRequest(*client);
            }
            return;
        }

        // Call the registered InstanceNameResolver, if any.
        if (mInstanceNameResolver != nullptr)
        {
            mInstanceNameResolver->FindCommissionableNode(instanceName);
        }
        else
        {
            ChipLogError(AppServer, "UserDirectedCommissioningServer::OnMessageReceived no mInstanceNameResolver registered");
        }
    }
    mUdcClients.MarkUDCClientActive(client);
}

void UserDirectedCommissioningServer::HandleUDCCancel(IdentificationDeclaration & id)
{
    char * instanceName = (char *) id.GetInstanceName();
    ChipLogProgress(AppServer, "HandleUDCCancel instance=%s ", id.GetInstanceName());

    UDCClientState * client = mUdcClients.FindUDCClientState(instanceName);
    if (client == nullptr)
    {
        ChipLogProgress(AppServer, "UDC no matching instance found");
        return;
    }
    id.DebugLog();
    mUdcClients.MarkUDCClientActive(client);

    // Call the registered mUserConfirmationProvider, if any.
    if (mUserConfirmationProvider != nullptr)
    {
        mUserConfirmationProvider->OnCancel(*client);
    }

    // reset this entry so that the client can try again without waiting an hour
    client->Reset();
}

void UserDirectedCommissioningServer::HandleUDCCommissionerPasscodeReady(IdentificationDeclaration & id)
{
    char * instanceName = (char *) id.GetInstanceName();
    ChipLogProgress(AppServer, "HandleUDCCommissionerPasscodeReady instance=%s ", id.GetInstanceName());

    UDCClientState * client = mUdcClients.FindUDCClientState(instanceName);
    if (client == nullptr)
    {
        ChipLogProgress(AppServer, "UDC no matching instance found");
        return;
    }
    if (client->GetUDCClientProcessingState() != UDCClientProcessingState::kWaitingForCommissionerPasscodeReady)
    {
        ChipLogProgress(AppServer, "UDC instance not in waiting for passcode ready state");
        return;
    }
    id.DebugLog();
    mUdcClients.MarkUDCClientActive(client);
    client->SetUDCClientProcessingState(UDCClientProcessingState::kObtainingOnboardingPayload);

    // Call the registered mUserConfirmationProvider, if any.
    if (mUserConfirmationProvider != nullptr)
    {
        mUserConfirmationProvider->OnCommissionerPasscodeReady(*client);
    }
}

CHIP_ERROR UserDirectedCommissioningServer::SendCDCMessage(CommissionerDeclaration cd, chip::Transport::PeerAddress peerAddress)
{
    if (mTransportMgr == nullptr)
    {
        ChipLogError(AppServer, "CDC: No transport manager\n");
        return CHIP_ERROR_INCORRECT_STATE;
    }
    uint8_t idBuffer[IdentificationDeclaration::kUdcTLVDataMaxBytes];
    uint32_t length = cd.WritePayload(idBuffer, sizeof(idBuffer));
    if (length == 0)
    {
        ChipLogError(AppServer, "CDC: error writing payload\n");
        return CHIP_ERROR_INTERNAL;
    }

    chip::System::PacketBufferHandle payload = chip::MessagePacketBuffer::NewWithData(idBuffer, length);
    if (payload.IsNull())
    {
        ChipLogError(AppServer, "Unable to allocate packet buffer\n");
        return CHIP_ERROR_NO_MEMORY;
    }
    ReturnErrorOnFailure(EncodeUDCMessage(payload));

    cd.DebugLog();
    ChipLogProgress(Inet, "Sending CDC msg");

    auto err = mTransportMgr->SendMessage(peerAddress, std::move(payload));
    if (err != CHIP_NO_ERROR)
    {
        ChipLogError(AppServer, "CDC SendMessage failed: %" CHIP_ERROR_FORMAT, err.Format());
        return err;
    }

    ChipLogProgress(Inet, "CDC msg sent");
    return CHIP_NO_ERROR;
}

CHIP_ERROR UserDirectedCommissioningServer::EncodeUDCMessage(const System::PacketBufferHandle & payload)
{
    PayloadHeader payloadHeader;
    PacketHeader packetHeader;

    payloadHeader.SetMessageType(MsgType::IdentificationDeclaration).SetInitiator(true).SetNeedsAck(false);

    VerifyOrReturnError(!payload.IsNull(), CHIP_ERROR_INVALID_ARGUMENT);
    VerifyOrReturnError(!payload->HasChainedBuffer(), CHIP_ERROR_INVALID_MESSAGE_LENGTH);
    VerifyOrReturnError(payload->TotalLength() <= kMaxAppMessageLen, CHIP_ERROR_MESSAGE_TOO_LONG);

    ReturnErrorOnFailure(payloadHeader.EncodeBeforeData(payload));

    ReturnErrorOnFailure(packetHeader.EncodeBeforeData(payload));

    return CHIP_NO_ERROR;
}

CHIP_ERROR IdentificationDeclaration::ReadPayload(uint8_t * udcPayload, size_t payloadBufferSize)
{
    size_t i = 0;
    while (i < std::min<size_t>(sizeof(mInstanceName), payloadBufferSize) && udcPayload[i] != '\0')
    {
        mInstanceName[i] = (char) udcPayload[i];
        i++;
    }
    mInstanceName[i] = '\0';

    if (payloadBufferSize <= sizeof(mInstanceName))
    {
        ChipLogProgress(AppServer, "UDC - No TLV information in Identification Declaration");
        return CHIP_NO_ERROR;
    }
    // advance i to the end of the fixed length block containing instance name
    i = sizeof(mInstanceName);

    CHIP_ERROR err;

    TLV::TLVReader reader;
    reader.Init(udcPayload + i, payloadBufferSize - i);

    // read the envelope
    ReturnErrorOnFailure(reader.Next(chip::TLV::kTLVType_Structure, chip::TLV::AnonymousTag()));

    chip::TLV::TLVType outerContainerType = chip::TLV::kTLVType_Structure;
    ReturnErrorOnFailure(reader.EnterContainer(outerContainerType));

    while ((err = reader.Next()) == CHIP_NO_ERROR)
    {
        chip::TLV::Tag containerTag = reader.GetTag();
        if (!TLV::IsContextTag(containerTag))
        {
            ChipLogError(AppServer, "Unexpected non-context TLV tag.");
            return CHIP_ERROR_INVALID_TLV_TAG;
        }
        uint8_t tagNum = static_cast<uint8_t>(chip::TLV::TagNumFromTag(containerTag));

        switch (tagNum)
        {
        case kVendorIdTag:
            // vendorId
            err = reader.Get(mVendorId);
            break;
        case kProductIdTag:
            // productId
            err = reader.Get(mProductId);
            break;
        case kCdPortTag:
            // port
            err = reader.Get(mCdPort);
            break;
        case kDeviceNameTag:
            // deviceName
            err = reader.GetString(mDeviceName, sizeof(mDeviceName));
            break;
        case kPairingInstTag:
            // pairingInst
            err = reader.GetString(mPairingInst, sizeof(mPairingInst));
            break;
        case kPairingHintTag:
            // pairingHint
            err = reader.Get(mPairingHint);
            break;
        case kRotatingIdTag:
            // rotatingId
            mRotatingIdLen = reader.GetLength();
            err            = reader.GetBytes(mRotatingId, sizeof(mRotatingId));
            break;
        case kTargetAppListTag:
            // app vendor list
            {
                ChipLogProgress(AppServer, "TLV found an applist");
                chip::TLV::TLVType listContainerType = chip::TLV::kTLVType_List;
                ReturnErrorOnFailure(reader.EnterContainer(listContainerType));

                while ((err = reader.Next()) == CHIP_NO_ERROR && mNumTargetAppInfos < sizeof(mTargetAppInfos))
                {
                    containerTag = reader.GetTag();
                    if (!TLV::IsContextTag(containerTag))
                    {
                        ChipLogError(AppServer, "Unexpected non-context TLV tag.");
                        return CHIP_ERROR_INVALID_TLV_TAG;
                    }
                    tagNum = static_cast<uint8_t>(chip::TLV::TagNumFromTag(containerTag));
                    if (tagNum == kTargetAppTag)
                    {
                        ReturnErrorOnFailure(reader.EnterContainer(outerContainerType));
                        uint16_t appVendorId  = 0;
                        uint16_t appProductId = 0;

                        while ((err = reader.Next()) == CHIP_NO_ERROR)
                        {
                            containerTag = reader.GetTag();
                            if (!TLV::IsContextTag(containerTag))
                            {
                                ChipLogError(AppServer, "Unexpected non-context TLV tag.");
                                return CHIP_ERROR_INVALID_TLV_TAG;
                            }
                            tagNum = static_cast<uint8_t>(chip::TLV::TagNumFromTag(containerTag));
                            if (tagNum == kAppVendorIdTag)
                            {
                                err = reader.Get(appVendorId);
                            }
                            else if (tagNum == kAppProductIdTag)
                            {
                                err = reader.Get(appProductId);
                            }
                        }
                        if (err == CHIP_END_OF_TLV)
                        {
                            ChipLogProgress(AppServer, "TLV end of struct TLV");
                            ReturnErrorOnFailure(reader.ExitContainer(outerContainerType));
                        }
                        if (appVendorId != 0)
                        {
                            mTargetAppInfos[mNumTargetAppInfos].vendorId  = appVendorId;
                            mTargetAppInfos[mNumTargetAppInfos].productId = appProductId;
                            mNumTargetAppInfos++;
                        }
                    }
                    else
                    {
                        ChipLogError(AppServer, "unrecognized tag %d", tagNum);
                    }
                }
                if (err == CHIP_END_OF_TLV)
                {
                    ChipLogProgress(AppServer, "TLV end of array");
                    ReturnErrorOnFailure(reader.ExitContainer(listContainerType));
                    err = CHIP_NO_ERROR;
                }
            }
            break;
        case kNoPasscodeTag:
            err = reader.Get(mNoPasscode);
            break;
        case kCdUponPasscodeDialogTag:
            err = reader.Get(mCdUponPasscodeDialog);
            break;
        case kCommissionerPasscodeTag:
            err = reader.Get(mCommissionerPasscode);
            break;
        case kCommissionerPasscodeReadyTag:
            err = reader.Get(mCommissionerPasscodeReady);
            break;
        case kCancelPasscodeTag:
            err = reader.Get(mCancelPasscode);
            break;
        }
        if (err != CHIP_NO_ERROR)
        {
            ChipLogError(AppServer, "IdentificationDeclaration::ReadPayload read error %" CHIP_ERROR_FORMAT, err.Format());
        }
    }

    if (err == CHIP_END_OF_TLV)
    {
        // Exiting container
        ReturnErrorOnFailure(reader.ExitContainer(outerContainerType));
    }
    else
    {
        ChipLogError(AppServer, "IdentificationDeclaration::ReadPayload exiting early error %" CHIP_ERROR_FORMAT, err.Format());
    }

    ChipLogProgress(AppServer, "UDC TLV parse complete");
    return CHIP_NO_ERROR;
}

/**
 *  Reset the connection state to a completely uninitialized status.
 */
uint32_t CommissionerDeclaration::WritePayload(uint8_t * payloadBuffer, size_t payloadBufferSize)
{
    CHIP_ERROR err;

    chip::TLV::TLVWriter writer;

    writer.Init(payloadBuffer, payloadBufferSize);

    chip::TLV::TLVType outerContainerType = chip::TLV::kTLVType_Structure;
    VerifyOrExit(CHIP_NO_ERROR ==
                     (err = writer.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, outerContainerType)),
                 LogErrorOnFailure(err));

    VerifyOrExit(CHIP_NO_ERROR == (err = writer.Put(chip::TLV::ContextTag(kErrorCodeTag), GetErrorCode())), LogErrorOnFailure(err));
    VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kNeedsPasscodeTag), mNeedsPasscode)),
                 LogErrorOnFailure(err));
    VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kNoAppsFoundTag), mNoAppsFound)),
                 LogErrorOnFailure(err));
    VerifyOrExit(CHIP_NO_ERROR ==
                     (err = writer.PutBoolean(chip::TLV::ContextTag(kPasscodeDialogDisplayedTag), mPasscodeDialogDisplayed)),
                 LogErrorOnFailure(err));
    VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kCommissionerPasscodeTag), mCommissionerPasscode)),
                 LogErrorOnFailure(err));
    VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kQRCodeDisplayedTag), mQRCodeDisplayed)),
                 LogErrorOnFailure(err));
    VerifyOrExit(CHIP_NO_ERROR == (err = writer.PutBoolean(chip::TLV::ContextTag(kCancelPasscodeTag), mCancelPasscode)),
                 LogErrorOnFailure(err));

    VerifyOrExit(CHIP_NO_ERROR == (err = writer.EndContainer(outerContainerType)), LogErrorOnFailure(err));
    VerifyOrExit(CHIP_NO_ERROR == (err = writer.Finalize()), LogErrorOnFailure(err));

    ChipLogProgress(AppServer, "TLV write done");

    return writer.GetLengthWritten();

exit:
    return 0;
}

void UserDirectedCommissioningServer::SetUDCClientProcessingState(char * instanceName, UDCClientProcessingState state)
{
    UDCClientState * client = mUdcClients.FindUDCClientState(instanceName);
    if (client == nullptr)
    {
        CHIP_ERROR err;
        err = mUdcClients.CreateNewUDCClientState(instanceName, &client);
        if (err != CHIP_NO_ERROR)
        {
            ChipLogError(AppServer,
                         "UserDirectedCommissioningServer::SetUDCClientProcessingState error creating new connection state");
            return;
        }
    }

    ChipLogDetail(AppServer, "SetUDCClientProcessingState instance=%s new state=%d", StringOrNullMarker(instanceName), (int) state);

    client->SetUDCClientProcessingState(state);

    mUdcClients.MarkUDCClientActive(client);
}

void UserDirectedCommissioningServer::OnCommissionableNodeFound(const Dnssd::DiscoveredNodeData & discNodeData)
{
    if (!discNodeData.Is<Dnssd::CommissionNodeData>())
    {
        return;
    }

    const Dnssd::CommissionNodeData & nodeData = discNodeData.Get<Dnssd::CommissionNodeData>();
    if (nodeData.numIPs == 0)
    {
        ChipLogError(AppServer, "OnCommissionableNodeFound no IP addresses returned for instance name=%s", nodeData.instanceName);
        return;
    }
    if (nodeData.port == 0)
    {
        ChipLogError(AppServer, "OnCommissionableNodeFound no port returned for instance name=%s", nodeData.instanceName);
        return;
    }

    UDCClientState * client = mUdcClients.FindUDCClientState(nodeData.instanceName);
    if (client != nullptr && client->GetUDCClientProcessingState() == UDCClientProcessingState::kDiscoveringNode)
    {
        ChipLogDetail(AppServer, "OnCommissionableNodeFound instance: name=%s old_state=%d new_state=%d", client->GetInstanceName(),
                      (int) client->GetUDCClientProcessingState(), (int) UDCClientProcessingState::kPromptingUser);
        client->SetUDCClientProcessingState(UDCClientProcessingState::kPromptingUser);

#if INET_CONFIG_ENABLE_IPV4
        // prefer IPv4 if its an option
        bool foundV4 = false;
        for (unsigned i = 0; i < nodeData.numIPs; ++i)
        {
            if (nodeData.ipAddress[i].IsIPv4())
            {
                foundV4 = true;
                client->SetPeerAddress(chip::Transport::PeerAddress::UDP(nodeData.ipAddress[i], nodeData.port));
                break;
            }
        }
        // use IPv6 as last resort
        if (!foundV4)
        {
            client->SetPeerAddress(chip::Transport::PeerAddress::UDP(nodeData.ipAddress[0], nodeData.port));
        }
#else  // INET_CONFIG_ENABLE_IPV4
       // if we only support V6, then try to find a v6 address
        bool foundV6 = false;
        for (unsigned i = 0; i < nodeData.numIPs; ++i)
        {
            if (nodeData.ipAddress[i].IsIPv6())
            {
                foundV6 = true;
                client->SetPeerAddress(chip::Transport::PeerAddress::UDP(nodeData.ipAddress[i], nodeData.port));
                break;
            }
        }
        // last resort, try with what we have
        if (!foundV6)
        {
            ChipLogError(AppServer, "OnCommissionableNodeFound no v6 returned for instance name=%s", nodeData.instanceName);
            client->SetPeerAddress(chip::Transport::PeerAddress::UDP(nodeData.ipAddress[0], nodeData.port));
        }
#endif // INET_CONFIG_ENABLE_IPV4

        client->SetDeviceName(nodeData.deviceName);
        client->SetLongDiscriminator(nodeData.longDiscriminator);
        client->SetVendorId(nodeData.vendorId);
        client->SetProductId(nodeData.productId);
        client->SetRotatingId(nodeData.rotatingId, nodeData.rotatingIdLen);

        // Call the registered mUserConfirmationProvider, if any.
        if (mUserConfirmationProvider != nullptr)
        {
            mUserConfirmationProvider->OnUserDirectedCommissioningRequest(*client);
        }
    }
}

void UserDirectedCommissioningServer::PrintUDCClients()
{
    for (uint8_t i = 0; i < kMaxUDCClients; i++)
    {
        UDCClientState * state = GetUDCClients().GetUDCClientState(i);
        if (state == nullptr)
        {
            ChipLogProgress(AppServer, "UDC Client[%d] null", i);
        }
        else
        {
            char addrBuffer[chip::Transport::PeerAddress::kMaxToStringSize];
            state->GetPeerAddress().ToString(addrBuffer);

            char rotatingIdString[chip::Dnssd::kMaxRotatingIdLen * 2 + 1] = "";
            Encoding::BytesToUppercaseHexString(state->GetRotatingId(), chip::Dnssd::kMaxRotatingIdLen, rotatingIdString,
                                                sizeof(rotatingIdString));

            ChipLogProgress(AppServer, "UDC Client[%d] instance=%s deviceName=%s address=%s, vid/pid=%d/%d disc=%d rid=%s", i,
                            state->GetInstanceName(), state->GetDeviceName(), addrBuffer, state->GetVendorId(),
                            state->GetProductId(), state->GetLongDiscriminator(), rotatingIdString);
        }
    }
}

} // namespace UserDirectedCommissioning
} // namespace Protocols
} // namespace chip
