/*
 *  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 the CLI interpreter.
 */

#include "cli_dataset.hpp"

#include <stdio.h>
#include <stdlib.h>

#include <openthread/dataset.h>
#include <openthread/dataset_ftd.h>
#include <openthread/dataset_updater.h>

#include "cli/cli.hpp"

namespace ot {
namespace Cli {

otOperationalDataset Dataset::sDataset;

otError Dataset::Print(otOperationalDataset &aDataset)
{
    if (aDataset.mComponents.mIsPendingTimestampPresent)
    {
        OutputLine("Pending Timestamp: %lu", aDataset.mPendingTimestamp.mSeconds);
    }

    if (aDataset.mComponents.mIsActiveTimestampPresent)
    {
        OutputLine("Active Timestamp: %lu", aDataset.mActiveTimestamp.mSeconds);
    }

    if (aDataset.mComponents.mIsChannelPresent)
    {
        OutputLine("Channel: %d", aDataset.mChannel);
    }

    if (aDataset.mComponents.mIsChannelMaskPresent)
    {
        OutputLine("Channel Mask: 0x%08x", aDataset.mChannelMask);
    }

    if (aDataset.mComponents.mIsDelayPresent)
    {
        OutputLine("Delay: %d", aDataset.mDelay);
    }

    if (aDataset.mComponents.mIsExtendedPanIdPresent)
    {
        OutputFormat("Ext PAN ID: ");
        OutputBytesLine(aDataset.mExtendedPanId.m8);
    }

    if (aDataset.mComponents.mIsMeshLocalPrefixPresent)
    {
        OutputFormat("Mesh Local Prefix: ");
        OutputIp6PrefixLine(aDataset.mMeshLocalPrefix);
    }

    if (aDataset.mComponents.mIsNetworkKeyPresent)
    {
        OutputFormat("Network Key: ");
        OutputBytesLine(aDataset.mNetworkKey.m8);
    }

    if (aDataset.mComponents.mIsNetworkNamePresent)
    {
        OutputFormat("Network Name: ");
        OutputLine("%s", aDataset.mNetworkName.m8);
    }

    if (aDataset.mComponents.mIsPanIdPresent)
    {
        OutputLine("PAN ID: 0x%04x", aDataset.mPanId);
    }

    if (aDataset.mComponents.mIsPskcPresent)
    {
        OutputFormat("PSKc: ");
        OutputBytesLine(aDataset.mPskc.m8);
    }

    if (aDataset.mComponents.mIsSecurityPolicyPresent)
    {
        OutputFormat("Security Policy: ");
        OutputSecurityPolicy(aDataset.mSecurityPolicy);
    }

    return OT_ERROR_NONE;
}

template <> otError Dataset::Process<Cmd("init")>(Arg aArgs[])
{
    otError error = OT_ERROR_INVALID_ARGS;

    if (aArgs[0] == "active")
    {
        error = otDatasetGetActive(GetInstancePtr(), &sDataset);
    }
    else if (aArgs[0] == "pending")
    {
        error = otDatasetGetPending(GetInstancePtr(), &sDataset);
    }
#if OPENTHREAD_FTD
    else if (aArgs[0] == "new")
    {
        error = otDatasetCreateNewNetwork(GetInstancePtr(), &sDataset);
    }
#endif
    else if (aArgs[0] == "tlvs")
    {
        otOperationalDatasetTlvs datasetTlvs;
        uint16_t                 size = sizeof(datasetTlvs.mTlvs);

        SuccessOrExit(error = aArgs[1].ParseAsHexString(size, datasetTlvs.mTlvs));
        datasetTlvs.mLength = static_cast<uint8_t>(size);

        SuccessOrExit(error = otDatasetParseTlvs(&datasetTlvs, &sDataset));
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("active")>(Arg aArgs[])
{
    otError error = OT_ERROR_INVALID_ARGS;

    if (aArgs[0].IsEmpty())
    {
        otOperationalDataset dataset;

        SuccessOrExit(error = otDatasetGetActive(GetInstancePtr(), &dataset));
        error = Print(dataset);
    }
    else if (aArgs[0] == "-x")
    {
        otOperationalDatasetTlvs dataset;

        SuccessOrExit(error = otDatasetGetActiveTlvs(GetInstancePtr(), &dataset));
        OutputBytesLine(dataset.mTlvs, dataset.mLength);
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("pending")>(Arg aArgs[])
{
    otError error = OT_ERROR_INVALID_ARGS;

    if (aArgs[0].IsEmpty())
    {
        otOperationalDataset dataset;

        SuccessOrExit(error = otDatasetGetPending(GetInstancePtr(), &dataset));
        error = Print(dataset);
    }
    else if (aArgs[0] == "-x")
    {
        otOperationalDatasetTlvs dataset;

        SuccessOrExit(error = otDatasetGetPendingTlvs(GetInstancePtr(), &dataset));
        OutputBytesLine(dataset.mTlvs, dataset.mLength);
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("activetimestamp")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsActiveTimestampPresent)
        {
            OutputLine("%lu", sDataset.mActiveTimestamp.mSeconds);
        }
    }
    else
    {
        SuccessOrExit(error = aArgs[0].ParseAsUint64(sDataset.mActiveTimestamp.mSeconds));
        sDataset.mActiveTimestamp.mTicks               = 0;
        sDataset.mActiveTimestamp.mAuthoritative       = false;
        sDataset.mComponents.mIsActiveTimestampPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("channel")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsChannelPresent)
        {
            OutputLine("%d", sDataset.mChannel);
        }
    }
    else
    {
        SuccessOrExit(error = aArgs[0].ParseAsUint16(sDataset.mChannel));
        sDataset.mComponents.mIsChannelPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("channelmask")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsChannelMaskPresent)
        {
            OutputLine("0x%08x", sDataset.mChannelMask);
        }
    }
    else
    {
        SuccessOrExit(error = aArgs[0].ParseAsUint32(sDataset.mChannelMask));
        sDataset.mComponents.mIsChannelMaskPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("clear")>(Arg aArgs[])
{
    OT_UNUSED_VARIABLE(aArgs);

    memset(&sDataset, 0, sizeof(sDataset));
    return OT_ERROR_NONE;
}

template <> otError Dataset::Process<Cmd("commit")>(Arg aArgs[])
{
    otError error = OT_ERROR_INVALID_ARGS;

    if (aArgs[0] == "active")
    {
        error = otDatasetSetActive(GetInstancePtr(), &sDataset);
    }
    else if (aArgs[0] == "pending")
    {
        error = otDatasetSetPending(GetInstancePtr(), &sDataset);
    }

    return error;
}

template <> otError Dataset::Process<Cmd("delay")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsDelayPresent)
        {
            OutputLine("%d", sDataset.mDelay);
        }
    }
    else
    {
        SuccessOrExit(error = aArgs[0].ParseAsUint32(sDataset.mDelay));
        sDataset.mComponents.mIsDelayPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("extpanid")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsExtendedPanIdPresent)
        {
            OutputBytesLine(sDataset.mExtendedPanId.m8);
        }
    }
    else
    {
        SuccessOrExit(error = aArgs[0].ParseAsHexString(sDataset.mExtendedPanId.m8));
        sDataset.mComponents.mIsExtendedPanIdPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("meshlocalprefix")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsMeshLocalPrefixPresent)
        {
            OutputFormat("Mesh Local Prefix: ");
            OutputIp6PrefixLine(sDataset.mMeshLocalPrefix);
        }
    }
    else
    {
        otIp6Address prefix;

        SuccessOrExit(error = aArgs[0].ParseAsIp6Address(prefix));

        memcpy(sDataset.mMeshLocalPrefix.m8, prefix.mFields.m8, sizeof(sDataset.mMeshLocalPrefix.m8));
        sDataset.mComponents.mIsMeshLocalPrefixPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("networkkey")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsNetworkKeyPresent)
        {
            OutputBytesLine(sDataset.mNetworkKey.m8);
        }
    }
    else
    {
        SuccessOrExit(error = aArgs[0].ParseAsHexString(sDataset.mNetworkKey.m8));
        sDataset.mComponents.mIsNetworkKeyPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("networkname")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsNetworkNamePresent)
        {
            OutputLine("%s", sDataset.mNetworkName.m8);
        }
    }
    else
    {
        SuccessOrExit(error = otNetworkNameFromString(&sDataset.mNetworkName, aArgs[0].GetCString()));
        sDataset.mComponents.mIsNetworkNamePresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("panid")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsPanIdPresent)
        {
            OutputLine("0x%04x", sDataset.mPanId);
        }
    }
    else
    {
        SuccessOrExit(error = aArgs[0].ParseAsUint16(sDataset.mPanId));
        sDataset.mComponents.mIsPanIdPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("pendingtimestamp")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsPendingTimestampPresent)
        {
            OutputLine("%lu", sDataset.mPendingTimestamp.mSeconds);
        }
    }
    else
    {
        SuccessOrExit(error = aArgs[0].ParseAsUint64(sDataset.mPendingTimestamp.mSeconds));
        sDataset.mPendingTimestamp.mTicks               = 0;
        sDataset.mPendingTimestamp.mAuthoritative       = false;
        sDataset.mComponents.mIsPendingTimestampPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("mgmtsetcommand")>(Arg aArgs[])
{
    otError              error = OT_ERROR_NONE;
    otOperationalDataset dataset;
    uint8_t              tlvs[128];
    uint8_t              tlvsLength = 0;

    memset(&dataset, 0, sizeof(dataset));

    for (Arg *arg = &aArgs[1]; !arg->IsEmpty(); arg++)
    {
        if (*arg == "activetimestamp")
        {
            arg++;
            SuccessOrExit(error = arg->ParseAsUint64(dataset.mActiveTimestamp.mSeconds));
            dataset.mActiveTimestamp.mTicks               = 0;
            dataset.mActiveTimestamp.mAuthoritative       = false;
            dataset.mComponents.mIsActiveTimestampPresent = true;
        }
        else if (*arg == "pendingtimestamp")
        {
            arg++;
            SuccessOrExit(error = arg->ParseAsUint64(dataset.mPendingTimestamp.mSeconds));
            dataset.mPendingTimestamp.mTicks               = 0;
            dataset.mPendingTimestamp.mAuthoritative       = false;
            dataset.mComponents.mIsPendingTimestampPresent = true;
        }
        else if (*arg == "networkkey")
        {
            arg++;
            dataset.mComponents.mIsNetworkKeyPresent = true;
            SuccessOrExit(error = arg->ParseAsHexString(dataset.mNetworkKey.m8));
        }
        else if (*arg == "networkname")
        {
            arg++;
            dataset.mComponents.mIsNetworkNamePresent = true;
            VerifyOrExit(!arg->IsEmpty(), error = OT_ERROR_INVALID_ARGS);
            SuccessOrExit(error = otNetworkNameFromString(&dataset.mNetworkName, arg->GetCString()));
        }
        else if (*arg == "extpanid")
        {
            arg++;
            dataset.mComponents.mIsExtendedPanIdPresent = true;
            SuccessOrExit(error = arg->ParseAsHexString(dataset.mExtendedPanId.m8));
        }
        else if (*arg == "localprefix")
        {
            otIp6Address prefix;

            arg++;
            dataset.mComponents.mIsMeshLocalPrefixPresent = true;
            SuccessOrExit(error = arg->ParseAsIp6Address(prefix));
            memcpy(dataset.mMeshLocalPrefix.m8, prefix.mFields.m8, sizeof(dataset.mMeshLocalPrefix.m8));
        }
        else if (*arg == "delaytimer")
        {
            arg++;
            dataset.mComponents.mIsDelayPresent = true;
            SuccessOrExit(error = arg->ParseAsUint32(dataset.mDelay));
        }
        else if (*arg == "panid")
        {
            arg++;
            dataset.mComponents.mIsPanIdPresent = true;
            SuccessOrExit(error = arg->ParseAsUint16(dataset.mPanId));
        }
        else if (*arg == "channel")
        {
            arg++;
            dataset.mComponents.mIsChannelPresent = true;
            SuccessOrExit(error = arg->ParseAsUint16(dataset.mChannel));
        }
        else if (*arg == "channelmask")
        {
            arg++;
            dataset.mComponents.mIsChannelMaskPresent = true;
            SuccessOrExit(error = arg->ParseAsUint32(dataset.mChannelMask));
        }
        else if (*arg == "securitypolicy")
        {
            arg++;
            SuccessOrExit(error = ParseSecurityPolicy(dataset.mSecurityPolicy, arg));
            dataset.mComponents.mIsSecurityPolicyPresent = true;
        }
        else if (*arg == "-x")
        {
            uint16_t length;

            arg++;
            length = sizeof(tlvs);
            SuccessOrExit(error = arg->ParseAsHexString(length, tlvs));
            tlvsLength = static_cast<uint8_t>(length);
        }
        else
        {
            ExitNow(error = OT_ERROR_INVALID_ARGS);
        }
    }

    if (aArgs[0] == "active")
    {
        error = otDatasetSendMgmtActiveSet(GetInstancePtr(), &dataset, tlvs, tlvsLength, /* aCallback */ nullptr,
                                           /* aContext */ nullptr);
    }
    else if (aArgs[0] == "pending")
    {
        error = otDatasetSendMgmtPendingSet(GetInstancePtr(), &dataset, tlvs, tlvsLength, /* aCallback */ nullptr,
                                            /* aContext */ nullptr);
    }
    else
    {
        error = OT_ERROR_INVALID_ARGS;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("mgmtgetcommand")>(Arg aArgs[])
{
    otError                        error = OT_ERROR_NONE;
    otOperationalDatasetComponents datasetComponents;
    uint8_t                        tlvs[32];
    uint8_t                        tlvsLength        = 0;
    bool                           destAddrSpecified = false;
    otIp6Address                   address;

    memset(&datasetComponents, 0, sizeof(datasetComponents));

    for (Arg *arg = &aArgs[1]; !arg->IsEmpty(); arg++)
    {
        if (*arg == "activetimestamp")
        {
            datasetComponents.mIsActiveTimestampPresent = true;
        }
        else if (*arg == "pendingtimestamp")
        {
            datasetComponents.mIsPendingTimestampPresent = true;
        }
        else if (*arg == "networkkey")
        {
            datasetComponents.mIsNetworkKeyPresent = true;
        }
        else if (*arg == "networkname")
        {
            datasetComponents.mIsNetworkNamePresent = true;
        }
        else if (*arg == "extpanid")
        {
            datasetComponents.mIsExtendedPanIdPresent = true;
        }
        else if (*arg == "localprefix")
        {
            datasetComponents.mIsMeshLocalPrefixPresent = true;
        }
        else if (*arg == "delaytimer")
        {
            datasetComponents.mIsDelayPresent = true;
        }
        else if (*arg == "panid")
        {
            datasetComponents.mIsPanIdPresent = true;
        }
        else if (*arg == "channel")
        {
            datasetComponents.mIsChannelPresent = true;
        }
        else if (*arg == "securitypolicy")
        {
            datasetComponents.mIsSecurityPolicyPresent = true;
        }
        else if (*arg == "-x")
        {
            uint16_t length;

            arg++;
            length = sizeof(tlvs);
            SuccessOrExit(error = arg->ParseAsHexString(length, tlvs));
            tlvsLength = static_cast<uint8_t>(length);
        }
        else if (*arg == "address")
        {
            arg++;
            SuccessOrExit(error = arg->ParseAsIp6Address(address));
            destAddrSpecified = true;
        }
        else
        {
            ExitNow(error = OT_ERROR_INVALID_ARGS);
        }
    }

    if (aArgs[0] == "active")
    {
        error = otDatasetSendMgmtActiveGet(GetInstancePtr(), &datasetComponents, tlvs, tlvsLength,
                                           destAddrSpecified ? &address : nullptr);
    }
    else if (aArgs[0] == "pending")
    {
        error = otDatasetSendMgmtPendingGet(GetInstancePtr(), &datasetComponents, tlvs, tlvsLength,
                                            destAddrSpecified ? &address : nullptr);
    }
    else
    {
        error = OT_ERROR_INVALID_ARGS;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("pskc")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        // sDataset holds the key as a literal string, we don't
        // need to export it from PSA ITS.
        if (sDataset.mComponents.mIsPskcPresent)
        {
            OutputBytesLine(sDataset.mPskc.m8);
        }
    }
    else
    {
#if OPENTHREAD_FTD
        if (aArgs[0] == "-p")
        {
            VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);

            SuccessOrExit(
                error = otDatasetGeneratePskc(
                    aArgs[1].GetCString(),
                    (sDataset.mComponents.mIsNetworkNamePresent
                         ? &sDataset.mNetworkName
                         : reinterpret_cast<const otNetworkName *>(otThreadGetNetworkName(GetInstancePtr()))),
                    (sDataset.mComponents.mIsExtendedPanIdPresent ? &sDataset.mExtendedPanId
                                                                  : otThreadGetExtendedPanId(GetInstancePtr())),
                    &sDataset.mPskc));
        }
        else
#endif
        {
            SuccessOrExit(error = aArgs[0].ParseAsHexString(sDataset.mPskc.m8));
        }

        sDataset.mComponents.mIsPskcPresent = true;
    }

exit:
    return error;
}

void Dataset::OutputSecurityPolicy(const otSecurityPolicy &aSecurityPolicy)
{
    OutputFormat("%d ", aSecurityPolicy.mRotationTime);

    if (aSecurityPolicy.mObtainNetworkKeyEnabled)
    {
        OutputFormat("o");
    }

    if (aSecurityPolicy.mNativeCommissioningEnabled)
    {
        OutputFormat("n");
    }

    if (aSecurityPolicy.mRoutersEnabled)
    {
        OutputFormat("r");
    }

    if (aSecurityPolicy.mExternalCommissioningEnabled)
    {
        OutputFormat("c");
    }

    if (aSecurityPolicy.mCommercialCommissioningEnabled)
    {
        OutputFormat("C");
    }

    if (aSecurityPolicy.mAutonomousEnrollmentEnabled)
    {
        OutputFormat("e");
    }

    if (aSecurityPolicy.mNetworkKeyProvisioningEnabled)
    {
        OutputFormat("p");
    }

    if (aSecurityPolicy.mNonCcmRoutersEnabled)
    {
        OutputFormat("R");
    }

    OutputLine("");
}

otError Dataset::ParseSecurityPolicy(otSecurityPolicy &aSecurityPolicy, Arg *&aArgs)
{
    otError          error;
    otSecurityPolicy policy;

    memset(&policy, 0, sizeof(policy));

    SuccessOrExit(error = aArgs->ParseAsUint16(policy.mRotationTime));
    aArgs++;

    VerifyOrExit(!aArgs->IsEmpty());

    for (const char *flag = aArgs->GetCString(); *flag != '\0'; flag++)
    {
        switch (*flag)
        {
        case 'o':
            policy.mObtainNetworkKeyEnabled = true;
            break;

        case 'n':
            policy.mNativeCommissioningEnabled = true;
            break;

        case 'r':
            policy.mRoutersEnabled = true;
            break;

        case 'c':
            policy.mExternalCommissioningEnabled = true;
            break;

        case 'C':
            policy.mCommercialCommissioningEnabled = true;
            break;

        case 'e':
            policy.mAutonomousEnrollmentEnabled = true;
            break;

        case 'p':
            policy.mNetworkKeyProvisioningEnabled = true;
            break;

        case 'R':
            policy.mNonCcmRoutersEnabled = true;
            break;

        default:
            ExitNow(error = OT_ERROR_INVALID_ARGS);
        }
    }

    aArgs++;

exit:
    if (error == OT_ERROR_NONE)
    {
        aSecurityPolicy = policy;
    }

    return error;
}

template <> otError Dataset::Process<Cmd("securitypolicy")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        if (sDataset.mComponents.mIsSecurityPolicyPresent)
        {
            OutputSecurityPolicy(sDataset.mSecurityPolicy);
        }
    }
    else
    {
        Arg *arg = &aArgs[0];

        SuccessOrExit(error = ParseSecurityPolicy(sDataset.mSecurityPolicy, arg));
        sDataset.mComponents.mIsSecurityPolicyPresent = true;
    }

exit:
    return error;
}

