/*
 *  Copyright (c) 2016, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file implements a MeshCoP Leader.
 */

#include "meshcop_leader.hpp"

#if OPENTHREAD_FTD

#include "instance/instance.hpp"

namespace ot {
namespace MeshCoP {

RegisterLogModule("MeshCoPLeader");

Leader::Leader(Instance &aInstance)
    : InstanceLocator(aInstance)
    , mTimer(aInstance)
    , mSessionId(Random::NonCrypto::Generate<uint16_t>())
{
}

template <> void Leader::HandleTmf<kUriLeaderPetition>(Coap::Msg &aMsg)
{
    CommissioningData             data;
    CommissionerIdTlv::StringType commissionerId;
    StateTlv::State               state = StateTlv::kReject;

    LogInfo("Received %s", UriToString<kUriLeaderPetition>());

    VerifyOrExit(Get<Mle::Mle>().IsLeader());

    VerifyOrExit(Get<Mle::Mle>().IsRoutingLocator(aMsg.mMessageInfo.GetPeerAddr()));

    SuccessOrExit(Tlv::Find<CommissionerIdTlv>(aMsg.mMessage, commissionerId));

    if (mTimer.IsRunning())
    {
        VerifyOrExit(StringMatch(mCommissionerId, commissionerId));
        ResignCommissioner();
    }

    data.Init(aMsg.mMessageInfo.GetPeerAddr().GetIid().GetLocator(), ++mSessionId);
    SuccessOrExit(Get<NetworkData::Leader>().SetCommissioningData(&data, data.GetLength()));

    IgnoreError(StringCopy(mCommissionerId, commissionerId));

    state = StateTlv::kAccept;
    mTimer.Start(kLeaderPetitionTimeout);

exit:
    SendPetitionResponse(aMsg, state);
}

void Leader::SendPetitionResponse(const Coap::Msg &aMsg, StateTlv::State aState)
{
    Error          error = kErrorNone;
    Coap::Message *message;

    message = Get<Tmf::Agent>().AllocateAndInitPriorityResponseFor(aMsg.mMessage);
    VerifyOrExit(message != nullptr, error = kErrorNoBufs);

    SuccessOrExit(error = Tlv::Append<StateTlv>(*message, aState));

    if (mTimer.IsRunning())
    {
        SuccessOrExit(error = Tlv::Append<CommissionerIdTlv>(*message, mCommissionerId));
    }

    if (aState == StateTlv::kAccept)
    {
        SuccessOrExit(error = Tlv::Append<CommissionerSessionIdTlv>(*message, mSessionId));
    }

    SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, aMsg.mMessageInfo));

    LogInfo("Sent %s response", UriToString<kUriLeaderPetition>());

exit:
    FreeMessageOnError(message, error);
    LogWarnOnError(error, "send petition response");
}

template <> void Leader::HandleTmf<kUriLeaderKeepAlive>(Coap::Msg &aMsg)
{
    uint8_t                state;
    uint16_t               sessionId;
    BorderAgentLocatorTlv *borderAgentLocator;
    StateTlv::State        responseState;

    LogInfo("Received %s", UriToString<kUriLeaderKeepAlive>());

    VerifyOrExit(Get<Mle::Mle>().IsLeader());

    SuccessOrExit(Tlv::Find<StateTlv>(aMsg.mMessage, state));

    SuccessOrExit(Tlv::Find<CommissionerSessionIdTlv>(aMsg.mMessage, sessionId));

    borderAgentLocator = Get<NetworkData::Leader>().FindInCommissioningData<BorderAgentLocatorTlv>();

    if ((borderAgentLocator == nullptr) || (sessionId != mSessionId))
    {
        responseState = StateTlv::kReject;
    }
    else if (state != StateTlv::kAccept)
    {
        responseState = StateTlv::kReject;
        ResignCommissioner();
    }
    else
    {
        uint16_t rloc = aMsg.mMessageInfo.GetPeerAddr().GetIid().GetLocator();

        if (borderAgentLocator->GetBorderAgentLocator() != rloc)
        {
            borderAgentLocator->SetBorderAgentLocator(rloc);
            Get<NetworkData::Leader>().IncrementVersion();
        }

        responseState = StateTlv::kAccept;
        mTimer.Start(kLeaderPetitionTimeout);
    }

    SendKeepAliveResponse(aMsg, responseState);

exit:
    return;
}

void Leader::SendKeepAliveResponse(const Coap::Msg &aMsg, StateTlv::State aState)
{
    Error          error = kErrorNone;
    Coap::Message *message;

    message = Get<Tmf::Agent>().AllocateAndInitPriorityResponseFor(aMsg.mMessage);
    VerifyOrExit(message != nullptr, error = kErrorNoBufs);

    SuccessOrExit(error = Tlv::Append<StateTlv>(*message, aState));

    SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, aMsg.mMessageInfo));

    LogInfo("Sent %s response", UriToString<kUriLeaderKeepAlive>());

exit:
    FreeMessageOnError(message, error);
    LogWarnOnError(error, "send keep alive response");
}

void Leader::SendDatasetChanged(const Ip6::Address &aAddress)
{
    Error          error = kErrorNone;
    Coap::Message *message;

    message = Get<Tmf::Agent>().AllocateAndInitPriorityConfirmablePostMessage(kUriDatasetChanged);
    VerifyOrExit(message != nullptr, error = kErrorNoBufs);

    SuccessOrExit(error = Get<Tmf::Agent>().SendMessageTo(*message, aAddress));

    LogInfo("Sent %s", UriToString<kUriDatasetChanged>());

exit:
    FreeMessageOnError(message, error);
    LogWarnOnError(error, "send dataset changed");
}

void Leader::HandleTimer(void)
{
    VerifyOrExit(Get<Mle::Mle>().IsLeader());

    ResignCommissioner();

exit:
    return;
}

void Leader::SetEmptyCommissionerData(void)
{
    CommissionerSessionIdTlv sessionIdTlv;

    sessionIdTlv.Init();
    sessionIdTlv.SetCommissionerSessionId(++mSessionId);

    IgnoreError(Get<NetworkData::Leader>().SetCommissioningData(&sessionIdTlv, sizeof(CommissionerSessionIdTlv)));
}

void Leader::ResignCommissioner(void)
{
    mTimer.Stop();
    SetEmptyCommissionerData();

    LogInfo("commissioner inactive");
}

void Leader::CommissioningData::Init(uint16_t aBorderAgentRloc16, uint16_t aSessionId)
{
    mBorderAgentLocatorTlv.Init();
    mBorderAgentLocatorTlv.SetBorderAgentLocator(aBorderAgentRloc16);

    mSessionIdTlv.Init();
    mSessionIdTlv.SetCommissionerSessionId(aSessionId);

    mSteeringDataTlv.Init();
    mSteeringDataTlv.SetLength(1);
    mSteeringDataTlv.Clear();
}

uint8_t Leader::CommissioningData::GetLength(void) const
{
    return static_cast<uint8_t>(sizeof(BorderAgentLocatorTlv) + sizeof(CommissionerSessionIdTlv) +
                                mSteeringDataTlv.GetSize());
}

} // namespace MeshCoP
} // namespace ot

#endif // OPENTHREAD_FTD
