/*
 *  Copyright (c) 2023, 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
 * @brief
 *  This file defines the OpenThread Mesh Diagnostic APIs.
 */

#ifndef OPENTHREAD_MESH_DIAG_H_
#define OPENTHREAD_MESH_DIAG_H_

#include <stdbool.h>
#include <stdint.h>

#include <openthread/error.h>
#include <openthread/instance.h>
#include <openthread/ip6.h>
#include <openthread/thread.h>
#include <openthread/platform/radio.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @addtogroup api-mesh-diag
 *
 * @brief
 *   This module includes definitions and functions for Mesh Diagnostics.
 *
 *   The Mesh Diagnostics APIs require `OPENTHREAD_CONFIG_MESH_DIAG_ENABLE` and `OPENTHREAD_FTD`.
 *
 * @{
 */

/**
 * Represents the set of configurations used when discovering mesh topology indicating which items to
 * discover.
 */
typedef struct otMeshDiagDiscoverConfig
{
    bool mDiscoverIp6Addresses : 1; ///< Whether or not to discover IPv6 addresses of every router.
    bool mDiscoverChildTable : 1;   ///< Whether or not to discover children of every router.
} otMeshDiagDiscoverConfig;

/**
 * An opaque iterator to iterate over list of IPv6 addresses of a router.
 *
 * Pointers to instance of this type are provided in `otMeshDiagRouterInfo`.
 */
typedef struct otMeshDiagIp6AddrIterator otMeshDiagIp6AddrIterator;

/**
 * An opaque iterator to iterate over list of children of a router.
 *
 * Pointers to instance of this type are provided in `otMeshDiagRouterInfo`.
 */
typedef struct otMeshDiagChildIterator otMeshDiagChildIterator;

/**
 * Specifies that Thread Version is unknown.
 *
 * This is used in `otMeshDiagRouterInfo` for `mVersion` property when device does not provide its version. This
 * indicates that device is likely running 1.3.0 (version value 4) or earlier.
 */
#define OT_MESH_DIAG_VERSION_UNKNOWN 0xffff

/**
 * Represents information about a router in Thread mesh discovered using `otMeshDiagDiscoverTopology()`.
 */
typedef struct otMeshDiagRouterInfo
{
    otExtAddress mExtAddress;             ///< Extended MAC address.
    uint16_t     mRloc16;                 ///< RLOC16.
    uint8_t      mRouterId;               ///< Router ID.
    uint16_t     mVersion;                ///< Thread Version. `OT_MESH_DIAG_VERSION_UNKNOWN` if unknown.
    bool         mIsThisDevice : 1;       ///< Whether router is this device itself.
    bool         mIsThisDeviceParent : 1; ///< Whether router is parent of this device (when device is a child).
    bool         mIsLeader : 1;           ///< Whether router is leader.
    bool         mIsBorderRouter : 1;     ///< Whether router acts as a border router providing ext connectivity.

    /**
     * Provides the link quality from this router to other routers, also indicating whether a link is established
     * between the routers.
     *
     * The array is indexed based on Router ID. `mLinkQualities[routerId]` indicates the incoming link quality, the
     * router sees to the router with `routerId`. Link quality is a value in [0, 3]. Value zero indicates no link.
     * Larger value indicate better link quality (as defined by Thread specification).
     */
    uint8_t mLinkQualities[OT_NETWORK_MAX_ROUTER_ID + 1];

    /**
     * A pointer to an iterator to go through the list of IPv6 addresses of the router.
     *
     * The pointer is valid only while `otMeshDiagRouterInfo` is valid. It can be used in `otMeshDiagGetNextIp6Address`
     * to iterate through the IPv6 addresses.
     *
     * The pointer can be NULL when there was no request to discover IPv6 addresses (in `otMeshDiagDiscoverConfig`) or
     * if the router did not provide the list.
     */
    otMeshDiagIp6AddrIterator *mIp6AddrIterator;

    /**
     * A pointer to an iterator to go through the list of children of the router.
     *
     * The pointer is valid only while `otMeshDiagRouterInfo` is valid. It can be used in `otMeshDiagGetNextChildInfo`
     * to iterate through the children of the router.
     *
     * The pointer can be NULL when there was no request to discover children (in `otMeshDiagDiscoverConfig`) or
     * if the router did not provide the list.
     */
    otMeshDiagChildIterator *mChildIterator;
} otMeshDiagRouterInfo;

/**
 * Represents information about a discovered child in Thread mesh using `otMeshDiagDiscoverTopology()`.
 */
typedef struct otMeshDiagChildInfo
{
    uint16_t         mRloc16;             ///< RLOC16.
    otLinkModeConfig mMode;               ///< Device mode.
    uint8_t          mLinkQuality;        ///< Incoming link quality to child from parent.
    bool             mIsThisDevice : 1;   ///< Whether child is this device itself.
    bool             mIsBorderRouter : 1; ///< Whether child acts as a border router providing ext connectivity.
} otMeshDiagChildInfo;

/**
 * Pointer type represents the callback used by `otMeshDiagDiscoverTopology()` to provide information
 * about a discovered router.
 *
 * When @p aError is `OT_ERROR_PENDING`, it indicates that the discovery is not yet finished and there will be more
 * routers to discover and the callback will be invoked again.
 *
 * @param[in] aError       OT_ERROR_PENDING            Indicates there are more routers to be discovered.
 *                         OT_ERROR_NONE               Indicates this is the last router and mesh discovery is done.
 *                         OT_ERROR_RESPONSE_TIMEOUT   Timed out waiting for response from one or more routers.
 * @param[in] aRouterInfo  The discovered router info (can be null if `aError` is OT_ERROR_RESPONSE_TIMEOUT).
 * @param[in] aContext     Application-specific context.
 */
typedef void (*otMeshDiagDiscoverCallback)(otError aError, otMeshDiagRouterInfo *aRouterInfo, void *aContext);

/**
 * Starts network topology discovery.
 *
 * @param[in] aInstance        The OpenThread instance.
 * @param[in] aConfig          The configuration to use for discovery (e.g., which items to discover).
 * @param[in] aCallback        The callback to report the discovered routers.
 * @param[in] aContext         A context to pass in @p aCallback.
 *
 * @retval OT_ERROR_NONE            The network topology discovery started successfully.
 * @retval OT_ERROR_BUSY            A previous discovery request is still ongoing.
 * @retval OT_ERROR_INVALID_STATE   Device is not attached.
 * @retval OT_ERROR_NO_BUFS         Could not allocate buffer to send discovery messages.
 */
otError otMeshDiagDiscoverTopology(otInstance                     *aInstance,
                                   const otMeshDiagDiscoverConfig *aConfig,
                                   otMeshDiagDiscoverCallback      aCallback,
                                   void                           *aContext);

/**
 * Cancels an ongoing topology discovery if there is one, otherwise no action.
 *
 * When ongoing discovery is cancelled, the callback from `otMeshDiagDiscoverTopology()` will not be called anymore.
 */
void otMeshDiagCancel(otInstance *aInstance);

/**
 * Iterates through the discovered IPv6 addresses of a router or an MTD child.
 *
 * MUST be used
 * - from the callback `otMeshDiagDiscoverCallback()` and use the `mIp6AddrIterator` from the `aRouterInfo` struct that
 *   is provided as input to the callback, or
 * - from the callback `otMeshDiagChildIp6AddrsCallback()` along with provided `aIp6AddrIterator`.
 *
 * @param[in,out]  aIterator    The address iterator to use.
 * @param[out]     aIp6Address  A pointer to return the next IPv6 address (if any).
 *
 * @retval OT_ERROR_NONE       Successfully retrieved the next address. @p aIp6Address and @p aIterator are updated.
 * @retval OT_ERROR_NOT_FOUND  No more address. Reached the end of the list.
 */
otError otMeshDiagGetNextIp6Address(otMeshDiagIp6AddrIterator *aIterator, otIp6Address *aIp6Address);

/**
 * Iterates through the discovered children of a router.
 *
 * This function MUST be used from the callback `otMeshDiagDiscoverCallback()` and use the `mChildIterator` from the
 * `aRouterInfo` struct that is provided as input to the callback.
 *
 * @param[in,out]  aIterator    The address iterator to use.
 * @param[out]     aChildInfo   A pointer to return the child info (if any).
 *
 * @retval OT_ERROR_NONE       Successfully retrieved the next child. @p aChildInfo and @p aIterator are updated.
 * @retval OT_ERROR_NOT_FOUND  No more child. Reached the end of the list.
 */
otError otMeshDiagGetNextChildInfo(otMeshDiagChildIterator *aIterator, otMeshDiagChildInfo *aChildInfo);

/**
 * Represents information about a child entry from `otMeshDiagQueryChildTable()`.
 *
 * `mSupportsErrRate` indicates whether or not the error tracking feature is supported and `mFrameErrorRate` and
 * `mMessageErrorRate` values are valid. The frame error rate tracks frame tx errors (towards the child) at MAC
 * layer,  while `mMessageErrorRate` tracks the IPv6 message error rate (above MAC layer and after MAC retries) when
 * an IPv6 message is dropped. For example, if the message is large and requires 6LoWPAN fragmentation, message tx is
 * considered as failed if one of its fragment frame tx fails (for example, never acked).
 */
typedef struct otMeshDiagChildEntry
{
    bool         mRxOnWhenIdle : 1;    ///< Is rx-on when idle (vs sleepy).
    bool         mDeviceTypeFtd : 1;   ///< Is device FTD (vs MTD).
    bool         mFullNetData : 1;     ///< Whether device gets full Network Data (vs stable sub-set).
    bool         mCslSynchronized : 1; ///< Is CSL capable and CSL synchronized.
    bool         mSupportsErrRate : 1; ///< `mFrameErrorRate` and `mMessageErrorRate` values are valid.
    uint16_t     mRloc16;              ///< RLOC16.
    otExtAddress mExtAddress;          ///< Extended Address.
    uint16_t     mVersion;             ///< Version.
    uint32_t     mTimeout;             ///< Timeout in seconds.
    uint32_t     mAge;                 ///< Seconds since last heard from the child.
    uint32_t     mConnectionTime;      ///< Seconds since child attach.
    uint16_t     mSupervisionInterval; ///< Supervision interval in seconds. Zero to indicate not used.
    uint8_t      mLinkMargin;          ///< Link Margin in dB.
    int8_t       mAverageRssi;         ///< Average RSSI.
    int8_t       mLastRssi;            ///< RSSI of last received frame.
    uint16_t     mFrameErrorRate;      ///< Frame error rate (0x0000->0%, 0xffff->100%).
    uint16_t     mMessageErrorRate;    ///< (IPv6) msg error rate (0x0000->0%, 0xffff->100%).
    uint16_t     mQueuedMessageCount;  ///< Number of queued messages for indirect tx to child.
    uint16_t     mCslPeriod;           ///< CSL Period in unit of 10-symbols-time. Zero indicates CSL is disabled.
    uint32_t     mCslTimeout;          ///< CSL Timeout in seconds.
    uint8_t      mCslChannel;          ///< CSL channel.
} otMeshDiagChildEntry;

/**
 * Represents the callback used by `otMeshDiagQueryChildTable()` to provide information about child table entries.
 *
 * When @p aError is `OT_ERROR_PENDING`, it indicates that the table still has more entries and the callback will be
 * invoked again.
 *
 * @param[in] aError       OT_ERROR_PENDING            Indicates there are more entries in the table.
 *                         OT_ERROR_NONE               Indicates the table is finished.
 *                         OT_ERROR_RESPONSE_TIMEOUT   Timed out waiting for response.
 * @param[in] aChildEntry  The child entry (can be null if `aError` is OT_ERROR_RESPONSE_TIMEOUT or OT_ERROR_NONE).
 * @param[in] aContext     Application-specific context.
 */
typedef void (*otMeshDiagQueryChildTableCallback)(otError                     aError,
                                                  const otMeshDiagChildEntry *aChildEntry,
                                                  void                       *aContext);

/**
 * Starts query for child table for a given router.
 *
 * @param[in] aInstance        The OpenThread instance.
 * @param[in] aRloc16          The RLOC16 of router to query.
 * @param[in] aCallback        The callback to report the queried child table.
 * @param[in] aContext         A context to pass in @p aCallback.
 *
 * @retval OT_ERROR_NONE           The query started successfully.
 * @retval OT_ERROR_BUSY           A previous discovery or query request is still ongoing.
 * @retval OT_ERROR_INVALID_ARGS   The @p aRloc16 is not a valid router RLOC16.
 * @retval OT_ERROR_INVALID_STATE  Device is not attached.
 * @retval OT_ERROR_NO_BUFS        Could not allocate buffer to send query messages.
 */
otError otMeshDiagQueryChildTable(otInstance                       *aInstance,
                                  uint16_t                          aRloc16,
                                  otMeshDiagQueryChildTableCallback aCallback,
                                  void                             *aContext);

/**
 * Represents the callback used by `otMeshDiagQueryChildrenIp6Addrs()` to provide information about an MTD child and
 * its list of IPv6 addresses.
 *
 * When @p aError is `OT_ERROR_PENDING`, it indicates that there are more children and the callback will be invoked
 * again.
 *
 * @param[in] aError            OT_ERROR_PENDING            Indicates there are more children in the table.
 *                              OT_ERROR_NONE               Indicates the table is finished.
 *                              OT_ERROR_RESPONSE_TIMEOUT   Timed out waiting for response.
 * @param[in] aChildRloc16      The RLOC16 of the child. `0xfffe` is used on `OT_ERROR_RESPONSE_TIMEOUT`.
 * @param[in] aIp6AddrIterator  An iterator to go through the IPv6 addresses of the child with @p aRloc using
 *                              `otMeshDiagGetNextIp6Address()`. Set to NULL on `OT_ERROR_RESPONSE_TIMEOUT`.
 * @param[in] aContext          Application-specific context.
 */
typedef void (*otMeshDiagChildIp6AddrsCallback)(otError                    aError,
                                                uint16_t                   aChildRloc16,
                                                otMeshDiagIp6AddrIterator *aIp6AddrIterator,
                                                void                      *aContext);

/**
 * Sends a query to a parent to retrieve the IPv6 addresses of all its MTD children.
 *
 * @param[in] aInstance        The OpenThread instance.
 * @param[in] aRloc16          The RLOC16 of parent to query.
 * @param[in] aCallback        The callback to report the queried child IPv6 address list.
 * @param[in] aContext         A context to pass in @p aCallback.
 *
 * @retval OT_ERROR_NONE           The query started successfully.
 * @retval OT_ERROR_BUSY           A previous discovery or query request is still ongoing.
 * @retval OT_ERROR_INVALID_ARGS   The @p aRloc16 is not a valid  RLOC16.
 * @retval OT_ERROR_INVALID_STATE  Device is not attached.
 * @retval OT_ERROR_NO_BUFS        Could not allocate buffer to send query messages.
 */
otError otMeshDiagQueryChildrenIp6Addrs(otInstance                     *aInstance,
                                        uint16_t                        aRloc16,
                                        otMeshDiagChildIp6AddrsCallback aCallback,
                                        void                           *aContext);

/**
 * Represents information about a router neighbor entry from `otMeshDiagQueryRouterNeighborTable()`.
 *
 * `mSupportsErrRate` indicates whether or not the error tracking feature is supported and `mFrameErrorRate` and
 * `mMessageErrorRate` values are valid. The frame error rate tracks frame tx errors (towards the child) at MAC
 * layer,  while `mMessageErrorRate` tracks the IPv6 message error rate (above MAC layer and after MAC retries) when
 * an IPv6 message is dropped. For example, if the message is large and requires 6LoWPAN fragmentation, message tx is
 * considered as failed if one of its fragment frame tx fails (for example, never acked).
 */
typedef struct otMeshDiagRouterNeighborEntry
{
    bool         mSupportsErrRate : 1; ///< `mFrameErrorRate` and `mMessageErrorRate` values are valid.
    uint16_t     mRloc16;              ///< RLOC16.
    otExtAddress mExtAddress;          ///< Extended Address.
    uint16_t     mVersion;             ///< Version.
    uint32_t     mConnectionTime;      ///< Seconds since link establishment.
    uint8_t      mLinkMargin;          ///< Link Margin in dB.
    int8_t       mAverageRssi;         ///< Average RSSI.
    int8_t       mLastRssi;            ///< RSSI of last received frame.
    uint16_t     mFrameErrorRate;      ///< Frame error rate (0x0000->0%, 0xffff->100%).
    uint16_t     mMessageErrorRate;    ///< (IPv6) msg error rate (0x0000->0%, 0xffff->100%).
} otMeshDiagRouterNeighborEntry;

/**
 * Represents the callback used by `otMeshDiagQueryRouterNeighborTable()` to provide information about neighbor router
 * table entries.
 *
 * When @p aError is `OT_ERROR_PENDING`, it indicates that the table still has more entries and the callback will be
 * invoked again.
 *
 * @param[in] aError          OT_ERROR_PENDING            Indicates there are more entries in the table.
 *                            OT_ERROR_NONE               Indicates the table is finished.
 *                            OT_ERROR_RESPONSE_TIMEOUT   Timed out waiting for response.
 * @param[in] aNeighborEntry  The neighbor entry (can be null if `aError` is RESPONSE_TIMEOUT or NONE).
 * @param[in] aContext        Application-specific context.
 */
typedef void (*otMeshDiagQueryRouterNeighborTableCallback)(otError                              aError,
                                                           const otMeshDiagRouterNeighborEntry *aNeighborEntry,
                                                           void                                *aContext);

/**
 * Starts query for router neighbor table for a given router.
 *
 * @param[in] aInstance        The OpenThread instance.
 * @param[in] aRloc16          The RLOC16 of router to query.
 * @param[in] aCallback        The callback to report the queried table.
 * @param[in] aContext         A context to pass in @p aCallback.
 *
 * @retval OT_ERROR_NONE           The query started successfully.
 * @retval OT_ERROR_BUSY           A previous discovery or query request is still ongoing.
 * @retval OT_ERROR_INVALID_ARGS   The @p aRloc16 is not a valid router RLOC16.
 * @retval OT_ERROR_INVALID_STATE  Device is not attached.
 * @retval OT_ERROR_NO_BUFS        Could not allocate buffer to send query messages.
 */
otError otMeshDiagQueryRouterNeighborTable(otInstance                                *aInstance,
                                           uint16_t                                   aRloc16,
                                           otMeshDiagQueryRouterNeighborTableCallback aCallback,
                                           void                                      *aContext);

/**
 * Sets the response timeout value to use for any future mesh diagnostic queries.
 *
 * The default response timeout value is specified by `OPENTHREAD_CONFIG_MESH_DIAG_RESPONSE_TIMEOUT` configuration.
 *
 * Changing the response timeout does not impact any ongoing query.
 *
 * The provided @p aTimeout value will be clamped to stay between 50 milliseconds and 10 minutes.
 *
 * @param[in] aInstance  A pointer to an OpenThread instance.
 * @param[in] aTimeout   The timeout interval in milliseconds.
 */
void otMeshDiagSetResponseTimeout(otInstance *aInstance, uint32_t aTimeout);

/**
 * Gets the response timeout value.
 *
 * @param[in] aInstance  A pointer to an OpenThread instance.
 *
 * @returns The response timeout interval in milliseconds.
 */
uint32_t otMeshDiagGetResponseTimeout(otInstance *aInstance);

/**
 * @}
 */

#ifdef __cplusplus
} // extern "C"
#endif

#endif // OPENTHREAD_MESH_DIAG_H_
