/*
 *  Copyright (c) 2017-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.
 */

#ifndef DNS_CLIENT_HPP_
#define DNS_CLIENT_HPP_

#include "openthread-core-config.h"

#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE

#include <openthread/dns_client.h>

#include "common/as_core_type.hpp"
#include "common/clearable.hpp"
#include "common/message.hpp"
#include "common/non_copyable.hpp"
#include "common/timer.hpp"
#include "net/dns_types.hpp"
#include "net/ip6.hpp"
#include "net/netif.hpp"

/**
 * @file
 *   This file includes definitions for the DNS client.
 */

#if OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE

#if !OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
#error "DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE requires OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE"
#endif

#if !OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
#error "DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE requires OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE"
#endif

#endif

/**
 * This struct represents an opaque (and empty) type for a response to an address resolution DNS query.
 *
 */
struct otDnsAddressResponse
{
};

#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE

/**
 * This struct represents an opaque (and empty) type for a response to browse (service instance enumeration) DNS query.
 *
 */
struct otDnsBrowseResponse
{
};

/**
 * This struct represents an opaque (and empty) type for a response to service inst resolution DNS query.
 *
 */
struct otDnsServiceResponse
{
};

#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE

namespace ot {

namespace Srp {
class Client;
}

namespace Dns {

/**
 * This class implements DNS client.
 *
 */
class Client : public InstanceLocator, private NonCopyable
{
    friend class ot::Srp::Client;

    typedef Message Query; // `Message` is used to save `Query` related info.

public:
    /**
     * This type represents a DNS query configuration (e.g., server address, response wait timeout, etc).
     *
     */
    class QueryConfig : public otDnsQueryConfig, public Clearable<QueryConfig>
    {
        friend class Client;

    public:
        /**
         * This enumeration type represents the "Recursion Desired" (RD) flag in a `otDnsQueryConfig`.
         *
         */
        enum RecursionFlag : uint8_t
        {
            kFlagUnspecified      = OT_DNS_FLAG_UNSPECIFIED,       ///< The flag is not specified.
            kFlagRecursionDesired = OT_DNS_FLAG_RECURSION_DESIRED, ///< Server can resolve the query recursively.
            kFlagNoRecursion      = OT_DNS_FLAG_NO_RECURSION,      ///< Server can not resolve the query recursively.
        };

#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
        /**
         * This enumeration type represents the NAT64 mode.
         *
         */
        enum Nat64Mode : uint8_t
        {
            kNat64Unspecified = OT_DNS_NAT64_UNSPECIFIED, ///< NAT64 mode is not specified. Use default NAT64 mode.
            kNat64Allow       = OT_DNS_NAT64_ALLOW,       ///< Allow NAT64 address translation
            kNat64Disallow    = OT_DNS_NAT64_DISALLOW,    ///< Disallow NAT64 address translation.
        };
#endif

        /**
         * This is the default constructor for `QueryConfig` object.
         *
         */
        QueryConfig(void) = default;

        /**
         * This method gets the server socket address (IPv6 address and port number).
         *
         * @returns The server socket address.
         *
         */
        const Ip6::SockAddr &GetServerSockAddr(void) const
        {
            return static_cast<const Ip6::SockAddr &>(mServerSockAddr);
        }

        /**
         * This method gets the wait time to receive response from server (in msec).
         *
         * @returns The timeout interval in msec.
         *
         */
        uint32_t GetResponseTimeout(void) const { return mResponseTimeout; }

        /**
         * This method gets the maximum number of query transmit attempts before reporting failure.
         *
         * @returns The maximum number of query transmit attempts.
         *
         */
        uint8_t GetMaxTxAttempts(void) const { return mMaxTxAttempts; }

