// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef IO_FETCH_H
#define IO_FETCH_H

#include <config.h>

// We want to use coroutine.hpp from the system's boost headers if possible.
// However, very old Boost versions (provided by RHEL 7 or CentOS 7) didn't have
// this header. So we can resort to our bundled version, but only if necessary.
#ifdef HAVE_BOOST_ASIO_COROUTINE_HPP
#include <boost/asio/coroutine.hpp>
#else
#include <ext/coroutine/coroutine.hpp>
#endif

#include <asiolink/io_address.h>
#include <asiolink/io_service.h>
#include <dns/message.h>
#include <dns/question.h>
#include <util/buffer.h>

#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/system/error_code.hpp>

namespace isc {
namespace asiodns {

// Forward declarations
struct IOFetchData;

/// @brief Upstream Fetch Processing.
///
/// IOFetch is the class used to send upstream fetches and to handle responses.
class IOFetch : public boost::asio::coroutine {
public:
    /// @brief Protocol to use on the fetch
    enum Protocol {
        UDP = 0,
        TCP = 1
    };

    /// @brief Origin of Asynchronous I/O Call.
    ///
    /// Indicates what initiated an asynchronous I/O call and used in deciding what
    /// error message to output if the I/O fails.
    enum Origin {
        NONE = 0,          // No asynchronous call outstanding
        OPEN = 1,
        SEND = 2,
        RECEIVE = 3,
        CLOSE = 4
    };

    /// @brief Result of Upstream Fetch.
    ///
    /// @note that this applies to the status of I/Os in the fetch - a fetch that
    /// resulted in a packet being received from the server is a SUCCESS, even if
    /// the contents of the packet indicate that some error occurred.
    enum Result {
        SUCCESS = 0,       // Success, fetch completed
        TIME_OUT = 1,      // Failure, fetch timed out
        STOPPED = 2,       // Control code, fetch has been stopped
        NOTSET = 3         // For testing, indicates value not set
    };

    /// @note The next enum is a "trick" to allow constants to be defined in a class
    /// declaration.

    /// @brief Integer Constants.
    enum {
        STAGING_LENGTH = 8192   // Size of staging buffer
    };

    /// @brief I/O Fetch Callback.
    ///
    /// Class of callback object for when the fetch itself has completed - an object
    /// of this class is passed to the IOFetch constructor and its operator() method
    /// called when the fetch completes.
    ///
    /// @note The difference between the two operator() methods:
    /// - IOFetch::operator() callback is called when an asynchronous I/O has completed.
    /// - IOFetch::Callback::operator() is called when an upstream fetch - which may
    ///   have involved several asynchronous I/O operations - has completed.
    ///
    /// This is an abstract class.
    class Callback {
    public:
        /// @brief Constructor.
        Callback() = default;

        /// @brief Destructor.
        virtual ~Callback() = default;

        /// @brief Callback method.
        ///
        /// This method is called when the fetch completes.
        ///
        /// @param result Result of the fetch.
        virtual void operator()(Result result) = 0;
    };

    /// @brief Constructor.
    ///
    /// Creates the object that will handle the upstream fetch.
    ///
    /// @param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP.
    /// @param service I/O Service object to handle the asynchronous
    ///        operations.
    /// @param question DNS question to send to the upstream server.
    /// @param address IP address of upstream server.
    /// @param port Port to which to connect on the upstream server.
    /// @param buff Output buffer into which the response (in wire format)
    ///        is written (if a response is received).
    /// @param cb Callback object containing the callback to be called when we
    ///        terminate.  The caller is responsible for managing this object
    ///        and deleting it if necessary.
    /// @param wait Timeout for the fetch (in ms).  The default value of
    ///        -1 indicates no timeout.
    /// @param edns true if the request should be EDNS. The default value is
    ///        true.
    IOFetch(Protocol protocol,
            const isc::asiolink::IOServicePtr& service,
            const isc::dns::Question& question,
            const isc::asiolink::IOAddress& address,
            uint16_t port,
            isc::util::OutputBufferPtr& buff,
            Callback* cb,
            int wait = -1,
            bool edns = true);

