/*
 *  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 includes definitions for MeshCoP.
 */

#ifndef OT_CORE_MESHCOP_MESHCOP_HPP_
#define OT_CORE_MESHCOP_MESHCOP_HPP_

#include "openthread-core-config.h"

#include <openthread/commissioner.h>
#include <openthread/instance.h>
#include <openthread/joiner.h>
#include <openthread/steering_data.h>

#include "coap/coap.hpp"
#include "common/as_core_type.hpp"
#include "common/clearable.hpp"
#include "common/code_utils.hpp"
#include "common/equatable.hpp"
#include "common/log.hpp"
#include "common/message.hpp"
#include "common/num_utils.hpp"
#include "common/numeric_limits.hpp"
#include "common/string.hpp"
#include "mac/mac_types.hpp"
#include "meshcop/meshcop_tlvs.hpp"

namespace ot {

class ThreadNetif;

namespace MeshCoP {

/**
 * Represents a Joiner PSKd.
 */
class JoinerPskd : public otJoinerPskd, public Clearable<JoinerPskd>, public Unequatable<JoinerPskd>
{
public:
    static constexpr uint8_t kMinLength = 6;                         ///< Min PSKd string length (excludes null char)
    static constexpr uint8_t kMaxLength = OT_JOINER_MAX_PSKD_LENGTH; ///< Max PSKd string length (excludes null char)

    /**
     * Indicates whether the PSKd if well-formed and valid.
     *
     * Per Thread specification, a Joining Device Credential is encoded as uppercase alphanumeric characters
     * (base32-thread: 0-9, A-Z excluding I, O, Q, and Z for readability) with a minimum length of 6 such characters
     * and a maximum length of 32 such characters.
     *
     * @returns TRUE if the PSKd is valid, FALSE otherwise.
     */
    bool IsValid(void) const { return IsPskdValid(m8); }

    /**
     * Sets the joiner PSKd from a given C string.
     *
     * @param[in] aPskdString   A pointer to the PSKd C string array.
     *
     * @retval kErrorNone          The PSKd was updated successfully.
     * @retval kErrorInvalidArgs   The given PSKd C string is not valid.
     */
    Error SetFrom(const char *aPskdString);

    /**
     * Gets the PSKd as a null terminated C string.
     *
     * Must be used after the PSKd is validated, otherwise its behavior is undefined.
     *
     * @returns The PSKd as a C string.
     */
    const char *GetAsCString(void) const { return m8; }

    /**
     * Gets the PSKd string length.
     *
     * Must be used after the PSKd is validated, otherwise its behavior is undefined.
     *
     * @returns The PSKd string length.
     */
    uint8_t GetLength(void) const { return static_cast<uint8_t>(StringLength(m8, kMaxLength + 1)); }

    /**
     * Gets the PSKd as a byte array.
     *
     * @returns The PSKd as a byte array.
     */
    const uint8_t *GetBytes(void) const { return reinterpret_cast<const uint8_t *>(m8); }

    /**
     * Overloads operator `==` to evaluate whether or not two PSKds are equal.
     *
     * @param[in]  aOther  The other PSKd to compare with.
     *
     * @retval TRUE   If the two are equal.
     * @retval FALSE  If the two are not equal.
     */
    bool operator==(const JoinerPskd &aOther) const;

    /**
     * Indicates whether a given PSKd string if well-formed and valid.
     *
     * @param[in] aPskdString  A pointer to a PSKd string array.
     *
     * @sa IsValid()
     *
     * @returns TRUE if @p aPskdString is valid, FALSE otherwise.
     */
    static bool IsPskdValid(const char *aPskdString);
};

/**
 * Represents a Joiner Discerner.
 */
class JoinerDiscerner : public otJoinerDiscerner, public Unequatable<JoinerDiscerner>
{
    friend class SteeringData;

public:
    static constexpr uint8_t kMaxLength = OT_JOINER_MAX_DISCERNER_LENGTH; ///< Max length of a Discerner in bits.

    static constexpr uint16_t kInfoStringSize = 45; ///< Size of `InfoString` to use with `ToString()