        /**
         * This method gets the recursion flag indicating whether the server can resolve the query recursively or not.
         *
         * @returns The recursion flag.
         *
         */
        RecursionFlag GetRecursionFlag(void) const { return static_cast<RecursionFlag>(mRecursionFlag); }

#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
        /**
         * This method gets the NAT64 mode.
         *
         * @returns The NAT64 mode.
         *
         */
        Nat64Mode GetNat64Mode(void) const { return static_cast<Nat64Mode>(mNat64Mode); }
#endif

    private:
        static constexpr uint32_t kDefaultResponseTimeout = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT;
        static constexpr uint16_t kDefaultServerPort      = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_PORT;
        static constexpr uint8_t  kDefaultMaxTxAttempts   = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_MAX_TX_ATTEMPTS;
        static constexpr bool kDefaultRecursionDesired    = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RECURSION_DESIRED_FLAG;

#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
        static constexpr bool kDefaultNat64Allowed = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_NAT64_ALLOWED;
#endif

        enum InitMode : uint8_t
        {
            kInitFromDefaults,
        };

        static const char kDefaultServerAddressString[];

        explicit QueryConfig(InitMode aMode);

        Ip6::SockAddr &GetServerSockAddr(void) { return AsCoreType(&mServerSockAddr); }

        void SetResponseTimeout(uint32_t aResponseTimeout) { mResponseTimeout = aResponseTimeout; }
        void SetMaxTxAttempts(uint8_t aMaxTxAttempts) { mMaxTxAttempts = aMaxTxAttempts; }
        void SetRecursionFlag(RecursionFlag aFlag) { mRecursionFlag = static_cast<otDnsRecursionFlag>(aFlag); }
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
        void SetNat64Mode(Nat64Mode aMode) { mNat64Mode = static_cast<otDnsNat64Mode>(aMode); }
#endif

        void SetFrom(const QueryConfig &aConfig, const QueryConfig &aDefaultConfig);
    };

#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
    /**
     * This structure provides info for a DNS service instance.
     *
     */
    typedef otDnsServiceInfo ServiceInfo;
#endif

    /**
     * This class represents a DNS query response.
     *
     */
    class Response : public otDnsAddressResponse,
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
                     public otDnsBrowseResponse,
                     public otDnsServiceResponse,
#endif
                     public Clearable<Response>
    {
        friend class Client;

    protected:
        enum Section : uint8_t
        {
            kAnswerSection,
            kAdditionalDataSection,
        };

        Response(void) { Clear(); }

        Error GetName(char *aNameBuffer, uint16_t aNameBufferSize) const;
        void  SelectSection(Section aSection, uint16_t &aOffset, uint16_t &aNumRecord) const;
        Error CheckForHostNameAlias(Section aSection, Name &aHostName) const;
        Error FindHostAddress(Section       aSection,
                              const Name &  aHostName,
                              uint16_t      aIndex,
                              Ip6::Address &aAddress,
                              uint32_t &    aTtl) const;
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
        Error FindARecord(Section aSection, const Name &aHostName, uint16_t aIndex, ARecord &aARecord) const;
#endif

#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
        Error FindServiceInfo(Section aSection, const Name &aName, ServiceInfo &aServiceInfo) const;
#endif

        Instance *     mInstance;              // The OpenThread instance.
        Query *        mQuery;                 // The associated query.
        const Message *mMessage;               // The response message.
        uint16_t       mAnswerOffset;          // Answer section offset in `mMessage`.
        uint16_t       mAnswerRecordCount;     // Number of records in answer section.
        uint16_t       mAdditionalOffset;      // Additional data section offset in `mMessage`.
        uint16_t       mAdditionalRecordCount; // Number of records in additional data section.
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
        // This flag is only used in an IPv6 address query response.
        // It indicates that the response does not contain any IPv6
        // addresses but server provided at least one IPv4 address
        // in the additional data section for NAT64 address synthesis.
        bool mIp6QueryResponseRequiresNat64;
#endif
    };

