/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "resolv"

#include <aidl/android/net/IDnsResolver.h>
#include <android-base/format.h>
#include <android-base/logging.h>
#include <arpa/inet.h>
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include <netdb.h>
#include <netdutils/InternetAddresses.h>
#include <netdutils/NetNativeTestBase.h>
#include <resolv_stats_test_utils.h>

#include "dns_responder.h"
#include "getaddrinfo.h"
#include "gethnamaddr.h"
#include "resolv_cache.h"
#include "stats.pb.h"
#include "tests/resolv_test_utils.h"

#define NAME(variable) #variable

namespace android {
namespace net {

using aidl::android::net::IDnsResolver;
using android::net::NetworkDnsEventReported;
using android::netdutils::ScopedAddrinfo;

// The buffer size of resolv_gethostbyname().
constexpr unsigned int MAXPACKET = 8 * 1024;

// Specifying 0 in ai_socktype or ai_protocol of struct addrinfo indicates
// that any type or protocol can be returned by getaddrinfo().
constexpr unsigned int ANY = 0;

class TestBase : public NetNativeTestBase {
  protected:
    struct DnsMessage {
        std::string host_name;   // host name
        ns_type type;            // record type
        test::DNSHeader header;  // dns header
    };

    void SetUp() override {
        // Create cache for test
        resolv_create_cache_for_net(TEST_NETID);
        SetMdnsRoute();
    }
    void TearDown() override {
        // Delete cache for test
        resolv_delete_cache_for_net(TEST_NETID);
        RemoveMdnsRoute();
    }

    test::DNSRecord MakeAnswerRecord(const std::string& name, unsigned rclass, unsigned rtype,
                                     const std::string& rdata, unsigned ttl = kAnswerRecordTtlSec) {
        test::DNSRecord record{
                .name = {.name = name},
                .rtype = rtype,
                .rclass = rclass,
                .ttl = ttl,
        };
        EXPECT_TRUE(test::DNSResponder::fillRdata(rdata, record));
        return record;
    }

    DnsMessage MakeDnsMessage(const std::string& qname, ns_type qtype,
                              const std::vector<std::string>& rdata) {
        const unsigned qclass = ns_c_in;
        // Build a DNSHeader in the following format.
        // Question
        //   <qname>                IN      <qtype>
        // Answer
        //   <qname>                IN      <qtype>     <rdata[0]>
        //   ..
        //   <qname>                IN      <qtype>     <rdata[n]>
        //
        // Example:
        // Question
        //   hello.example.com.     IN      A
        // Answer
        //   hello.example.com.     IN      A           1.2.3.1
        //   ..
        //   hello.example.com.     IN      A           1.2.3.9
        test::DNSHeader header(kDefaultDnsHeader);

        // Question section
        test::DNSQuestion question{
                .qname = {.name = qname},
                .qtype = qtype,
                .qclass = qclass,
        };
        header.questions.push_back(std::move(question));

        // Answer section
        for (const auto& r : rdata) {
            test::DNSRecord record = MakeAnswerRecord(qname, qclass, qtype, r);
            header.answers.push_back(std::move(record));
        }
        // TODO: Perhaps add support for authority RRs and additional RRs.
        return {qname, qtype, header};
    }

    void StartDns(test::DNSResponder& dns, const std::vector<DnsMessage>& messages) {
        for (const auto& m : messages) {
            dns.addMappingDnsHeader(m.host_name, m.type, m.header);
        }
        ASSERT_TRUE(dns.startServer());
        dns.clearQueries();
    }

    int SetResolvers() { return SetResolvers(servers); }

    int SetResolvers(std::vector<std::string> servers) {
        return resolv_set_nameservers(TEST_NETID, servers, domains, params, std::nullopt);
    }