    /**
     * Defines the fixed-length `String` object returned from `ToString()`.
     */
    typedef String<kInfoStringSize> InfoString;

    /**
     * Clears the Joiner Discerner.
     */
    void Clear(void) { mLength = 0; }

    /**
     * Indicates whether the Joiner Discerner is empty (no value set).
     *
     * @returns TRUE if empty, FALSE otherwise.
     */
    bool IsEmpty(void) const { return mLength == 0; }

    /**
     * Gets the Joiner Discerner's value.
     *
     * @returns The Joiner Discerner value.
     */
    uint64_t GetValue(void) const { return mValue; }

    /**
     * Gets the Joiner Discerner's length (in bits).
     *
     * @return The Joiner Discerner length.
     */
    uint8_t GetLength(void) const { return mLength; }

    /**
     * Indicates whether the Joiner Discerner is valid (i.e. it not empty and its length is within
     * valid range).
     *
     * @returns TRUE if Joiner Discerner is valid, FALSE otherwise.
     */
    bool IsValid(void) const { return (0 < mLength) && (mLength <= kMaxLength); }

    /**
     * Generates a Joiner ID from the Discerner.
     *
     * @param[out] aJoinerId   A reference to `Mac::ExtAddress` to output the generated Joiner ID.
     */
    void GenerateJoinerId(Mac::ExtAddress &aJoinerId) const;

    /**
     * Indicates whether a given Joiner ID matches the Discerner.
     *
     * @param[in] aJoinerId  A Joiner ID to match with the Discerner.
     *
     * @returns TRUE if the Joiner ID matches the Discerner, FALSE otherwise.
     */
    bool Matches(const Mac::ExtAddress &aJoinerId) const;

    /**
     * Overloads operator `==` to evaluate whether or not two Joiner Discerner instances are equal.
     *
     * @param[in]  aOther  The other Joiner Discerner to compare with.
     *
     * @retval TRUE   If the two are equal.
     * @retval FALSE  If the two are not equal.
     */
    bool operator==(const JoinerDiscerner &aOther) const;

    /**
     * Converts the Joiner Discerner to a string.
     *
     * @returns An `InfoString` representation of Joiner Discerner.
     */
    InfoString ToString(void) const;

private:
    uint64_t GetMask(void) const { return (static_cast<uint64_t>(1ULL) << mLength) - 1; }
    void     CopyTo(Mac::ExtAddress &aExtAddress) const;
};

/**
 * Represents Steering Data (bloom filter).
 */
class SteeringData : public otSteeringData
{
public:
    static constexpr uint8_t kMinLength = OT_STEERING_DATA_MIN_LENGTH; ///< Minimum Steering Data length (in bytes).
    static constexpr uint8_t kMaxLength = OT_STEERING_DATA_MAX_LENGTH; ///< Maximum Steering Data length (in bytes).

    static constexpr uint16_t kInfoStringSize = 45; ///< Size of `InfoString` to use with `ToString()`.

    /**
     * Defines the fixed-length `String` object returned from `ToString()`.
     */
    typedef String<kInfoStringSize> InfoString;

    /**
     * Represents the hash bit index values for the bloom filter calculated from a Joiner ID.
     *
     * The first hash bit index is derived using CRC16-CCITT and second one using CRC16-ANSI.
     */
    struct HashBitIndexes
    {
        static constexpr uint8_t kNumIndexes = 2; ///< Number of hash bit indexes.

        uint16_t mIndex[kNumIndexes]; ///< The hash bit index array.
    };

    /**
     * Initializes the Steering Data and clears the bloom filter.
     *
     * @param[in]  aLength   The Steering Data length (in bytes).
     *
     * @retval kErrorSuccess      Successfully initialized the Steering Data.
     * @retval kErrorInvalidArgs  @p aLength is not valid.
     */
    Error Init(uint8_t aLength);