    /**
     * This type represents the function pointer callback which is called when a DNS response for an address resolution
     * query is received.
     *
     */
    typedef otDnsAddressCallback AddressCallback;

    /**
     * This type represents an address resolution query DNS response.
     *
     */
    class AddressResponse : public Response
    {
        friend class Client;

    public:
        /**
         * This method gets the host name associated with an address resolution DNS response.
         *
         * This method MUST only be used from `AddressCallback`.
         *
         * @param[out] aNameBuffer       A buffer to char array to output the host name.
         * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
         *
         * @retval kErrorNone    The host name was read successfully.
         * @retval kErrorNoBufs  The name does not fit in @p aNameBuffer.
         *
         */
        Error GetHostName(char *aNameBuffer, uint16_t aNameBufferSize) const
        {
            return GetName(aNameBuffer, aNameBufferSize);
        }

        /**
         * This method gets the IPv6 address associated with an address resolution DNS response.
         *
         * This method MUST only be used from `AddressCallback`.
         *
         * The response may include multiple IPv6 address records. @p aIndex can be used to iterate through the list of
         * addresses. Index zero gets the the first address and so on. When we reach end of the list, this method
         * returns `kErrorNotFound`.
         *
         * @param[in]  aIndex        The address record index to retrieve.
         * @param[out] aAddress      A reference to an IPv6 address to output the address.
         * @param[out] aTtl          A reference to a `uint32_t` to output TTL for the address.
         *
         * @retval kErrorNone          The address was read successfully.
         * @retval kErrorNotFound      No address record at @p aIndex.
         * @retval kErrorParse         Could not parse the records.
         * @retval kErrorInvalidState  No NAT64 prefix (applicable only when NAT64 is allowed).
         *
         */
        Error GetAddress(uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const;

    private:
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
        Error GetNat64Prefix(Ip6::Prefix &aPrefix) const;
#endif
    };

#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE

    /**
     * This type represents the function pointer callback which is called when a response for a browse (service
     * instance enumeration) DNS query is received.
     *
     */
    typedef otDnsBrowseCallback BrowseCallback;

    /**
     * This type represents a browse (service instance enumeration) DNS response.
     *
     */
    class BrowseResponse : public Response
    {
        friend class Client;

    public:
        /**
         * This method gets the service name associated with a DNS browse response.
         *
         * This method MUST only be used from `BrowseCallback`.
         *
         * @param[out] aNameBuffer       A buffer to char array to output the host name.
         * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
         *
         * @retval kErrorNone    The host name was read successfully.
         * @retval kErrorNoBufs  The name does not fit in @p aNameBuffer.
         *
         */
        Error GetServiceName(char *aNameBuffer, uint16_t aNameBufferSize) const
        {
            return GetName(aNameBuffer, aNameBufferSize);
        }

        /**
         * This method gets a service instance associated with a DNS browse (service instance enumeration) response.
         *
         * This method MUST only be used from `BrowseCallback`.
         *
         * A response may include multiple service instance records. @p aIndex can be used to iterate through the list.
         * Index zero gives the the first record. When we reach end of the list, `kErrorNotFound` is returned.
         *
         * Note that this method gets the service instance label and not the full service instance name which is of the
         * form `<Instance>.<Service>.<Domain>`.
         *
         * @param[in]  aIndex             The service instance record index to retrieve.
         * @param[out] aLabelBuffer       A char array to output the service instance label (MUST NOT be NULL).
         * @param[in]  aLabelBufferSize   The size of @p aLabelBuffer.
         *
         * @retval kErrorNone         The service instance was read successfully.
         * @retval kErrorNoBufs       The name does not fit in @p aNameBuffer.
         * @retval kErrorNotFound     No service instance record at @p aIndex.
         * @retval kErrorParse        Could not parse the records.
         *
         */
        Error GetServiceInstance(uint16_t aIndex, char *aLabelBuffer, uint8_t aLabelBufferSize) const;