    const android_net_context mNetcontext = {
            .app_netid = TEST_NETID,
            .app_mark = MARK_UNSET,
            .dns_netid = TEST_NETID,
            .dns_mark = MARK_UNSET,
            .uid = NET_CONTEXT_INVALID_UID,
    };
    const std::vector<std::string> servers = {test::kDefaultListenAddr};
    const std::vector<std::string> domains = {"example.com"};
    const res_params params = {
            .sample_validity = 300,
            .success_threshold = 25,
            .min_samples = 8,
            .max_samples = 8,
            .base_timeout_msec = 1000,
            .retry_count = 2,
    };
};

class ResolvGetAddrInfoTest : public TestBase {};
class GetHostByNameForNetContextTest : public TestBase {};
class ResolvCommonFunctionTest : public TestBase {};

TEST_F(ResolvGetAddrInfoTest, InvalidParameters) {
    // Both null "netcontext" and null "res" of resolv_getaddrinfo() are not tested
    // here because they are checked by assert() without returning any error number.

    // Invalid hostname and servname.
    // Both hostname and servname are null pointers. Expect error number EAI_NONAME.
    {
        addrinfo* result = nullptr;
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo(nullptr /*hostname*/, nullptr /*servname*/, nullptr /*hints*/,
                                    &mNetcontext, &result, &event);
        ScopedAddrinfo result_cleanup(result);
        EXPECT_EQ(EAI_NONAME, rv);
    }

    // Invalid hints.
    // These place holders are used to test function call with unrequired parameters.
    // The content is not important because function call returns error directly if
    // there have any unrequired parameter.
    char placeholder_cname[] = "invalid_cname";
    sockaddr placeholder_addr = {};
    addrinfo placeholder_next = {};
    static const struct TestConfig {
        int ai_flags;
        socklen_t ai_addrlen;
        char* ai_canonname;
        sockaddr* ai_addr;
        addrinfo* ai_next;

        int expected_eai_error;

        std::string asParameters() const {
            return fmt::format("{:#x}/{}/{}/{}/{}", ai_flags, ai_addrlen,
                               ai_canonname ? ai_canonname : "(null)", (void*)ai_addr,
                               (void*)ai_next);
        }
    } testConfigs[]{
            {0, sizeof(in_addr) /*bad*/, nullptr, nullptr, nullptr, EAI_BADHINTS},
            {0, 0, placeholder_cname /*bad*/, nullptr, nullptr, EAI_BADHINTS},
            {0, 0, nullptr, &placeholder_addr /*bad*/, nullptr, EAI_BADHINTS},
            {0, 0, nullptr, nullptr, &placeholder_next /*bad*/, EAI_BADHINTS},
            {AI_ALL /*bad*/, 0, nullptr, nullptr, nullptr, EAI_BADFLAGS},
            {AI_V4MAPPED_CFG /*bad*/, 0, nullptr, nullptr, nullptr, EAI_BADFLAGS},
            {AI_V4MAPPED /*bad*/, 0, nullptr, nullptr, nullptr, EAI_BADFLAGS},
            {AI_DEFAULT /*bad*/, 0, nullptr, nullptr, nullptr, EAI_BADFLAGS},
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(config.asParameters());

        addrinfo* result = nullptr;
        // In current test configuration set, ai_family, ai_protocol and ai_socktype are not
        // checked because other fields cause hints error check failed first.
        const addrinfo hints = {
                .ai_flags = config.ai_flags,
                .ai_family = AF_UNSPEC,
                .ai_socktype = ANY,
                .ai_protocol = ANY,
                .ai_addrlen = config.ai_addrlen,
                .ai_canonname = config.ai_canonname,
                .ai_addr = config.ai_addr,
                .ai_next = config.ai_next,
        };
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("localhost", nullptr /*servname*/, &hints, &mNetcontext,
                                    &result, &event);
        ScopedAddrinfo result_cleanup(result);
        EXPECT_EQ(config.expected_eai_error, rv);
    }
}

TEST_F(ResolvGetAddrInfoTest, InvalidParameters_Family) {
    for (int family = 0; family < AF_MAX; ++family) {
        if (family == AF_UNSPEC || family == AF_INET || family == AF_INET6) {
            continue;  // skip supported family
        }
        SCOPED_TRACE(fmt::format("family: {}", family));

        addrinfo* result = nullptr;
        const addrinfo hints = {
                .ai_family = family,  // unsupported family
        };
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("localhost", nullptr /*servname*/, &hints, &mNetcontext,
                                    &result, &event);
        ScopedAddrinfo result_cleanup(result);
        EXPECT_EQ(EAI_FAMILY, rv);
    }
}

TEST_F(ResolvGetAddrInfoTest, InvalidParameters_SocketType) {
    for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
        for (int protocol = 0; protocol < IPPROTO_MAX; ++protocol) {
            // Socket types which are not in explore_options.
            for (const auto& socktype : {SOCK_RDM, SOCK_SEQPACKET, SOCK_DCCP, SOCK_PACKET}) {
                const addrinfo hints = {
                        .ai_family = family,
                        .ai_socktype = socktype,
                        .ai_protocol = protocol,
                };
                for (const char* service : {static_cast<const char*>(nullptr),  // service is null
                                            "80",
                                            "",  // empty service name
                                            "ftp",
                                            "65536",  // out of valid port range from 0 to 65535
                                            "invalid"}) {
                    SCOPED_TRACE(fmt::format("family: {}, socktype: {}, protocol: {}, service: {}",
                                             family, socktype, protocol,
                                             service ? service : "service is nullptr"));
                    addrinfo* result = nullptr;
                    NetworkDnsEventReported event;
                    int rv = resolv_getaddrinfo("localhost", service, &hints, &mNetcontext, &result,
                                                &event);
                    ScopedAddrinfo result_cleanup(result);
                    EXPECT_EQ(EAI_SOCKTYPE, rv);
                }
            }
        }
    }
}

TEST_F(ResolvGetAddrInfoTest, InvalidParameters_MeaningfulSocktypeAndProtocolCombination) {
    static const int families[] = {PF_INET, PF_INET6, PF_UNSPEC};
    // Skip to test socket type SOCK_RAW in meaningful combination (explore_options[]) of
    // system\netd\resolv\getaddrinfo.cpp. In explore_options[], the socket type SOCK_RAW always
    // comes with protocol ANY which causes skipping meaningful socktype/protocol combination
    // check. So it nerver returns error number EAI_BADHINTS which we want to test in this test
    // case.
    static const int socktypes[] = {SOCK_STREAM, SOCK_DGRAM};

    // If both socktype/protocol are specified, check non-meaningful combination returns
    // expected error number EAI_BADHINTS. See meaningful combination in explore_options[] of
    // system\netd\resolv\getaddrinfo.cpp.
    for (const auto& family : families) {
        for (const auto& socktype : socktypes) {
            for (int protocol = 0; protocol < IPPROTO_MAX; ++protocol) {
                SCOPED_TRACE(fmt::format("family: {}, socktype: {}, protocol: {}", family, socktype,
                                         protocol));

                // Both socktype/protocol need to be specified.
                if (!socktype || !protocol) continue;

                // Skip meaningful combination in explore_options[] of
                // system\netd\resolv\getaddrinfo.cpp.
                if ((family == AF_INET6 && socktype == SOCK_DGRAM && protocol == IPPROTO_UDP) ||
                    (family == AF_INET6 && socktype == SOCK_STREAM && protocol == IPPROTO_TCP) ||
                    (family == AF_INET && socktype == SOCK_DGRAM && protocol == IPPROTO_UDP) ||
                    (family == AF_INET && socktype == SOCK_STREAM && protocol == IPPROTO_TCP) ||
                    (family == AF_UNSPEC && socktype == SOCK_DGRAM && protocol == IPPROTO_UDP) ||
                    (family == AF_UNSPEC && socktype == SOCK_STREAM && protocol == IPPROTO_TCP)) {
                    continue;
                }

                addrinfo* result = nullptr;
                const addrinfo hints = {
                        .ai_family = family,
                        .ai_socktype = socktype,
                        .ai_protocol = protocol,
                };
                NetworkDnsEventReported event;
                int rv = resolv_getaddrinfo("localhost", nullptr /*servname*/, &hints, &mNetcontext,
                                            &result, &event);
                ScopedAddrinfo result_cleanup(result);
                EXPECT_EQ(EAI_BADHINTS, rv);
            }
        }
    }
}

// The test configs are used for verifying the error path of get_port().
// Note that the EAI_SOCKTYPE verification are moved to an independent
// test case because validateHints() verify invalid socket type early now.
// See also InvalidParameters_SocketType.
TEST_F(ResolvGetAddrInfoTest, InvalidParameters_PortNameAndNumber) {
    constexpr char http_portno[] = "80";
    constexpr char invalid_portno[] = "65536";  // out of valid port range from 0 to 65535
    constexpr char http_portname[] = "http";
    constexpr char invalid_portname[] = "invalid_portname";

    static const struct TestConfig {
        int ai_flags;
        int ai_family;
        int ai_socktype;
        const char* servname;

        int expected_eai_error;

        std::string asParameters() const {
            return fmt::format("{:#x}/{}/{}/{}", ai_flags, ai_family, ai_socktype,
                               servname ? servname : "(null)");
        }
    } testConfigs[]{
            {0, AF_INET, SOCK_RAW /*bad*/, http_portno, EAI_SERVICE},
            {0, AF_INET6, SOCK_RAW /*bad*/, http_portno, EAI_SERVICE},
            {0, AF_UNSPEC, SOCK_RAW /*bad*/, http_portno, EAI_SERVICE},
            {0, AF_INET, ANY, invalid_portno /*bad*/, EAI_SERVICE},
            {0, AF_INET, SOCK_STREAM, invalid_portno /*bad*/, EAI_SERVICE},
            {0, AF_INET, SOCK_DGRAM, invalid_portno /*bad*/, EAI_SERVICE},
            {0, AF_INET6, ANY, invalid_portno /*bad*/, EAI_SERVICE},
            {0, AF_INET6, SOCK_STREAM, invalid_portno /*bad*/, EAI_SERVICE},
            {0, AF_INET6, SOCK_DGRAM, invalid_portno /*bad*/, EAI_SERVICE},
            {0, AF_UNSPEC, ANY, invalid_portno /*bad*/, EAI_SERVICE},
            {0, AF_UNSPEC, SOCK_STREAM, invalid_portno /*bad*/, EAI_SERVICE},
            {0, AF_UNSPEC, SOCK_DGRAM, invalid_portno /*bad*/, EAI_SERVICE},
            {AI_NUMERICSERV, AF_INET, ANY, http_portname /*bad*/, EAI_NONAME},
            {AI_NUMERICSERV, AF_INET, SOCK_STREAM, http_portname /*bad*/, EAI_NONAME},
            {AI_NUMERICSERV, AF_INET, SOCK_DGRAM, http_portname /*bad*/, EAI_NONAME},
            {AI_NUMERICSERV, AF_INET6, ANY, http_portname /*bad*/, EAI_NONAME},
            {AI_NUMERICSERV, AF_INET6, SOCK_STREAM, http_portname /*bad*/, EAI_NONAME},
            {AI_NUMERICSERV, AF_INET6, SOCK_DGRAM, http_portname /*bad*/, EAI_NONAME},
            {AI_NUMERICSERV, AF_UNSPEC, ANY, http_portname /*bad*/, EAI_NONAME},
            {AI_NUMERICSERV, AF_UNSPEC, SOCK_STREAM, http_portname /*bad*/, EAI_NONAME},
            {AI_NUMERICSERV, AF_UNSPEC, SOCK_DGRAM, http_portname /*bad*/, EAI_NONAME},
            {0, AF_INET, ANY, invalid_portname /*bad*/, EAI_SERVICE},
            {0, AF_INET, SOCK_STREAM, invalid_portname /*bad*/, EAI_SERVICE},
            {0, AF_INET, SOCK_DGRAM, invalid_portname /*bad*/, EAI_SERVICE},
            {0, AF_INET6, ANY, invalid_portname /*bad*/, EAI_SERVICE},
            {0, AF_INET6, SOCK_STREAM, invalid_portname /*bad*/, EAI_SERVICE},
            {0, AF_INET6, SOCK_DGRAM, invalid_portname /*bad*/, EAI_SERVICE},
            {0, AF_UNSPEC, ANY, invalid_portname /*bad*/, EAI_SERVICE},
            {0, AF_UNSPEC, SOCK_STREAM, invalid_portname /*bad*/, EAI_SERVICE},
            {0, AF_UNSPEC, SOCK_DGRAM, invalid_portname /*bad*/, EAI_SERVICE},
    };

    for (const auto& config : testConfigs) {
        const std::string testParameters = config.asParameters();
        SCOPED_TRACE(testParameters);

        const addrinfo hints = {
                .ai_flags = config.ai_flags,
                .ai_family = config.ai_family,
                .ai_socktype = config.ai_socktype,
        };

        addrinfo* result = nullptr;
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("localhost", config.servname, &hints, &mNetcontext, &result,
                                    &event);
        ScopedAddrinfo result_cleanup(result);
        EXPECT_EQ(config.expected_eai_error, rv);
    }
}

TEST_F(ResolvGetAddrInfoTest, AlphabeticalHostname_NoData) {
    constexpr char v4_host_name[] = "v4only.example.com.";
    // Following fields will not be verified during the test in proto NetworkDnsEventReported.
    // So don't need to config those values: event_type, return_code, latency_micros,
    // hints_ai_flags, res_nsend_flags, network_type, private_dns_modes.
    constexpr char event_ipv6[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 latency_micros: 0,
                 linux_errno: 0,
                },
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 latency_micros: 0,
                 linux_errno: 0,
                },
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 latency_micros: 0,
                 linux_errno: 0,
                },
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 latency_micros: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";

    test::DNSResponder dns;
    dns.addMapping(v4_host_name, ns_type::ns_t_a, "1.2.3.3");
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    // Want AAAA answer but DNS server has A answer only.
    addrinfo* result = nullptr;
    const addrinfo hints = {.ai_family = AF_INET6};
    NetworkDnsEventReported event;
    int rv = resolv_getaddrinfo("v4only", nullptr, &hints, &mNetcontext, &result, &event);
    EXPECT_THAT(event, NetworkDnsEventEq(fromNetworkDnsEventReportedStr(event_ipv6)));
    ScopedAddrinfo result_cleanup(result);
    EXPECT_LE(1U, GetNumQueries(dns, v4_host_name));
    EXPECT_EQ(nullptr, result);
    EXPECT_EQ(EAI_NODATA, rv);
}