    /**
     * Initializes the Steering Data.
     *
     * @param[in]  aLength   The Steering Data length (in bytes).
     * @param[in]  aData     A pointer to a buffer containing the data bytes.
     *
     * @retval kErrorSuccess      Successfully initialized the Steering Data.
     * @retval kErrorInvalidArgs  @p aLength is not valid.
     */
    Error Init(uint8_t aLength, const uint8_t *aData);

    /**
     * Checks whether the Steering Data has a valid length.
     *
     * @returns TRUE if the Steering Data's length is valid (within min to max range), FALSE otherwise.
     */
    bool IsValid(void) const { return IsValueInRange(mLength, kMinLength, kMaxLength); }

    /**
     * Clears the bloom filter (all bits are cleared and no Joiner Id is accepted)..
     *
     * The Steering Data length (bloom filter length) is set to one byte with all bits cleared.
     */
    void Clear(void) { IgnoreError(Init(1)); }

    /**
     * Sets the bloom filter to permit all Joiner IDs.
     *
     * To permit all Joiner IDs, The Steering Data length (bloom filter length) is set to one byte with all bits set.
     */
    void SetToPermitAllJoiners(void);

    /**
     * Returns the Steering Data length (in bytes).
     *
     * @returns The Steering Data length (in bytes).
     */
    uint8_t GetLength(void) const { return mLength; }

    /**
     * Gets the Steering Data buffer (bloom filter).
     *
     * @returns A pointer to the Steering Data buffer.
     */
    const uint8_t *GetData(void) const { return m8; }

    /**
     * Gets the Steering Data buffer (bloom filter).
     *
     * @returns A pointer to the Steering Data buffer.
     */
    uint8_t *GetData(void) { return m8; }

    /**
     * Updates the bloom filter adding the given Joiner ID.
     *
     * @param[in]  aJoinerId  The Joiner ID to add to bloom filter.
     *
     * @retval kErrorNone         Successfully updated the bloom filter.
     * @retval kErrorInvalidArgs  The Steering Data's length is invalid.
     */
    Error UpdateBloomFilter(const Mac::ExtAddress &aJoinerId);

    /**
     * Updates the bloom filter adding a given Joiner Discerner.
     *
     * @param[in]  aDiscerner  The Joiner Discerner to add to bloom filter.
     *
     * @retval kErrorNone         Successfully updated the bloom filter.
     * @retval kErrorInvalidArgs  The Steering Data's length is invalid.
     */
    Error UpdateBloomFilter(const JoinerDiscerner &aDiscerner);

    /**
     * Merges the bloom filter by combining it with another steering data filter.
     *
     * Both bloom filters must have valid lengths (non-zero and not exceeding `kMaxLength`).
     *
     * The bloom filter from @p aOther must have a length that is a divisor of the current filter's length.
     *
     * @param[in] aOther  The other bloom filter to combine with current filter.
     *
     * @retval kErrorNone         Successfully merged @p aOther into the current bloom filter.
     * @retval kErrorInvalidArgs  The filter lengths are not valid or they cannot be merged.
     */
    Error MergeBloomFilterWith(const SteeringData &aOther);

    /**
     * Indicates whether the bloom filter is empty (all the bits are cleared).
     *
     * @returns TRUE if the bloom filter is empty, FALSE otherwise.
     */
    bool IsEmpty(void) const { return DoesAllMatch(0); }

    /**
     * Indicates whether the bloom filter permits all Joiner IDs (all the bits are set).
     *
     * @returns TRUE if the bloom filter permits all Joiners IDs, FALSE otherwise.
     */
    bool PermitsAllJoiners(void) const { return (mLength > 0) && DoesAllMatch(kPermitAll); }

    /**
     * Indicates whether the bloom filter contains a given Joiner ID.
     *
     * @param[in] aJoinerId  A Joiner ID.
     *
     * @returns TRUE if the bloom filter contains @p aJoinerId, FALSE otherwise.
     */
    bool Contains(const Mac::ExtAddress &aJoinerId) const;

    /**
     * Indicates whether the bloom filter contains a given Joiner Discerner.
     *
     * @param[in] aDiscerner   A Joiner Discerner.
     *
     * @returns TRUE if the bloom filter contains @p aDiscerner, FALSE otherwise.
     */
    bool Contains(const JoinerDiscerner &aDiscerner) const;