template <> otError Dataset::Process<Cmd("set")>(Arg aArgs[])
{
    otError                error = OT_ERROR_NONE;
    MeshCoP::Dataset::Type datasetType;

    if (aArgs[0] == "active")
    {
        datasetType = MeshCoP::Dataset::Type::kActive;
    }
    else if (aArgs[0] == "pending")
    {
        datasetType = MeshCoP::Dataset::Type::kPending;
    }
    else
    {
        ExitNow(error = OT_ERROR_INVALID_ARGS);
    }

    {
        MeshCoP::Dataset       dataset;
        MeshCoP::Dataset::Info datasetInfo;
        uint16_t               tlvsLength = MeshCoP::Dataset::kMaxSize;

        SuccessOrExit(error = aArgs[1].ParseAsHexString(tlvsLength, dataset.GetBytes()));
        dataset.SetSize(tlvsLength);
        VerifyOrExit(dataset.IsValid(), error = OT_ERROR_INVALID_ARGS);
        dataset.ConvertTo(datasetInfo);

        switch (datasetType)
        {
        case MeshCoP::Dataset::Type::kActive:
            SuccessOrExit(error = otDatasetSetActive(GetInstancePtr(), &datasetInfo));
            break;
        case MeshCoP::Dataset::Type::kPending:
            SuccessOrExit(error = otDatasetSetPending(GetInstancePtr(), &datasetInfo));
            break;
        }
    }

exit:
    return error;
}