TEST_F(ResolvGetAddrInfoTest, AlphabeticalHostname) {
    constexpr char host_name[] = "sawadee.example.com.";
    constexpr char v4addr[] = "1.2.3.4";
    constexpr char v6addr[] = "::1.2.3.4";
    // Following fields will not be verified during the test in proto NetworkDnsEventReported.
    // So don't need to config those values: event_type, return_code, latency_micros,
    // hints_ai_flags, res_nsend_flags, network_type, private_dns_modes.
    constexpr char event_ipv4[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                },
                {
                 rcode: 0,
                 type: 1,
                 cache_hit: 2,
                 ip_version: 0,
                 protocol: 0,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";

    constexpr char event_ipv6[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                },
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 2,
                 ip_version: 0,
                 protocol: 0,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";
    test::DNSResponder dns;
    dns.addMapping(host_name, ns_type::ns_t_a, v4addr);
    dns.addMapping(host_name, ns_type::ns_t_aaaa, v6addr);
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        int ai_family;
        const std::string expected_addr;
        const std::string expected_event;
    } testConfigs[]{
            {AF_INET, v4addr, event_ipv4},
            {AF_INET6, v6addr, event_ipv6},
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("family: {}", config.ai_family));
        dns.clearQueries();

        addrinfo* result = nullptr;
        const addrinfo hints = {.ai_family = config.ai_family};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("sawadee", nullptr, &hints, &mNetcontext, &result, &event);
        EXPECT_THAT(event,
                    NetworkDnsEventEq(fromNetworkDnsEventReportedStr(config.expected_event)));
        ScopedAddrinfo result_cleanup(result);
        EXPECT_EQ(0, rv);
        EXPECT_TRUE(result != nullptr);
        EXPECT_EQ(1U, GetNumQueries(dns, host_name));
        EXPECT_EQ(config.expected_addr, ToString(result));
    }
}

TEST_F(ResolvGetAddrInfoTest, IllegalHostname) {
    test::DNSResponder dns;
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    // Illegal hostname is verified by res_hnok() in system/netd/resolv/res_comp.cpp.
    static constexpr char const* illegalHostnames[] = {
            kBadCharAfterPeriodHost,
            kBadCharBeforePeriodHost,
            kBadCharAtTheEndHost,
            kBadCharInTheMiddleOfLabelHost,
    };

    for (const auto& hostname : illegalHostnames) {
        // Expect to get no address because hostname format is illegal.
        //
        // Ex:
        // ANSWER SECTION:
        // a.ex^ample.com.      IN  A       1.2.3.3
        // a.ex^ample.com.      IN  AAAA    2001:db8::42
        //
        // In this example, querying "a.ex^ample.com" should get no address because
        // "a.ex^ample.com" has an illegal char '^' in the middle of label.
        dns.addMapping(hostname, ns_type::ns_t_a, "1.2.3.3");
        dns.addMapping(hostname, ns_type::ns_t_aaaa, "2001:db8::42");

        for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
            SCOPED_TRACE(fmt::format("family: {}, config.name: {}", family, hostname));

            addrinfo* res = nullptr;
            const addrinfo hints = {.ai_family = family};
            NetworkDnsEventReported event;
            int rv = resolv_getaddrinfo(hostname, nullptr, &hints, &mNetcontext, &res, &event);
            ScopedAddrinfo result(res);
            EXPECT_EQ(nullptr, result);
            EXPECT_EQ(EAI_FAIL, rv);
        }
    }
}

TEST_F(ResolvGetAddrInfoTest, ServerResponseError) {
    constexpr char host_name[] = "hello.example.com.";

    static const struct TestConfig {
        ns_rcode rcode;
        int expected_eai_error;

        // Only test failure RCODE [1..5] in RFC 1035 section 4.1.1 and skip successful RCODE 0
        // which means no error.
    } testConfigs[]{
            // clang-format off
            {ns_rcode::ns_r_formerr,  EAI_FAIL},
            {ns_rcode::ns_r_servfail, EAI_AGAIN},
            {ns_rcode::ns_r_nxdomain, EAI_NODATA},
            {ns_rcode::ns_r_notimpl,  EAI_FAIL},
            {ns_rcode::ns_r_refused,  EAI_FAIL},
            // clang-format on
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("rcode: {}", static_cast<int>(config.rcode)));

        test::DNSResponder dns(config.rcode);
        dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
        dns.setResponseProbability(0.0);  // always ignore requests and response preset rcode
        ASSERT_TRUE(dns.startServer());
        ASSERT_EQ(0, SetResolvers());

        addrinfo* result = nullptr;
        const addrinfo hints = {.ai_family = AF_UNSPEC};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo(host_name, nullptr, &hints, &mNetcontext, &result, &event);
        EXPECT_EQ(config.expected_eai_error, rv);
    }
}

// TODO: Add private DNS server timeout test.
TEST_F(ResolvGetAddrInfoTest, ServerTimeout) {
    constexpr char host_name[] = "hello.example.com.";
    // Following fields will not be verified during the test in proto NetworkDnsEventReported.
    // So don't need to config those values: event_type, return_code, latency_micros,
    // hints_ai_flags, res_nsend_flags, network_type, private_dns_modes.
    // Expected_event is 16 DNS queries and only "type" and "retry_times" fields are changed.
    // 2(T_AAAA + T_A) * 2(w/ retry) * 2(query w/ and w/o domain) * 2(SOCK_DGRAM and SOCK_STREAM)
    constexpr char expected_event[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 255,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 1,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                }
                {
                 rcode: 255,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 1,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 1,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                }
                {
                 rcode: 255,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 1,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 1,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                }
                {
                 rcode: 255,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 1,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 1,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                }
                {
                 rcode: 255,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
                {
                 rcode: 255,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 1,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 110,
                },
               ]
             }
        })Event";
    test::DNSResponder dns(static_cast<ns_rcode>(-1) /*no response*/);
    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
    dns.setResponseProbability(0.0);  // always ignore requests and don't respond
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    addrinfo* result = nullptr;
    const addrinfo hints = {.ai_family = AF_UNSPEC};
    NetworkDnsEventReported event;
    int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &result, &event);
    EXPECT_THAT(event, NetworkDnsEventEq(fromNetworkDnsEventReportedStr(expected_event)));
    EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
}