        /**
         * This method gets info for a service instance from a DNS browse (service instance enumeration) response.
         *
         * This method MUST only be used from `BrowseCallback`.
         *
         * A browse DNS response should include the SRV, TXT, and AAAA records for the service instances that are
         * enumerated (note that it is a SHOULD and not a MUST requirement). This method tries to retrieve this info
         * for a given service instance.
         *
         * - If no matching SRV record is found, `kErrorNotFound` is returned.
         * - If a matching SRV record is found, @p aServiceInfo is updated returning `kErrorNone`.
         * - If no matching TXT record is found, `mTxtDataSize` in @p aServiceInfo is set to zero.
         * - If no matching AAAA record is found, `mHostAddress is set to all zero or unspecified address.
         * - If there are multiple AAAA records for the host name `mHostAddress` is set to the first one. The other
         *   addresses can be retrieved using `GetHostAddress()` method.
         *
         * @param[in]  aInstanceLabel     The service instance label (MUST NOT be `nullptr`).
         * @param[out] aServiceInfo       A `ServiceInfo` to output the service instance information.
         *
         * @retval kErrorNone         The service instance info was read. @p aServiceInfo is updated.
         * @retval kErrorNotFound     Could not find a matching SRV record for @p aInstanceLabel.
         * @retval kErrorNoBufs       The host name and/or the TXT data could not fit in given buffers.
         * @retval kErrorParse        Could not parse the records.
         *
         */
        Error GetServiceInfo(const char *aInstanceLabel, ServiceInfo &aServiceInfo) const;

        /**
         * This method gets the host IPv6 address from a DNS browse (service instance enumeration) response.
         *
         * This method MUST only be used from `BrowseCallback`.
         *
         * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the
         * list of addresses. Index zero gets the first address and so on. When we reach end of the list, this method
         * returns `kErrorNotFound`.
         *
         * @param[in]  aHostName     The host name to get the address (MUST NOT be `nullptr`).
         * @param[in]  aIndex        The address record index to retrieve.
         * @param[out] aAddress      A reference to an IPv6 address to output the address.
         * @param[out] aTtl          A reference to a `uint32_t` to output TTL for the address.
         *
         * @retval kErrorNone       The address was read successfully.
         * @retval kErrorNotFound   No address record for @p aHostname at @p aIndex.
         * @retval kErrorParse      Could not parse the records.
         *
         */
        Error GetHostAddress(const char *aHostName, uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const;

    private:
        Error FindPtrRecord(const char *aInstanceLabel, Name &aInstanceName) const;
    };

    /**
     * This type represents the function pointer callback which is called when a response for a service instance
     * resolution DNS query is received.
     *
     */
    typedef otDnsServiceCallback ServiceCallback;

    /**
     * This type represents a service instance resolution DNS response.
     *
     */
    class ServiceResponse : public Response
    {
        friend class Client;

    public:
        /**
         * This method gets the service instance name associated with a DNS service instance resolution response.
         *
         * This method MUST only be used from `ServiceCallback`.
         *
         * @param[out] aLabelBuffer      A buffer to char array to output the service instance label (MUST NOT be NULL).
         * @param[in]  aLabelBufferSize  The size of @p aLabelBuffer.
         * @param[out] aNameBuffer       A buffer to char array to output the rest of service name (can be NULL if user
         *                               is not interested in getting the name).
         * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
         *
         * @retval kErrorNone    The service instance name was read successfully.
         * @retval kErrorNoBufs  Either the label or name does not fit in the given buffers.
         *
         */
        Error GetServiceName(char *   aLabelBuffer,
                             uint8_t  aLabelBufferSize,
                             char *   aNameBuffer,
                             uint16_t aNameBufferSize) const;