#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD

template <> otError Dataset::Process<Cmd("updater")>(Arg aArgs[])
{
    otError error = OT_ERROR_NONE;

    if (aArgs[0].IsEmpty())
    {
        OutputEnabledDisabledStatus(otDatasetUpdaterIsUpdateOngoing(GetInstancePtr()));
    }
    else if (aArgs[0] == "start")
    {
        error = otDatasetUpdaterRequestUpdate(GetInstancePtr(), &sDataset, &Dataset::HandleDatasetUpdater, this);
    }
    else if (aArgs[0] == "cancel")
    {
        otDatasetUpdaterCancelUpdate(GetInstancePtr());
    }
    else
    {
        error = OT_ERROR_INVALID_ARGS;
    }

    return error;
}

void Dataset::HandleDatasetUpdater(otError aError, void *aContext)
{
    static_cast<Dataset *>(aContext)->HandleDatasetUpdater(aError);
}

void Dataset::HandleDatasetUpdater(otError aError)
{
    OutputLine("Dataset update complete: %s", otThreadErrorToString(aError));
}

#endif // OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD

otError Dataset::Process(Arg aArgs[])
{
#define CmdEntry(aCommandString)                               \
    {                                                          \
        aCommandString, &Dataset::Process<Cmd(aCommandString)> \
    }

    static constexpr Command kCommands[] = {
        CmdEntry("active"),
        CmdEntry("activetimestamp"),
        CmdEntry("channel"),
        CmdEntry("channelmask"),
        CmdEntry("clear"),
        CmdEntry("commit"),
        CmdEntry("delay"),
        CmdEntry("extpanid"),
        CmdEntry("init"),
        CmdEntry("meshlocalprefix"),
        CmdEntry("mgmtgetcommand"),
        CmdEntry("mgmtsetcommand"),
        CmdEntry("networkkey"),
        CmdEntry("networkname"),
        CmdEntry("panid"),
        CmdEntry("pending"),
        CmdEntry("pendingtimestamp"),
        CmdEntry("pskc"),
        CmdEntry("securitypolicy"),
        CmdEntry("set"),
#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
        CmdEntry("updater"),
#endif
    };

#undef CmdEntry

    static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");

    otError        error = OT_ERROR_INVALID_COMMAND;
    const Command *command;

    if (aArgs[0].IsEmpty())
    {
        ExitNow(error = Print(sDataset));
    }

    if (aArgs[0] == "help")
    {
        OutputCommandTable(kCommands);
        ExitNow(error = OT_ERROR_NONE);
    }

    command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
    VerifyOrExit(command != nullptr);

    error = (this->*command->mHandler)(aArgs + 1);

exit:
    return error;
}

} // namespace Cli
} // namespace ot