TEST_F(ResolvGetAddrInfoTest, MdnsAlphabeticalHostname) {
    constexpr char v4addr[] = "127.0.0.3";
    constexpr char v6addr[] = "::127.0.0.3";
    constexpr char host_name[] = "hello.local.";
    // Following fields will not be verified during the test in proto NetworkDnsEventReported.
    // So don't need to config those values: event_type, return_code, latency_micros,
    // hints_ai_flags, res_nsend_flags, network_type, private_dns_modes.

    constexpr char event_ipv4[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 5,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                },
               ]
             }
        })Event";

    constexpr char event_ipv6[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 2,
                 protocol: 5,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                },
               ]
             }
        })Event";

    constexpr char event_ipv4v6[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 2,
                 protocol: 5,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                },
                {
                 rcode: 0,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 5,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";

    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService);
    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService);
    mdnsv4.addMapping(host_name, ns_type::ns_t_a, v4addr);
    mdnsv6.addMapping(host_name, ns_type::ns_t_aaaa, v6addr);
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        int ai_family;
        const std::vector<std::string> expected_addr;
        const std::string expected_event;
    } testConfigs[]{
            {AF_UNSPEC, {v4addr, v6addr}, event_ipv4v6},
            {AF_INET, {v4addr}, event_ipv4},
            {AF_INET6, {v6addr}, event_ipv6},
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("family: {}", config.ai_family));
        mdnsv4.clearQueries();
        mdnsv6.clearQueries();

        addrinfo* result = nullptr;
        const addrinfo hints = {.ai_family = config.ai_family, .ai_socktype = SOCK_DGRAM};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("hello.local", nullptr, &hints, &mNetcontext, &result, &event);
        EXPECT_THAT(event,
                    NetworkDnsEventEq(fromNetworkDnsEventReportedStr(config.expected_event)));
        ScopedAddrinfo result_cleanup(result);

        if (config.ai_family == AF_UNSPEC) {
            EXPECT_EQ(0, rv);
            EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
            EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
            const std::vector<std::string> result_strs = ToStrings(result);
            EXPECT_THAT(result_strs, testing::UnorderedElementsAreArray(config.expected_addr));
        } else if (config.ai_family == AF_INET) {
            EXPECT_EQ(0, rv);
            EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
            const std::vector<std::string> result_strs = ToStrings(result);
            EXPECT_THAT(result_strs, testing::UnorderedElementsAreArray(config.expected_addr));
        } else if (config.ai_family == AF_INET6) {
            EXPECT_EQ(0, rv);
            EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
            const std::vector<std::string> result_strs = ToStrings(result);
            EXPECT_THAT(result_strs, testing::UnorderedElementsAreArray(config.expected_addr));
        }
        resolv_flush_cache_for_net(TEST_NETID);
    }
}

TEST_F(ResolvGetAddrInfoTest, MdnsIllegalHostname) {
    constexpr char v6addr[] = "::127.0.0.3";
    constexpr char v4addr[] = "127.0.0.3";

    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService);
    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService);
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());
    ASSERT_EQ(0, SetResolvers());
    mdnsv4.clearQueries();
    mdnsv6.clearQueries();

    constexpr char illegalHostname[] = "hello^.local.";
    // Expect to get no address because hostname format is illegal.
    //
    // Ex:
    // ANSWER SECTION:
    // hello^.local.      IN  A       127.0.0.3
    // hello^.local.      IN  AAAA    ::127.0.0.3
    //
    // In this example, querying "hello^.local" should get no address because
    // "hello^.local" has an illegal char '^' in the middle of label.
    mdnsv4.addMapping(illegalHostname, ns_type::ns_t_a, v4addr);
    mdnsv6.addMapping(illegalHostname, ns_type::ns_t_aaaa, v6addr);

    for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
        SCOPED_TRACE(fmt::format("family: {}, illegalHostname: {}", family, illegalHostname));
        addrinfo* result = nullptr;
        const addrinfo hints = {.ai_family = family};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("hello^.local", nullptr, &hints, &mNetcontext, &result, &event);
        ScopedAddrinfo result_cleanup(result);
        EXPECT_EQ(nullptr, result);
        EXPECT_EQ(EAI_FAIL, rv);
    }
}

TEST_F(ResolvGetAddrInfoTest, MdnsResponderTimeout) {
    constexpr char host_name[] = "hello.local.";
    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService,
                              static_cast<ns_rcode>(-1));
    mdnsv4.setResponseProbability(0.0);  // always ignore requests and don't respond
    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService, static_cast<ns_rcode>(-1));
    mdnsv6.setResponseProbability(0.0);
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());
    ASSERT_EQ(0, SetResolvers());
    test::DNSResponder dns("127.0.0.3", test::kDefaultListenService, static_cast<ns_rcode>(-1));
    dns.setResponseProbability(0.0);
    ASSERT_TRUE(dns.startServer());

    for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
        SCOPED_TRACE(fmt::format("family: {}, host_name: {}", family, host_name));
        addrinfo* result = nullptr;
        const addrinfo hints = {.ai_family = family};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("hello.local", nullptr, &hints, &mNetcontext, &result, &event);
        EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
    }
}

TEST_F(ResolvGetAddrInfoTest, CnamesNoIpAddress) {
    constexpr char ACNAME[] = "acname";  // expect a cname in answer
    constexpr char CNAMES[] = "cnames";  // expect cname chain in answer

    test::DNSResponder dns;
    dns.addMapping("cnames.example.com.", ns_type::ns_t_cname, "acname.example.com.");
    dns.addMapping("acname.example.com.", ns_type::ns_t_cname, "hello.example.com.");
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        const char* name;
        int family;
    } testConfigs[]{
            // clang-format off
            {ACNAME, AF_INET},
            {ACNAME, AF_INET6},
            {ACNAME, AF_UNSPEC},
            {CNAMES, AF_INET},
            {CNAMES, AF_INET6},
            {CNAMES, AF_UNSPEC},
            // clang-format on
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("config.family: {}, config.name: {}", config.family, config.name));

        addrinfo* res = nullptr;
        const addrinfo hints = {.ai_family = config.family};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo(config.name, nullptr, &hints, &mNetcontext, &res, &event);
        ScopedAddrinfo result(res);
        EXPECT_EQ(nullptr, result);
        EXPECT_EQ(EAI_FAIL, rv);
    }
}

TEST_F(ResolvGetAddrInfoTest, CnamesBrokenChainByIllegalCname) {
    test::DNSResponder dns;
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        const char* name;
        const char* cname;
        std::string asHostName() const { return fmt::format("{}.example.com.", name); }

        // Illegal cname is verified by res_hnok() in system/netd/resolv/res_comp.cpp.
    } testConfigs[]{
            // clang-format off
            {NAME(kBadCharAfterPeriodHost),        kBadCharAfterPeriodHost},
            {NAME(kBadCharBeforePeriodHost),       kBadCharBeforePeriodHost},
            {NAME(kBadCharAtTheEndHost),           kBadCharAtTheEndHost},
            {NAME(kBadCharInTheMiddleOfLabelHost), kBadCharInTheMiddleOfLabelHost},
            // clang-format on
    };

    for (const auto& config : testConfigs) {
        const std::string testHostName = config.asHostName();

        // Expect to get no address because the cname chain is broken by an illegal cname format.
        //
        // Ex:
        // ANSWER SECTION:
        // hello.example.com.   IN  CNAME   a.ex^ample.com.
        // a.ex^ample.com.      IN  A       1.2.3.3
        // a.ex^ample.com.      IN  AAAA    2001:db8::42
        //
        // In this example, querying hello.example.com should get no address because
        // "a.ex^ample.com" has an illegal char '^' in the middle of label.
        dns.addMapping(testHostName.c_str(), ns_type::ns_t_cname, config.cname);
        dns.addMapping(config.cname, ns_type::ns_t_a, "1.2.3.3");
        dns.addMapping(config.cname, ns_type::ns_t_aaaa, "2001:db8::42");

        for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
            SCOPED_TRACE(fmt::format("family: {}, testHostName: {}", family, testHostName));

            addrinfo* res = nullptr;
            const addrinfo hints = {.ai_family = family};
            NetworkDnsEventReported event;
            int rv = resolv_getaddrinfo(config.name, nullptr, &hints, &mNetcontext, &res, &event);
            ScopedAddrinfo result(res);
            EXPECT_EQ(nullptr, result);
            EXPECT_EQ(EAI_FAIL, rv);
        }
    }
}