        /**
         * This method gets info for a service instance from a DNS service instance resolution response.
         *
         * This method MUST only be used from `ServiceCallback`.
         *
         * - If no matching SRV record is found, `kErrorNotFound` is returned.
         * - If a matching SRV record is found, @p aServiceInfo is updated and `kErrorNone` is returned.
         * - If no matching TXT record is found, `mTxtDataSize` in @p aServiceInfo is set to zero.
         * - If no matching AAAA record is found, `mHostAddress is set to all zero or unspecified address.
         * - If there are multiple AAAA records for the host name, `mHostAddress` is set to the first one. The other
         *   addresses can be retrieved using `GetHostAddress()` method.
         *
         * @param[out] aServiceInfo       A `ServiceInfo` to output the service instance information
         *
         * @retval kErrorNone         The service instance info was read. @p aServiceInfo is updated.
         * @retval kErrorNotFound     Could not find a matching SRV record.
         * @retval kErrorNoBufs       The host name and/or TXT data could not fit in the given buffers.
         * @retval kErrorParse        Could not parse the records in the @p aResponse.
         *
         */
        Error GetServiceInfo(ServiceInfo &aServiceInfo) const;

        /**
         * This method gets the host IPv6 address from a DNS service instance resolution response.
         *
         * This method MUST only be used from `ServiceCallback`.
         *
         * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the
         * list of addresses. Index zero gets the first address and so on. When we reach end of the list, this method
         * returns `kErrorNotFound`.
         *
         * @param[in]  aHostName     The host name to get the address (MUST NOT be `nullptr`).
         * @param[in]  aIndex        The address record index to retrieve.
         * @param[out] aAddress      A reference to an IPv6 address to output the address.
         * @param[out] aTtl          A reference to a `uint32_t` to output TTL for the address.
         *
         * @retval kErrorNone       The address was read successfully.
         * @retval kErrorNotFound   No address record for @p aHostname at @p aIndex.
         * @retval kErrorParse      Could not parse the records.
         *
         */
        Error GetHostAddress(const char *aHostName, uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const;
    };

#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE

    /**
     * This constructor initializes the object.
     *
     * @param[in]  aInstance     A reference to the OpenThread instance.
     *
     */
    explicit Client(Instance &aInstance);

    /**
     * This method starts the DNS client.
     *
     * @retval kErrorNone     Successfully started the DNS client.
     * @retval kErrorAlready  The socket is already open.
     *
     */
    Error Start(void);

    /**
     * This method stops the DNS client.
     *
     */
    void Stop(void);

    /**
     * This method gets the current default query config being used by DNS client.
     *
     * @returns The current default query config.
     *
     */
    const QueryConfig &GetDefaultConfig(void) const { return mDefaultConfig; }

    /**
     * This method sets the default query config.
     *
     * @param[in] aQueryConfig   The new default query config.
     *
     */
    void SetDefaultConfig(const QueryConfig &aQueryConfig);

    /**
     * This method resets the default config to the config used when the OpenThread stack starts.
     *
     * When OpenThread stack starts, the default DNS query config is determined from a set of OT config options such as
     * `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_IP6_ADDRESS`, `_DEFAULT_SERVER_PORT`, or `_DEFAULT_RESPONSE_TIMEOUT`
     * etc. (see `config/dns_client.h` for all related config options).
     *
     */
    void ResetDefaultConfig(void);

