// Copyright (C) 2013-2025 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 DNS_CLIENT_H
#define DNS_CLIENT_H

#include <asiolink/io_service.h>
#include <asiodns/io_fetch.h>
#include <d2srv/d2_tsig_key.h>
#include <d2srv/d2_update_message.h>
#include <util/buffer.h>

namespace isc {
namespace d2 {

class DNSClient;
typedef boost::shared_ptr<DNSClient> DNSClientPtr;

/// DNSClient class implementation.
class DNSClientImpl;

/// @brief The @c DNSClient class handles communication with the DNS server.
///
/// Communication with the DNS server is asynchronous. Caller must provide a
/// callback, which will be invoked when the response from the DNS server is
/// received, a timeout has occurred or IO service has been stopped for any
/// reason. The caller-supplied callback is called by the internal callback
/// operator implemented by @c DNSClient. This callback is responsible for
/// initializing the @c D2UpdateMessage instance which encapsulates the response
/// from the DNS. This initialization does not take place if the response from
/// DNS is not received.
///
/// Caller must supply a pointer to the @c D2UpdateMessage object, which will
/// encapsulate DNS response, through class constructor. An exception will be
/// thrown if the pointer is not initialized by the caller.
///
/// @todo Ultimately, this class will support both TCP and UDP Transport.
/// Currently only UDP is supported and can be specified as a preferred
/// protocol. @c DNSClient constructor will throw an exception if TCP is
/// specified. Once both protocols are supported, the @c DNSClient logic will
/// try to obey caller's preference. However, it may use the other protocol if
/// on its own discretion, when there is a legitimate reason to do so. For
/// example, if communication with the server using preferred protocol fails.
class DNSClient {
public:

    /// @brief Transport layer protocol used by a DNS Client to communicate
    /// with a server.
    enum Protocol {
        UDP,
        TCP
    };

    /// @brief A status code of the DNSClient.
    enum Status : uint16_t {
        SUCCESS = 0,            ///< Response received and is ok.
        TIMEOUT = 1,            ///< No response, timeout.
        IO_STOPPED = 2,         ///< IO was stopped.
        INVALID_RESPONSE = 3,   ///< Response received but invalid.
        OTHER = 4,              ///< Other, unclassified error.
    };

    /// @brief Convert enum to string.
    ///
    /// @param status input enum
    ///
    /// @return reference to static string
    static std::string const& statusToText(Status const& status) {
        static std::vector<std::string> const text_vector {
            "SUCCESS",
            "TIMEOUT",
            "IO_STOPPED",
            "INVALID_RESPONSE",
            "OTHER",
        };
        static std::string const unknown("UNKNOWN");
        return (status < text_vector.size() ? text_vector[status] : unknown);
    }

    /// @brief Callback for the @c DNSClient class.
    ///
    /// This is an abstract class which represents the external callback for the
    /// @c DNSClient. Caller must implement this class and supply its instance
    /// in the @c DNSClient constructor to get callbacks when the DNS Update
    /// exchange is complete (@see @c DNSClient).
    class Callback {
    public:
        /// @brief Virtual destructor.
        virtual ~Callback() { }

        /// @brief Function operator implementing a callback.
        ///
        /// @param status a @c DNSClient::Status enum representing status code
        /// of DNSClient operation.
        virtual void operator()(DNSClient::Status status) = 0;
    };

    /// @brief Constructor.
    ///
    /// @param response_placeholder Message object pointer which will be updated
    /// with dynamically allocated object holding the DNS server's response.
    /// @param callback Pointer to an object implementing @c DNSClient::Callback
    /// class. This object will be called when DNS message exchange completes or
    /// if an error occurs. NULL value disables callback invocation.
    /// @param proto caller's preference regarding Transport layer protocol to
    /// be used by DNS Client to communicate with a server.
    DNSClient(D2UpdateMessagePtr& response_placeholder, Callback* callback,
              const Protocol proto = UDP);

    /// @brief Virtual destructor, does nothing.
    ~DNSClient();

    /// @brief Stop the client.
    void stop();

    ///
    /// @name Copy constructor and assignment operator
    ///
    /// Copy constructor and assignment operator are private because there are
    /// no use cases when a DNSClient instance will need to be copied. Also, it
    /// is desired to avoid copying a DNSClient::impl_ pointer and external
    /// callbacks.
    ///
    //@{
private:
    DNSClient(const DNSClient& source);
    DNSClient& operator=(const DNSClient& source);
    //@}

public:

    /// @brief Returns maximal allowed timeout value accepted by
    /// @c DNSClient::doUpdate.
    ///
    /// @return maximal allowed timeout value accepted by @c DNSClient::doUpdate
    static unsigned int getMaxTimeout();

    /// @brief Start asynchronous DNS Update with TSIG.
    ///
    /// This function starts asynchronous DNS Update and returns. The DNS Update
    /// will be executed by the specified IO service. Once the message exchange
    /// with a DNS server is complete, timeout occurs or IO operation is
    /// interrupted, the caller-supplied callback function will be invoked.
    ///
    /// An address and port of the DNS server is specified through the function
    /// arguments so as the same instance of the @c DNSClient can be used to
    /// initiate multiple message exchanges.
    ///
    /// @param io_service IO service to be used to run the message exchange.
    /// @param ns_addr DNS server address.
    /// @param ns_port DNS server port.
    /// @param update A DNS Update message to be sent to the server.
    /// @param wait A timeout (in milliseconds) for the response. If a response
    /// is not received within the timeout, exchange is interrupted. This value
    /// must not exceed maximal value for 'int' data type.
    /// @param tsig_key A pointer to an @c D2TsigKeyPtr object that will
    /// (if not null) be used to sign the DNS Update message and verify the
    /// response.
    void doUpdate(const asiolink::IOServicePtr& io_service,
                  const asiolink::IOAddress& ns_addr,
                  const uint16_t ns_port,
                  D2UpdateMessage& update,
                  const unsigned int wait,
                  const D2TsigKeyPtr& tsig_key = D2TsigKeyPtr());

private:
    /// @brief Pointer to DNSClient implementation.
    std::unique_ptr<DNSClientImpl> impl_;
};

} // namespace d2
} // namespace isc

#endif // DNS_CLIENT_H