TEST_F(ResolvGetAddrInfoTest, CnamesInfiniteLoop) {
    test::DNSResponder dns;
    dns.addMapping("hello.example.com.", ns_type::ns_t_cname, "a.example.com.");
    dns.addMapping("a.example.com.", ns_type::ns_t_cname, "hello.example.com.");
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
        SCOPED_TRACE(fmt::format("family: {}", family));

        addrinfo* res = nullptr;
        const addrinfo hints = {.ai_family = family};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &res, &event);
        ScopedAddrinfo result(res);
        EXPECT_EQ(nullptr, result);
        EXPECT_EQ(EAI_FAIL, rv);
    }
}

TEST_F(ResolvGetAddrInfoTest, MultiAnswerSections) {
    test::DNSResponder dns(test::DNSResponder::MappingType::DNS_HEADER);
    // Answer section for query type {A, AAAA}
    // Type A:
    //   hello.example.com.   IN    A       1.2.3.1
    //   hello.example.com.   IN    A       1.2.3.2
    // Type AAAA:
    //   hello.example.com.   IN    AAAA    2001:db8::41
    //   hello.example.com.   IN    AAAA    2001:db8::42
    StartDns(dns, {MakeDnsMessage(kHelloExampleCom, ns_type::ns_t_a, {"1.2.3.1", "1.2.3.2"}),
                   MakeDnsMessage(kHelloExampleCom, ns_type::ns_t_aaaa,
                                  {"2001:db8::41", "2001:db8::42"})});
    ASSERT_EQ(0, SetResolvers());

    for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
        SCOPED_TRACE(fmt::format("family: {}", family));

        addrinfo* res = nullptr;
        // If the socket type is not specified, every address will appear twice, once for
        // SOCK_STREAM and one for SOCK_DGRAM. Just pick one because the addresses for
        // the second query of different socket type are responded by the cache.
        const addrinfo hints = {.ai_family = family, .ai_socktype = SOCK_STREAM};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &res, &event);
        ScopedAddrinfo result(res);
        ASSERT_NE(nullptr, result);
        ASSERT_EQ(0, rv);

        const std::vector<std::string> result_strs = ToStrings(result);
        if (family == AF_INET) {
            EXPECT_EQ(1U, GetNumQueries(dns, kHelloExampleCom));
            EXPECT_THAT(result_strs, testing::UnorderedElementsAreArray({"1.2.3.1", "1.2.3.2"}));
        } else if (family == AF_INET6) {
            EXPECT_EQ(1U, GetNumQueries(dns, kHelloExampleCom));
            EXPECT_THAT(result_strs,
                        testing::UnorderedElementsAreArray({"2001:db8::41", "2001:db8::42"}));
        } else if (family == AF_UNSPEC) {
            EXPECT_EQ(0U, GetNumQueries(dns, kHelloExampleCom));  // no query because of the cache
            EXPECT_THAT(result_strs,
                        testing::UnorderedElementsAreArray(
                                {"1.2.3.1", "1.2.3.2", "2001:db8::41", "2001:db8::42"}));
        }
        dns.clearQueries();
    }
}

TEST_F(ResolvGetAddrInfoTest, TruncatedResponse) {
    // Following fields will not be verified during the test in proto NetworkDnsEventReported.
    // So don't need to config those values: event_type, return_code, latency_micros,
    // hints_ai_flags, res_nsend_flags, network_type, private_dns_modes.
    constexpr char event_ipv4[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 254,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 7,
                },
                {
                 rcode: 0,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 2,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                },
                {
                 rcode: 0,
                 type: 1,
                 cache_hit: 2,
                 ip_version: 0,
                 protocol: 0,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";

    constexpr char event_ipv6[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 254,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 7,
                },
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 2,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                },
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 2,
                 ip_version: 0,
                 protocol: 0,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";

    test::DNSResponder dns;
    dns.addMapping(kHelloExampleCom, ns_type::ns_t_cname, kCnameA);
    dns.addMapping(kCnameA, ns_type::ns_t_cname, kCnameB);
    dns.addMapping(kCnameB, ns_type::ns_t_cname, kCnameC);
    dns.addMapping(kCnameC, ns_type::ns_t_cname, kCnameD);
    dns.addMapping(kCnameD, ns_type::ns_t_a, kHelloExampleComAddrV4);
    dns.addMapping(kCnameD, ns_type::ns_t_aaaa, kHelloExampleComAddrV6);
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        int ai_family;
        const std::string expected_addr;
        const std::string expected_event;
    } testConfigs[]{
            {AF_INET, kHelloExampleComAddrV4, event_ipv4},
            {AF_INET6, kHelloExampleComAddrV6, event_ipv6},
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("family: {}", config.ai_family));
        dns.clearQueries();

        addrinfo* result = nullptr;
        const addrinfo hints = {.ai_family = config.ai_family};
        NetworkDnsEventReported event;
        int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &result, &event);
        EXPECT_THAT(event,
                    NetworkDnsEventEq(fromNetworkDnsEventReportedStr(config.expected_event)));
        ScopedAddrinfo result_cleanup(result);
        EXPECT_EQ(rv, 0);
        EXPECT_TRUE(result != nullptr);
        // Expect UDP response is truncated. The resolver retries over TCP. See RFC 1035
        // section 4.2.1.
        EXPECT_EQ(GetNumQueriesForProtocol(dns, IPPROTO_UDP, kHelloExampleCom), 1U);
        EXPECT_EQ(GetNumQueriesForProtocol(dns, IPPROTO_TCP, kHelloExampleCom), 1U);
        EXPECT_EQ(ToString(result), config.expected_addr);
    }
}

// Audit if Resolver read out of bounds, which needs HWAddressSanitizer build to trigger SIGABRT.
TEST_F(ResolvGetAddrInfoTest, OverlengthResp) {
    std::vector<std::string> nameList;
    // Construct a long enough record that exceeds 8192 bytes (the maximum buffer size):
    // Header: (Transaction ID, Flags, ...)                                        = 12   bytes
    // Query: 19(Name)+2(Type)+2(Class)                                            = 23   bytes
    // The 1st answer RR: 19(Name)+2(Type)+2(Class)+4(TTL)+2(Len)+77(CNAME)        = 106  bytes
    // 2nd-50th answer RRs: 49*(77(Name)+2(Type)+2(Class)+4(TTL)+2(Len)+77(CNAME)) = 8036 bytes
    // The last answer RR: 77(Name)+2(Type)+2(Class)+4(TTL)+2(Len)+4(Address)      = 91   bytes
    // ----------------------------------------------------------------------------------------
    // Sum:                                                                          8268 bytes
    for (int i = 0; i < 10; i++) {
        std::string domain(kMaxmiumLabelSize / 2, 'a' + i);
        for (int j = 0; j < 5; j++) {
            nameList.push_back(domain + std::string(kMaxmiumLabelSize / 2 + 1, '0' + j) +
                               kExampleComDomain + ".");
        }
    }
    test::DNSResponder dns;
    dns.addMapping(kHelloExampleCom, ns_type::ns_t_cname, nameList[0]);
    for (size_t i = 0; i < nameList.size() - 1; i++) {
        dns.addMapping(nameList[i], ns_type::ns_t_cname, nameList[i + 1]);
    }
    dns.addMapping(nameList[nameList.size() - 1], ns_type::ns_t_a, kHelloExampleComAddrV4);

    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());
    addrinfo* result = nullptr;
    const addrinfo hints = {.ai_family = AF_INET};
    NetworkDnsEventReported event;
    int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &result, &event);
    ScopedAddrinfo result_cleanup(result);
    EXPECT_EQ(rv, EAI_FAIL);
    EXPECT_TRUE(result == nullptr);
    EXPECT_EQ(GetNumQueriesForProtocol(dns, IPPROTO_UDP, kHelloExampleCom), 2U);
    EXPECT_EQ(GetNumQueriesForProtocol(dns, IPPROTO_TCP, kHelloExampleCom), 2U);
}