    /**
     * This method sends an address resolution DNS query for AAAA (IPv6) record for a given host name.
     *
     * The @p aConfig can be nullptr. In this case the default config (from `GetDefaultConfig()`) will be used as
     * the config for this query. In a non-nullptr @p aConfig, some of the fields can be left unspecified (value zero).
     * The unspecified fields are then replaced by the values from the default config.
     *
     * @param[in]  aHostName        The host name for which to query the address (MUST NOT be `nullptr`).
     * @param[in]  aCallback        A callback function pointer to report the result of query.
     * @param[in]  aContext         A pointer to arbitrary context information passed to @p aCallback.
     * @param[in]  aConfig          The config to use for this query.
     *
     * @retval kErrorNone           Successfully sent DNS query.
     * @retval kErrorNoBufs         Failed to allocate retransmission data.
     * @retval kErrorInvalidArgs    The host name is not valid format.
     * @retval kErrorInvalidState   Cannot send query since Thread interface is not up.
     *
     */
    Error ResolveAddress(const char *       aHostName,
                         AddressCallback    aCallback,
                         void *             aContext,
                         const QueryConfig *aConfig = nullptr);

#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
    /**
     * This method sends an address resolution DNS query for A (IPv4) record for a given host name.
     *
     * When a successful response is received, the addresses are returned from @p aCallback as NAT64 IPv6 translated
     * versions of the IPv4 addresses from the query response.
     *
     * The @p aConfig can be nullptr. In this case the default config (from `GetDefaultConfig()`) will be used as
     * the config for this query. In a non-nullptr @p aConfig, some of the fields can be left unspecified (value zero).
     * The unspecified fields are then replaced by the values from the default config.
     *
     * @param[in]  aHostName        The host name for which to query the address (MUST NOT be `nullptr`).
     * @param[in]  aCallback        A callback function pointer to report the result of query.
     * @param[in]  aContext         A pointer to arbitrary context information passed to @p aCallback.
     * @param[in]  aConfig          The config to use for this query.
     *
     * @retval kErrorNone           Successfully sent DNS query.
     * @retval kErrorNoBufs         Failed to allocate retransmission data.
     * @retval kErrorInvalidArgs    The host name is not valid format or NAT64 is not enabled in config.
     * @retval kErrorInvalidState   Cannot send query since Thread interface is not up, or there is no NAT64 prefix.
     *
     */
    Error ResolveIp4Address(const char *       aHostName,
                            AddressCallback    aCallback,
                            void *             aContext,
                            const QueryConfig *aConfig = nullptr);
#endif

#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE

    /**
     * This method sends a browse (service instance enumeration) DNS query for a given service name.
     *
     * The @p aConfig can be nullptr. In this case the default config (from `GetDefaultConfig()`) will be used as
     * the config for this query. In a non-nullptr @p aConfig, some of the fields can be left unspecified (value zero).
     * The unspecified fields are then replaced by the values from the default config.
     *
     * @param[in]  aServiceName     The service name to query for (MUST NOT be `nullptr`).
     * @param[in]  aCallback        The callback to report the response or errors (such as time-out).
     * @param[in]  aContext         A pointer to arbitrary context information.
     * @param[in]  aConfig          The config to use for this query.
     *
     * @retval kErrorNone       Query sent successfully. @p aCallback will be invoked to report the status.
     * @retval kErrorNoBufs     Insufficient buffer to prepare and send query.
     *
     */
    Error Browse(const char *       aServiceName,
                 BrowseCallback     aCallback,
                 void *             aContext,
                 const QueryConfig *aConfig = nullptr);

    /**
     * This method sends a DNS service instance resolution query for a given service instance.
     *
     * The @p aConfig can be `nullptr`. In this case the default config (from `GetDefaultConfig()`) will be used as
     * the config for this query. In a non-`nullptr` @p aConfig, some of the fields can be left unspecified (value
     * zero). The unspecified fields are then replaced by the values from the default config.
     *
     * @param[in]  aInstanceLabel     The service instance label.
     * @param[in]  aServiceName       The service name (together with @p aInstanceLabel form full instance name).
     * @param[in]  aCallback          A function pointer that shall be called on response reception or time-out.
     * @param[in]  aContext           A pointer to arbitrary context information.
     * @param[in]  aConfig            The config to use for this query.
     *
     * @retval kErrorNone         Query sent successfully. @p aCallback will be invoked to report the status.
     * @retval kErrorNoBufs       Insufficient buffer to prepare and send query.
     * @retval kErrorInvalidArgs  @p aInstanceLabel is `nullptr`.
     *
     */
    Error ResolveService(const char *         aInstanceLabel,
                         const char *         aServiceName,
                         otDnsServiceCallback aCallback,
                         void *               aContext,
                         const QueryConfig *  aConfig = nullptr);

#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE

private:
    enum QueryType : uint8_t
    {
        kIp6AddressQuery, // IPv6 Address resolution.
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
        kIp4AddressQuery, // IPv4 Address resolution
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
        kBrowseQuery,  // Browse (service instance enumeration).
        kServiceQuery, // Service instance resolution.
#endif
    };