    /**
     * Indicates whether the bloom filter contains the hash bit indexes (derived from a Joiner ID).
     *
     * @param[in]  aIndexes   A hash bit index structure (derived from a Joiner ID).
     *
     * @returns TRUE if the bloom filter contains the Joiner ID mapping to @p aIndexes, FALSE otherwise.
     */
    bool Contains(const HashBitIndexes &aIndexes) const;

    /**
     * Converts the Steering Data to a human-readable string representation.
     *
     * @returns An `InfoString` representation of the Steering Data.
     */
    InfoString ToString(void) const;

    /**
     * Calculates the bloom filter hash bit indexes from a given Joiner ID.
     *
     * The first hash bit index is derived using CRC16-CCITT and second one using CRC16-ANSI.
     *
     * @param[in]  aJoinerId  The Joiner ID to calculate the hash bit indexes.
     * @param[out] aIndexes   A reference to a `HashBitIndexes` structure to output the calculated index values.
     */
    static void CalculateHashBitIndexes(const Mac::ExtAddress &aJoinerId, HashBitIndexes &aIndexes);

    /**
     * Calculates the bloom filter hash bit indexes from a given Joiner Discerner.
     *
     * The first hash bit index is derived using CRC16-CCITT and second one using CRC16-ANSI.
     *
     * @param[in]  aDiscerner     The Joiner Discerner to calculate the hash bit indexes.
     * @param[out] aIndexes       A reference to a `HashBitIndexes` structure to output the calculated index values.
     */
    static void CalculateHashBitIndexes(const JoinerDiscerner &aDiscerner, HashBitIndexes &aIndexes);

private:
    static constexpr uint8_t kPermitAll = 0xff;

    uint8_t GetNumBits(void) const { return (mLength * kBitsPerByte); }

    uint8_t BitIndex(uint8_t aBit) const { return (mLength - 1 - (aBit / kBitsPerByte)); }
    uint8_t BitFlag(uint8_t aBit) const { return static_cast<uint8_t>(1U << (aBit % kBitsPerByte)); }

    bool GetBit(uint8_t aBit) const { return (m8[BitIndex(aBit)] & BitFlag(aBit)) != 0; }
    void SetBit(uint8_t aBit) { m8[BitIndex(aBit)] |= BitFlag(aBit); }
    void ClearBit(uint8_t aBit) { m8[BitIndex(aBit)] &= ~BitFlag(aBit); }

    bool  DoesAllMatch(uint8_t aMatch) const;
    Error UpdateBloomFilter(const HashBitIndexes &aIndexes);
};

/**
 * Represents a Commissioning Dataset.
 */
class CommissioningDataset : public otCommissioningDataset, public Clearable<CommissioningDataset>
{
public:
    /**
     * Indicates whether or not the Border Router RLOC16 Locator is set in the Dataset.
     *
     * @returns TRUE if Border Router RLOC16 Locator is set, FALSE otherwise.
     */
    bool IsLocatorSet(void) const { return mIsLocatorSet; }

    /**
     * Gets the Border Router RLOC16 Locator in the Dataset.
     *
     * MUST be used when Locator is set in the Dataset, otherwise its behavior is undefined.
     *
     * @returns The Border Router RLOC16 Locator in the Dataset.
     */
    uint16_t GetLocator(void) const { return mLocator; }

    /**
     * Sets the Border Router RLOCG16 Locator in the Dataset.
     *
     * @param[in] aLocator  A Locator.
     */
    void SetLocator(uint16_t aLocator)
    {
        mIsLocatorSet = true;
        mLocator      = aLocator;
    }

    /**
     * Indicates whether or not the Session ID is set in the Dataset.
     *
     * @returns TRUE if Session ID is set, FALSE otherwise.
     */
    bool IsSessionIdSet(void) const { return mIsSessionIdSet; }

    /**
     * Gets the Session ID in the Dataset.
     *
     * MUST be used when Session ID is set in the Dataset, otherwise its behavior is undefined.
     *
     * @returns The Session ID in the Dataset.
     */
    uint16_t GetSessionId(void) const { return mSessionId; }