TEST_F(GetHostByNameForNetContextTest, AlphabeticalHostname) {
    constexpr char host_name[] = "jiababuei.example.com.";
    constexpr char v4addr[] = "1.2.3.4";
    constexpr char v6addr[] = "::1.2.3.4";
    // Following fields will not be verified during the test in proto NetworkDnsEventReported.
    // So don't need to config those values: event_type, return_code, latency_micros,
    // hints_ai_flags, res_nsend_flags, network_type, private_dns_modes.
    constexpr char event_ipv4[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 latency_micros: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";

    constexpr char event_ipv6[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 1,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 latency_micros: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";
    test::DNSResponder dns;
    dns.addMapping(host_name, ns_type::ns_t_a, v4addr);
    dns.addMapping(host_name, ns_type::ns_t_aaaa, v6addr);
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        int ai_family;
        const std::string expected_addr;
        const std::string expected_event;
    } testConfigs[]{
            {AF_INET, v4addr, event_ipv4},
            {AF_INET6, v6addr, event_ipv6},
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("family: {}", config.ai_family));
        dns.clearQueries();

        hostent* hp = nullptr;
        hostent hbuf;
        char tmpbuf[MAXPACKET];
        NetworkDnsEventReported event;
        int rv = resolv_gethostbyname("jiababuei", config.ai_family, &hbuf, tmpbuf, sizeof(tmpbuf),
                                      &mNetcontext, &hp, &event);
        EXPECT_THAT(event,
                    NetworkDnsEventEq(fromNetworkDnsEventReportedStr(config.expected_event)));
        EXPECT_EQ(0, rv);
        EXPECT_TRUE(hp != nullptr);
        EXPECT_EQ(1U, GetNumQueries(dns, host_name));
        EXPECT_EQ(config.expected_addr, ToString(hp));
    }
}

TEST_F(GetHostByNameForNetContextTest, IllegalHostname) {
    test::DNSResponder dns;
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    // Illegal hostname is verified by res_hnok() in system/netd/resolv/res_comp.cpp.
    static constexpr char const* illegalHostnames[] = {
            kBadCharAfterPeriodHost,
            kBadCharBeforePeriodHost,
            kBadCharAtTheEndHost,
            kBadCharInTheMiddleOfLabelHost,
    };

    for (const auto& hostname : illegalHostnames) {
        // Expect to get no address because hostname format is illegal.
        //
        // Ex:
        // ANSWER SECTION:
        // a.ex^ample.com.      IN  A       1.2.3.3
        // a.ex^ample.com.      IN  AAAA    2001:db8::42
        //
        // In this example, querying "a.ex^ample.com" should get no address because
        // "a.ex^ample.com" has an illegal char '^' in the middle of label.
        dns.addMapping(hostname, ns_type::ns_t_a, "1.2.3.3");
        dns.addMapping(hostname, ns_type::ns_t_aaaa, "2001:db8::42");

        for (const auto& family : {AF_INET, AF_INET6}) {
            SCOPED_TRACE(fmt::format("family: {}, config.name: {}", family, hostname));

            struct hostent* hp = nullptr;
            hostent hbuf;
            char tmpbuf[MAXPACKET];
            NetworkDnsEventReported event;
            int rv = resolv_gethostbyname(hostname, family, &hbuf, tmpbuf, sizeof(tmpbuf),
                                          &mNetcontext, &hp, &event);
            EXPECT_EQ(nullptr, hp);
            EXPECT_EQ(EAI_FAIL, rv);
        }
    }
}

TEST_F(GetHostByNameForNetContextTest, NoData) {
    constexpr char v4_host_name[] = "v4only.example.com.";

    test::DNSResponder dns;
    dns.addMapping(v4_host_name, ns_type::ns_t_a, "1.2.3.3");
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());
    dns.clearQueries();

    // Want AAAA answer but DNS server has A answer only.
    hostent* hp = nullptr;
    hostent hbuf;
    char tmpbuf[MAXPACKET];
    NetworkDnsEventReported event;
    int rv = resolv_gethostbyname("v4only", AF_INET6, &hbuf, tmpbuf, sizeof tmpbuf, &mNetcontext,
                                  &hp, &event);
    EXPECT_LE(1U, GetNumQueries(dns, v4_host_name));
    EXPECT_EQ(nullptr, hp);
    EXPECT_EQ(EAI_NODATA, rv);
}

TEST_F(GetHostByNameForNetContextTest, ServerResponseError) {
    constexpr char host_name[] = "hello.example.com.";

    static const struct TestConfig {
        ns_rcode rcode;
        int expected_eai_error;  // expected result

        // Only test failure RCODE [1..5] in RFC 1035 section 4.1.1 and skip successful RCODE 0
        // which means no error. Note that the return error codes aren't mapped by rcode in the
        // test case SERVFAIL, NOTIMP and REFUSED. See the comment of res_nsend()
        // in system\netd\resolv\res_query.cpp for more detail.
    } testConfigs[]{
            // clang-format off
            {ns_rcode::ns_r_formerr, EAI_FAIL},
            {ns_rcode::ns_r_servfail, EAI_AGAIN},  // Not mapped by rcode.
            {ns_rcode::ns_r_nxdomain, EAI_NODATA},
            {ns_rcode::ns_r_notimpl, EAI_AGAIN},  // Not mapped by rcode.
            {ns_rcode::ns_r_refused, EAI_AGAIN},  // Not mapped by rcode.
            // clang-format on
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("rcode: {}", static_cast<int>(config.rcode)));

        test::DNSResponder dns(config.rcode);
        dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
        dns.setResponseProbability(0.0);  // always ignore requests and response preset rcode
        ASSERT_TRUE(dns.startServer());
        ASSERT_EQ(0, SetResolvers());

        hostent* hp = nullptr;
        hostent hbuf;
        char tmpbuf[MAXPACKET];
        NetworkDnsEventReported event;
        int rv = resolv_gethostbyname(host_name, AF_INET, &hbuf, tmpbuf, sizeof tmpbuf,
                                      &mNetcontext, &hp, &event);
        EXPECT_EQ(nullptr, hp);
        EXPECT_EQ(config.expected_eai_error, rv);
    }
}

// TODO: Add private DNS server timeout test.
TEST_F(GetHostByNameForNetContextTest, ServerTimeout) {
    constexpr char host_name[] = "hello.example.com.";
    test::DNSResponder dns(static_cast<ns_rcode>(-1) /*no response*/);
    dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
    dns.setResponseProbability(0.0);  // always ignore requests and don't respond
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    hostent* hp = nullptr;
    hostent hbuf;
    char tmpbuf[MAXPACKET];
    NetworkDnsEventReported event;
    int rv = resolv_gethostbyname(host_name, AF_INET, &hbuf, tmpbuf, sizeof tmpbuf, &mNetcontext,
                                  &hp, &event);
    EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
}

TEST_F(GetHostByNameForNetContextTest, CnamesNoIpAddress) {
    constexpr char ACNAME[] = "acname";  // expect a cname in answer
    constexpr char CNAMES[] = "cnames";  // expect cname chain in answer

    test::DNSResponder dns;
    dns.addMapping("cnames.example.com.", ns_type::ns_t_cname, "acname.example.com.");
    dns.addMapping("acname.example.com.", ns_type::ns_t_cname, "hello.example.com.");
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        const char* name;
        int family;
    } testConfigs[]{
            {ACNAME, AF_INET},
            {ACNAME, AF_INET6},
            {CNAMES, AF_INET},
            {CNAMES, AF_INET6},
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("config.family: {}, config.name: {}", config.family, config.name));

        struct hostent* hp = nullptr;
        hostent hbuf;
        char tmpbuf[MAXPACKET];
        NetworkDnsEventReported event;
        int rv = resolv_gethostbyname(config.name, config.family, &hbuf, tmpbuf, sizeof tmpbuf,
                                      &mNetcontext, &hp, &event);
        EXPECT_EQ(nullptr, hp);
        EXPECT_EQ(EAI_FAIL, rv);
    }
}