    union Callback
    {
        AddressCallback mAddressCallback;
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
        BrowseCallback  mBrowseCallback;
        ServiceCallback mServiceCallback;
#endif
    };

    typedef MessageQueue QueryList; // List of queries.

    struct QueryInfo : public Clearable<QueryInfo> // Query related Info
    {
        void ReadFrom(const Query &aQuery) { IgnoreError(aQuery.Read(0, *this)); }

        QueryType   mQueryType;
        uint16_t    mMessageId;
        Callback    mCallback;
        void *      mCallbackContext;
        TimeMilli   mRetransmissionTime;
        QueryConfig mConfig;
        uint8_t     mTransmissionCount;
        // Followed by the name (service, host, instance) encoded as a `Dns::Name`.
    };

    static constexpr uint16_t kNameOffsetInQuery = sizeof(QueryInfo);

    Error       StartQuery(QueryInfo &        aInfo,
                           const QueryConfig *aConfig,
                           const char *       aLabel,
                           const char *       aName,
                           void *             aContext);
    Error       AllocateQuery(const QueryInfo &aInfo, const char *aLabel, const char *aName, Query *&aQuery);
    void        FreeQuery(Query &aQuery);
    void        UpdateQuery(Query &aQuery, const QueryInfo &aInfo) { aQuery.Write(0, aInfo); }
    void        SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer);
    void        FinalizeQuery(Query &aQuery, Error aError);
    void        FinalizeQuery(Response &Response, QueryType aType, Error aError);
    static void GetCallback(const Query &aQuery, Callback &aCallback, void *&aContext);
    Error       AppendNameFromQuery(const Query &aQuery, Message &aMessage);
    Query *     FindQueryById(uint16_t aMessageId);
    static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMsgInfo);
    void        ProcessResponse(const Message &aMessage);
    Error       ParseResponse(Response &aResponse, QueryType &aType, Error &aResponseError);
    static void HandleTimer(Timer &aTimer);
    void        HandleTimer(void);
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
    Error CheckAddressResponse(Response &aResponse, Error aResponseError) const;
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE
    void UpdateDefaultConfigAddress(void);
#endif

    static const uint8_t   kQuestionCount[];
    static const uint16_t *kQuestionRecordTypes[];

    static const uint16_t kIp6AddressQueryRecordTypes[];
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
    static const uint16_t kIp4AddressQueryRecordTypes[];
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
    static const uint16_t kBrowseQueryRecordTypes[];
    static const uint16_t kServiceQueryRecordTypes[];
#endif

    Ip6::Udp::Socket mSocket;
    QueryList        mQueries;
    TimerMilli       mTimer;
    QueryConfig      mDefaultConfig;
#if OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE
    bool mUserDidSetDefaultAddress;
#endif
};

} // namespace Dns

DefineCoreType(otDnsQueryConfig, Dns::Client::QueryConfig);
DefineCoreType(otDnsAddressResponse, Dns::Client::AddressResponse);
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
DefineCoreType(otDnsBrowseResponse, Dns::Client::BrowseResponse);
DefineCoreType(otDnsServiceResponse, Dns::Client::ServiceResponse);
DefineCoreType(otDnsServiceInfo, Dns::Client::ServiceInfo);
#endif

} // namespace ot

#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE

#endif // DNS_CLIENT_HPP_