    /**
     * Sets the Session ID in the Dataset.
     *
     * @param[in] aSessionId  The Session ID.
     */
    void SetSessionId(uint16_t aSessionId)
    {
        mIsSessionIdSet = true;
        mSessionId      = aSessionId;
    }

    /**
     * Indicates whether or not the Steering Data is set in the Dataset.
     *
     * @returns TRUE if Steering Data is set, FALSE otherwise.
     */
    bool IsSteeringDataSet(void) const { return mIsSteeringDataSet; }

    /**
     * Gets the Steering Data in the Dataset.
     *
     * MUST be used when Steering Data is set in the Dataset, otherwise its behavior is undefined.
     *
     * @returns The Steering Data in the Dataset.
     */
    const SteeringData &GetSteeringData(void) const { return static_cast<const SteeringData &>(mSteeringData); }

    /**
     * Returns a reference to the Steering Data in the Dataset to be updated by caller.
     *
     * @returns A reference to the Steering Data in the Dataset.
     */
    SteeringData &UpdateSteeringData(void)
    {
        mIsSteeringDataSet = true;
        return static_cast<SteeringData &>(mSteeringData);
    }

    /**
     * Indicates whether or not the Joiner UDP port is set in the Dataset.
     *
     * @returns TRUE if Joiner UDP port is set, FALSE otherwise.
     */
    bool IsJoinerUdpPortSet(void) const { return mIsJoinerUdpPortSet; }

    /**
     * Gets the Joiner UDP port in the Dataset.
     *
     * MUST be used when Joiner UDP port is set in the Dataset, otherwise its behavior is undefined.
     *
     * @returns The Joiner UDP port in the Dataset.
     */
    uint16_t GetJoinerUdpPort(void) const { return mJoinerUdpPort; }

    /**
     * Sets the Joiner UDP Port in the Dataset.
     *
     * @param[in] aJoinerUdpPort  The Joiner UDP Port.
     */
    void SetJoinerUdpPort(uint16_t aJoinerUdpPort)
    {
        mIsJoinerUdpPortSet = true;
        mJoinerUdpPort      = aJoinerUdpPort;
    }
};

/**
 * Generates PSKc.
 *
 * PSKc is used to establish the Commissioner Session.
 *
 * @param[in]  aPassPhrase   The commissioning passphrase.
 * @param[in]  aNetworkName  The network name for PSKc computation.
 * @param[in]  aExtPanId     The extended PAN ID for PSKc computation.
 * @param[out] aPskc         A reference to a PSKc where the generated PSKc will be placed.
 *
 * @retval kErrorNone          Successfully generate PSKc.
 * @retval kErrorInvalidArgs   If the length of passphrase is out of range.
 */
Error GeneratePskc(const char          *aPassPhrase,
                   const NetworkName   &aNetworkName,
                   const ExtendedPanId &aExtPanId,
                   Pskc                &aPskc);

/**
 * Computes the Joiner ID from a factory-assigned IEEE EUI-64.
 *
 * @param[in]   aEui64     The factory-assigned IEEE EUI-64.
 * @param[out]  aJoinerId  The Joiner ID.
 */
void ComputeJoinerId(const Mac::ExtAddress &aEui64, Mac::ExtAddress &aJoinerId);

#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE

/**
 * Generates a message dump log for certification test.
 *
 * @param[in] aText     The title text to include in the log.
 * @param[in] aMessage  The message to dump the content of.
 */
void LogCertMessage(const char *aText, const Coap::Message &aMessage);

#endif

} // namespace MeshCoP

DefineCoreType(otJoinerPskd, MeshCoP::JoinerPskd);
DefineCoreType(otJoinerDiscerner, MeshCoP::JoinerDiscerner);
DefineCoreType(otSteeringData, MeshCoP::SteeringData);
DefineCoreType(otCommissioningDataset, MeshCoP::CommissioningDataset);

} // namespace ot

#endif // OT_CORE_MESHCOP_MESHCOP_HPP_