TEST_F(GetHostByNameForNetContextTest, CnamesBrokenChainByIllegalCname) {
    test::DNSResponder dns;
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        const char* name;
        const char* cname;
        std::string asHostName() const { return fmt::format("{}.example.com.", name); }

        // Illegal cname is verified by res_hnok() in system/netd/resolv/res_comp.cpp
    } testConfigs[]{
            // clang-format off
            {NAME(kBadCharAfterPeriodHost),        kBadCharAfterPeriodHost},
            {NAME(kBadCharBeforePeriodHost),       kBadCharBeforePeriodHost},
            {NAME(kBadCharAtTheEndHost),           kBadCharAtTheEndHost},
            {NAME(kBadCharInTheMiddleOfLabelHost), kBadCharInTheMiddleOfLabelHost},
            // clang-format on
    };

    for (const auto& config : testConfigs) {
        const std::string testHostName = config.asHostName();

        // Expect to get no address because the cname chain is broken by an illegal cname format.
        //
        // Ex:
        // ANSWER SECTION:
        // hello.example.com.   IN  CNAME   a.ex^ample.com.
        // a.ex^ample.com.      IN  A       1.2.3.3
        // a.ex^ample.com.      IN  AAAA    2001:db8::42
        //
        // In this example, querying hello.example.com should get no address because
        // "a.ex^ample.com" has an illegal char '^' in the middle of label.
        dns.addMapping(testHostName.c_str(), ns_type::ns_t_cname, config.cname);
        dns.addMapping(config.cname, ns_type::ns_t_a, "1.2.3.3");
        dns.addMapping(config.cname, ns_type::ns_t_aaaa, "2001:db8::42");

        for (const auto& family : {AF_INET, AF_INET6}) {
            SCOPED_TRACE(fmt::format("family: {}, testHostName: {}", family, testHostName));

            struct hostent* hp = nullptr;
            hostent hbuf;
            char tmpbuf[MAXPACKET];
            NetworkDnsEventReported event;
            int rv = resolv_gethostbyname(config.name, family, &hbuf, tmpbuf, sizeof tmpbuf,
                                          &mNetcontext, &hp, &event);
            EXPECT_EQ(nullptr, hp);
            EXPECT_EQ(EAI_FAIL, rv);
        }
    }
}

TEST_F(GetHostByNameForNetContextTest, CnamesInfiniteLoop) {
    test::DNSResponder dns;
    dns.addMapping("hello.example.com.", ns_type::ns_t_cname, "a.example.com.");
    dns.addMapping("a.example.com.", ns_type::ns_t_cname, "hello.example.com.");
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    for (const auto& family : {AF_INET, AF_INET6}) {
        SCOPED_TRACE(fmt::format("family: {}", family));

        struct hostent* hp = nullptr;
        hostent hbuf;
        char tmpbuf[MAXPACKET];
        NetworkDnsEventReported event;
        int rv = resolv_gethostbyname("hello", family, &hbuf, tmpbuf, sizeof tmpbuf, &mNetcontext,
                                      &hp, &event);
        EXPECT_EQ(nullptr, hp);
        EXPECT_EQ(EAI_FAIL, rv);
    }
}

TEST_F(GetHostByNameForNetContextTest, MdnsAlphabeticalHostname) {
    constexpr char v4addr[] = "127.0.0.3";
    constexpr char v6addr[] = "::127.0.0.3";
    constexpr char host_name[] = "hello.local.";

    // Following fields will not be verified during the test in proto NetworkDnsEventReported.
    // So don't need to config those values: event_type, return_code, latency_micros,
    // hints_ai_flags, res_nsend_flags, network_type, private_dns_modes.
    constexpr char event_ipv4[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 1,
                 cache_hit: 1,
                 ip_version: 1,
                 protocol: 5,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 latency_micros: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";

    constexpr char event_ipv6[] = R"Event(
             NetworkDnsEventReported {
             dns_query_events:
             {
               dns_query_event:[
                {
                 rcode: 0,
                 type: 28,
                 cache_hit: 1,
                 ip_version: 2,
                 protocol: 5,
                 retry_times: 0,
                 dns_server_index: 0,
                 connected: 0,
                 latency_micros: 0,
                 linux_errno: 0,
                }
               ]
             }
        })Event";

    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService);
    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService);

    mdnsv4.addMapping(host_name, ns_type::ns_t_a, v4addr);
    mdnsv6.addMapping(host_name, ns_type::ns_t_aaaa, v6addr);

    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());
    ASSERT_EQ(0, SetResolvers());

    static const struct TestConfig {
        int ai_family;
        const std::vector<std::string> expected_addr;
        const std::string expected_event;
    } testConfigs[]{
            {AF_INET, {v4addr}, event_ipv4},
            {AF_INET6, {v6addr}, event_ipv6},
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("family: {}", config.ai_family));
        hostent* result = nullptr;
        hostent hbuf;
        char tmpbuf[MAXPACKET];
        NetworkDnsEventReported event;
        int rv = resolv_gethostbyname("hello.local", config.ai_family, &hbuf, tmpbuf,
                                      sizeof(tmpbuf), &mNetcontext, &result, &event);
        EXPECT_THAT(event,
                    NetworkDnsEventEq(fromNetworkDnsEventReportedStr(config.expected_event)));
        EXPECT_EQ(0, rv);
        test::DNSResponder& mdns = config.ai_family == AF_INET ? mdnsv4 : mdnsv6;
        EXPECT_EQ(1U, GetNumQueries(mdns, host_name));
        mdns.clearQueries();
        std::vector<std::string> result_strs = ToStrings(result);
        EXPECT_THAT(result_strs, testing::UnorderedElementsAreArray(config.expected_addr));

        // Ensure the query result is still cached.
        rv = resolv_gethostbyname("hello.local", config.ai_family, &hbuf, tmpbuf, sizeof(tmpbuf),
                                  &mNetcontext, &result, &event);
        EXPECT_EQ(0, rv);
        EXPECT_EQ(0U, GetNumQueries(mdns, host_name));
        result_strs = ToStrings(result);
        EXPECT_THAT(result_strs, testing::UnorderedElementsAreArray(config.expected_addr));
    }
}

TEST_F(GetHostByNameForNetContextTest, MdnsIllegalHostname) {
    constexpr char v6addr[] = "::127.0.0.3";
    constexpr char v4addr[] = "127.0.0.3";
    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService);
    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService);
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());
    ASSERT_EQ(0, SetResolvers());
    mdnsv4.clearQueries();
    mdnsv6.clearQueries();

    constexpr char illegalHostname[] = "hello^.local.";
    // Expect to get no address because hostname format is illegal.
    //
    // Ex:
    // ANSWER SECTION:
    // hello^.local.      IN  A       127.0.0.3
    // hello^.local.      IN  AAAA    ::127.0.0.3
    //
    // In this example, querying "hello^.local" should get no address because
    // "hello^.local" has an illegal char '^' in the middle of label.
    mdnsv4.addMapping(illegalHostname, ns_type::ns_t_a, v4addr);
    mdnsv6.addMapping(illegalHostname, ns_type::ns_t_aaaa, v6addr);

    SCOPED_TRACE(fmt::format("family: {}, illegalHostname: {}", AF_INET6, illegalHostname));
    struct hostent* result = nullptr;
    hostent hbuf;
    char tmpbuf[MAXPACKET];
    NetworkDnsEventReported event;
    int rv = resolv_gethostbyname("hello^.local", AF_INET6, &hbuf, tmpbuf, sizeof(tmpbuf),
                                  &mNetcontext, &result, &event);
    EXPECT_EQ(nullptr, result);
    EXPECT_EQ(EAI_FAIL, rv);

    SCOPED_TRACE(fmt::format("family: {}, illegalHostname: {}", AF_INET, illegalHostname));
    rv = resolv_gethostbyname("hello^.local", AF_INET, &hbuf, tmpbuf, sizeof(tmpbuf), &mNetcontext,
                              &result, &event);
    EXPECT_EQ(nullptr, result);
    EXPECT_EQ(EAI_FAIL, rv);
}

