/*
 *  Copyright (c) 2021, 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 to support ping functionality.
 */

#ifndef OT_CORE_UTILS_PING_SENDER_HPP_
#define OT_CORE_UTILS_PING_SENDER_HPP_

#include "openthread-core-config.h"

#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE

#include <openthread/ping_sender.h>

#include "common/as_core_type.hpp"
#include "common/code_utils.hpp"
#include "common/locator.hpp"
#include "common/message.hpp"
#include "common/non_copyable.hpp"
#include "common/numeric_limits.hpp"
#include "common/time.hpp"
#include "common/timer.hpp"
#include "net/icmp6.hpp"
#include "net/ip6_address.hpp"

namespace ot {
namespace Utils {

/**
 * Implements sending ICMPv6 Echo Request messages and processing ICMPv6 Echo Reply messages.
 */
class PingSender : public InstanceLocator, private NonCopyable
{
public:
    /**
     * Represents a ping reply.
     */
    typedef otPingSenderReply Reply;

    /**
     * Represents the statistics of several ping requests.
     */
    struct Statistics : public otPingSenderStatistics
    {
        Statistics(void) { Clear(); }

        void Clear(void)
        {
            mSentCount          = 0;
            mReceivedCount      = 0;
            mTotalRoundTripTime = 0;
            mMaxRoundTripTime   = 0;
            mIsMulticast        = false;
            SetToUintMax(mMinRoundTripTime);
        }
    };

    /**
     * Represents a ping request configuration.
     */
    class Config : public otPingSenderConfig
    {
        friend class PingSender;

    public:
        /**
         * Gets the source IPv6 address of the ping.
         *
         * @returns The ping source IPv6 address.
         */
        Ip6::Address &GetSource(void) { return AsCoreType(&mSource); }

        /**
         * Gets the source IPv6 address of the ping.
         *
         * @returns The ping source IPv6 address.
         */
        const Ip6::Address &GetSource(void) const { return AsCoreType(&mSource); }

        /**
         * Gets the destination IPv6 address to ping.
         *
         * @returns The ping destination IPv6 address.
         */
        Ip6::Address &GetDestination(void) { return AsCoreType(&mDestination); }

        /**
         * Gets the destination IPv6 address to ping.
         *
         * @returns The ping destination IPv6 address.
         */
        const Ip6::Address &GetDestination(void) const { return AsCoreType(&mDestination); }

    private:
        static constexpr uint16_t kDefaultSize     = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_SIZE;
        static constexpr uint16_t kDefaultCount    = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_COUNT;
        static constexpr uint32_t kDefaultInterval = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_INTERVAL;
        static constexpr uint32_t kDefaultTimeout  = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_TIMEOUT;

        void SetUnspecifiedToDefault(void);
        void InvokeReplyCallback(const Reply &aReply) const;
        void InvokeStatisticsCallback(const Statistics &aStatistics) const;
    };

    /**
     * Initializes the `PingSender` object.
     *
     * @param[in]  aInstance     A reference to the OpenThread instance.
     */
    explicit PingSender(Instance &aInstance);

    /**
     * Starts a ping.
     *
     * @param[in] aConfig          The ping config to use.
     *
     * @retval kErrorNone          The ping started successfully.
     * @retval kErrorBusy          Could not start since busy with a previous ongoing ping request.
     * @retval kErrorInvalidArgs   The @p aConfig contains invalid parameters (e.g., ping interval is too long).
     */
    Error Ping(const Config &aConfig);

    /**
     * Stops an ongoing ping.
     */
    void Stop(void);

private:
    void        SendPing(void);
    void        HandleTimer(void);
    static void HandleIcmpReceive(void                *aContext,
                                  otMessage           *aMessage,
                                  const otMessageInfo *aMessageInfo,
                                  const otIcmp6Header *aIcmpHeader);
    void        HandleIcmpReceive(const Message           &aMessage,
                                  const Ip6::MessageInfo  &aMessageInfo,
                                  const Ip6::Icmp::Header &aIcmpHeader);

    using PingTimer = TimerMilliIn<PingSender, &PingSender::HandleTimer>;

    Config             mConfig;
    Statistics         mStatistics;
    uint16_t           mIdentifier;
    uint16_t           mTargetEchoSequence;
    PingTimer          mTimer;
    Ip6::Icmp::Handler mIcmpHandler;
};

} // namespace Utils

DefineCoreType(otPingSenderReply, Utils::PingSender::Reply);
DefineCoreType(otPingSenderConfig, Utils::PingSender::Config);
DefineCoreType(otPingSenderStatistics, Utils::PingSender::Statistics);

} // namespace ot

#endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE

#endif // OT_CORE_UTILS_PING_SENDER_HPP_