    /// @brief Constructor
    ///
    /// This constructor has one parameter "query_message", which is the shared_ptr
    /// to a full query message. It's different with above constructor which has only
    /// question section. All other parameters are the same.
    ///
    /// @param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP.
    /// @param service I/O Service object to handle the asynchronous
    ///        operations.
    /// @param query_message the shared_ptr to a full query message got from a
    ///        query client.
    /// @param address IP address of upstream server.
    /// @param port Port to which to connect on the upstream server.
    /// @param buff Output buffer into which the response (in wire format)
    ///        is written (if a response is received).
    /// @param cb Callback object containing the callback to be called when we
    ///        terminate.  The caller is responsible for managing this object
    ///        and deleting it if necessary.
    /// @param wait Timeout for the fetch (in ms).  The default value of -1
    ///        indicates no timeout.
    IOFetch(Protocol protocol,
            const isc::asiolink::IOServicePtr& service,
            isc::dns::ConstMessagePtr query_message,
            const isc::asiolink::IOAddress& address,
            uint16_t port,
            isc::util::OutputBufferPtr& buff,
            Callback* cb,
            int wait = -1);

    /// @brief Constructor.
    ///
    /// Creates the object that will handle the upstream fetch.
    ///
    /// @param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP
    /// @param service I/O Service object to handle the asynchronous
    ///     operations.
    /// @param outpkt Packet to send to upstream server.  Note that the
    ///     QID (first two bytes of the packet) may be altered in the sending.
    /// @param buff Output buffer into which the response (in wire format)
    ///     is written (if a response is received).
    /// @param cb Callback object containing the callback to be called
    ///     when we terminate.  The caller is responsible for managing this
    ///     object and deleting it if necessary.
    /// @param address IP address of upstream server.
    /// @param port Port to which to connect on the upstream server
    ///     (default = 53).
    /// @param wait Timeout for the fetch (in ms).  The default value of
    ///     -1 indicates no timeout.
    IOFetch(Protocol protocol,
            const isc::asiolink::IOServicePtr& service,
            isc::util::OutputBufferPtr& outpkt,
            const isc::asiolink::IOAddress& address,
            uint16_t port,
            isc::util::OutputBufferPtr& buff,
            Callback* cb,
            int wait = -1);

    /// @brief Return Current Protocol.
    ///
    /// @return Protocol associated with this IOFetch object.
    Protocol getProtocol() const;

    /// @brief Coroutine entry point.
    ///
    /// The operator() method is the method in which the coroutine code enters
    /// this object when an operation has been completed.
    ///
    /// @param ec Error code, the result of the last asynchronous I/O operation.
    /// @param length Amount of data received on the last asynchronous read.
    void operator()(boost::system::error_code ec = boost::system::error_code(),
                    size_t length = 0);

    /// @brief Terminate query.
    ///
    /// This method can be called at any point.  It terminates the current
    /// query with the specified reason.
    /// This function stops the coroutine sequence.  It is called either when the
    /// query finishes or when the timer times out.  Either way, it sets the
    /// "stopped_" flag and cancels anything that is in progress.
    ///
    /// As the function may be entered multiple times as things wind down, it checks
    /// if the stopped_ flag is already set.  If it is, the call is a no-op.
    ///
    /// @param reason Reason for terminating the query.
    void stop(Result reason = STOPPED);

private:
    /// @brief IOFetch Initialization Function.
    ///
    /// All the parameters are same with the constructor, except parameter
    /// "query_message".
    ///
    /// @param query_message the message to be sent out.
    void initIOFetch(isc::dns::MessagePtr& query_message,
                     Protocol protocol,
                     const isc::asiolink::IOServicePtr& service,
                     const isc::dns::Question& question,
                     const isc::asiolink::IOAddress& address,
                     uint16_t port,
                     isc::util::OutputBufferPtr& buff,
                     Callback* cb, int wait,
                     bool edns = true);

    /// @brief Log I/O Failure.
    ///
    /// Records an I/O failure to the log file.
    ///
    /// @param ec ASIO error code.
    void logIOFailure(boost::system::error_code ec);

    /// @brief data which is a structure pointed to by a shared pointer.  The IOFetch
    /// object is copied a number of times during its life, and only requiring a
    /// pointer to be copied reduces overhead.
    boost::shared_ptr<IOFetchData> data_;   ///< Private data
};

/// @brief Defines a pointer to an IOFetch.
typedef boost::shared_ptr<IOFetch> IOFetchPtr;

}  // namespace asiodns
}  // namespace isc

#endif // IO_FETCH_H