TEST_F(GetHostByNameForNetContextTest, MdnsResponderTimeout) {
    constexpr char host_name[] = "hello.local.";
    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService,
                              static_cast<ns_rcode>(-1));
    mdnsv4.setResponseProbability(0.0);  // always ignore requests and don't respond
    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService, static_cast<ns_rcode>(-1));
    mdnsv6.setResponseProbability(0.0);
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());
    ASSERT_EQ(0, SetResolvers());
    test::DNSResponder dns("127.0.0.3", test::kDefaultListenService, static_cast<ns_rcode>(-1));
    dns.setResponseProbability(0.0);
    ASSERT_TRUE(dns.startServer());

    for (const auto& family : {AF_INET, AF_INET6}) {
        SCOPED_TRACE(fmt::format("family: {}, host_name: {}", family, host_name));
        hostent* result = nullptr;
        hostent hbuf;
        char tmpbuf[MAXPACKET];
        NetworkDnsEventReported event;
        int rv = resolv_gethostbyname("hello.local", family, &hbuf, tmpbuf, sizeof tmpbuf,
                                      &mNetcontext, &result, &event);
        EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
    }
}

TEST_F(ResolvCommonFunctionTest, GetCustTableByName) {
    const char custAddrV4[] = "1.2.3.4";
    const char custAddrV6[] = "::1.2.3.4";
    const char hostnameV4V6[] = "v4v6.example.com.";
    const aidl::android::net::ResolverOptionsParcel& resolverOptions = {
            {
                    {custAddrV4, hostnameV4V6},
                    {custAddrV6, hostnameV4V6},
            },
            aidl::android::net::IDnsResolver::TC_MODE_DEFAULT};
    const std::vector<int32_t>& transportTypes = {IDnsResolver::TRANSPORT_WIFI};
    EXPECT_EQ(0, resolv_set_nameservers(TEST_NETID, servers, domains, params, resolverOptions,
                                        transportTypes));
    EXPECT_THAT(getCustomizedTableByName(TEST_NETID, hostnameV4V6),
                testing::UnorderedElementsAreArray({custAddrV4, custAddrV6}));

    // Query address by mismatch hostname.
    ASSERT_TRUE(getCustomizedTableByName(TEST_NETID, "not.in.cust.table").empty());

    // Query address by different netid.
    ASSERT_TRUE(getCustomizedTableByName(TEST_NETID + 1, hostnameV4V6).empty());
    resolv_create_cache_for_net(TEST_NETID + 1);
    EXPECT_EQ(0, resolv_set_nameservers(TEST_NETID + 1, servers, domains, params, resolverOptions,
                                        transportTypes));
    EXPECT_THAT(getCustomizedTableByName(TEST_NETID + 1, hostnameV4V6),
                testing::UnorderedElementsAreArray({custAddrV4, custAddrV6}));
}

TEST_F(ResolvCommonFunctionTest, GetNetworkTypesForNet) {
    const aidl::android::net::ResolverOptionsParcel& resolverOptions = {
            {} /* hosts */, aidl::android::net::IDnsResolver::TC_MODE_DEFAULT};
    const std::vector<int32_t>& transportTypes = {IDnsResolver::TRANSPORT_WIFI,
                                                  IDnsResolver::TRANSPORT_VPN};
    EXPECT_EQ(0, resolv_set_nameservers(TEST_NETID, servers, domains, params, resolverOptions,
                                        transportTypes));
    EXPECT_EQ(android::net::NT_WIFI_VPN, resolv_get_network_types_for_net(TEST_NETID));
}

TEST_F(ResolvCommonFunctionTest, ConvertTransportsToNetworkType) {
    static const struct TestConfig {
        int32_t networkType;
        std::vector<int32_t> transportTypes;
    } testConfigs[] = {
            {android::net::NT_CELLULAR, {IDnsResolver::TRANSPORT_CELLULAR}},
            {android::net::NT_WIFI, {IDnsResolver::TRANSPORT_WIFI}},
            {android::net::NT_BLUETOOTH, {IDnsResolver::TRANSPORT_BLUETOOTH}},
            {android::net::NT_ETHERNET, {IDnsResolver::TRANSPORT_ETHERNET}},
            {android::net::NT_VPN, {IDnsResolver::TRANSPORT_VPN}},
            {android::net::NT_WIFI_AWARE, {IDnsResolver::TRANSPORT_WIFI_AWARE}},
            {android::net::NT_LOWPAN, {IDnsResolver::TRANSPORT_LOWPAN}},
            {android::net::NT_SATELLITE, {IDnsResolver::TRANSPORT_SATELLITE}},
            {android::net::NT_CELLULAR_VPN,
             {IDnsResolver::TRANSPORT_CELLULAR, IDnsResolver::TRANSPORT_VPN}},
            {android::net::NT_CELLULAR_VPN,
             {IDnsResolver::TRANSPORT_VPN, IDnsResolver::TRANSPORT_CELLULAR}},
            {android::net::NT_WIFI_VPN,
             {IDnsResolver::TRANSPORT_WIFI, IDnsResolver::TRANSPORT_VPN}},
            {android::net::NT_WIFI_VPN,
             {IDnsResolver::TRANSPORT_VPN, IDnsResolver::TRANSPORT_WIFI}},
            {android::net::NT_BLUETOOTH_VPN,
             {IDnsResolver::TRANSPORT_BLUETOOTH, IDnsResolver::TRANSPORT_VPN}},
            {android::net::NT_BLUETOOTH_VPN,
             {IDnsResolver::TRANSPORT_VPN, IDnsResolver::TRANSPORT_BLUETOOTH}},
            {android::net::NT_ETHERNET_VPN,
             {IDnsResolver::TRANSPORT_ETHERNET, IDnsResolver::TRANSPORT_VPN}},
            {android::net::NT_ETHERNET_VPN,
             {IDnsResolver::TRANSPORT_VPN, IDnsResolver::TRANSPORT_ETHERNET}},
            {android::net::NT_UNKNOWN, {IDnsResolver::TRANSPORT_VPN, IDnsResolver::TRANSPORT_VPN}},
            {android::net::NT_UNKNOWN,
             {IDnsResolver::TRANSPORT_WIFI, IDnsResolver::TRANSPORT_LOWPAN}},
            {android::net::NT_UNKNOWN, {}},
            {android::net::NT_UNKNOWN,
             {IDnsResolver::TRANSPORT_CELLULAR, IDnsResolver::TRANSPORT_BLUETOOTH,
              IDnsResolver::TRANSPORT_VPN}},
            {android::net::NT_WIFI_CELLULAR_VPN,
             {IDnsResolver::TRANSPORT_CELLULAR, IDnsResolver::TRANSPORT_WIFI,
              IDnsResolver::TRANSPORT_VPN}},
            {android::net::NT_WIFI_CELLULAR_VPN,
             {IDnsResolver::TRANSPORT_VPN, IDnsResolver::TRANSPORT_WIFI,
              IDnsResolver::TRANSPORT_CELLULAR}},
    };
    for (const auto& config : testConfigs) {
        EXPECT_EQ(config.networkType, convert_network_type(config.transportTypes));
    }
}

// Note that local host file function, files_getaddrinfo(), of resolv_getaddrinfo()
// is not tested because it only returns a boolean (success or failure) without any error number.

// TODO: Add test for resolv_getaddrinfo().
//       - DNS response message parsing.
//           - Unexpected type of resource record (RR).
//           - Invalid length CNAME, or QNAME.
//           - Unexpected amount of questions.
//       - CNAME RDATA with the domain name which has null label(s).
// TODO: Add test for resolv_gethostbyname().
//       - Invalid parameters.
//       - DNS response message parsing.
//           - Unexpected type of resource record (RR).
//           - Invalid length CNAME, or QNAME.
//           - Unexpected amount of questions.
//       - CNAME RDATA with the domain name which has null label(s).
// TODO: Add test for resolv_gethostbyaddr().

}  // end of namespace net
}  // end of namespace android
