Newer
Older
mbed-os / connectivity / netsocket / tests / UNITTESTS / netsocket / IfaceDnsSocket / moduletest.cpp
@George Psimenos George Psimenos on 28 Jul 2020 26 KB Restructure events directory & move tests
/*
 * Copyright (c) 2019, Arm Limited and affiliates
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 */

#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "netsocket/nsapi_dns.h"
#include "netsocket/EthernetInterface.h"
#include "netsocket/SocketAddress.h"
#include "EMAC_mock.h"
#include "OnboardNetworkStack_mock.h"
#include "events/EventQueue.h"
#include <future>
#include <thread>
#include <chrono>
#include <list>

// Control the rtos EventFlags stub. See EventFlags_stub.cpp
extern std::list<uint32_t> eventFlagsStubNextRetval;

using ::testing::_;
using ::testing::Return;
using ::testing::ReturnArg;
using ::testing::SaveArg;
using ::testing::SaveArgPointee;
using ::testing::SetArrayArgument;
using ::testing::SetArgPointee;
using ::testing::SetArgReferee;
using ::testing::DoAll;

// This is the bottom layer, that goes outside of module's scope.
// This would be implemented by LWIPInterface, NanostackInterface, or board-specific stack.
OnboardNetworkStack &OnboardNetworkStack::get_default_instance()
{
    return OnboardNetworkStackMock::get_instance();
}

// Wrapper to avoid calling OnboardNetworkStack::get_default_instance() everywhere
static OnboardNetworkStackMock &stackMock()
{
    return OnboardNetworkStackMock::get_instance();
}

class Test_IfaceDnsSocket : public testing::Test {
public:
    static nsapi_error_t call_in(int delay, mbed::Callback<void()> func)
    {
        (void) delay; // Ignore the delay, just call the callbacks in the order they came.
        eventQueue.push_back(std::async(std::launch::deferred, func));
        return NSAPI_ERROR_OK;
    }
protected:
    EthernetInterface *iface;

    virtual void SetUp()
    {
        iface = new EthernetInterface(MockEMAC::get_instance(), OnboardNetworkStackMock::get_instance());;
        nsapi_dns_reset();
        eventQueue.clear();
        hostname_cb_result = NSAPI_ERROR_DEVICE_ERROR;
        hostname_cb_address = NULL;
    }

    virtual void TearDown()
    {
        delete iface;

    }

    void executeEventQueueCallbacks(void)
    {
        size_t events_num = eventQueue.size(); // Events will re-register themselves.
        for (int i = 0; i < events_num; i++) {
            eventQueue.front().get();
            eventQueue.pop_front();
        }
    }

    static std::list<std::future<void>> eventQueue;

    static nsapi_error_t hostname_cb_result;
    static SocketAddress *hostname_cb_address;
    static void hostbyname_cb(nsapi_error_t result, SocketAddress *address)
    {
        hostname_cb_result = result;
        if (result == 0) {
            result = 1;
        }
        hostname_cb_address = new SocketAddress[result];
        for (int i = 0; i < result; i++) {
            hostname_cb_address[i].set_addr(address[i].get_addr());
        }
    }

    static int query_id;

    static constexpr unsigned int packet_ip4_size = 44;
    static const unsigned char packet_ip4[packet_ip4_size];
    static constexpr unsigned int packet_ip6_size = 56;
    static const unsigned char packet_ip6[packet_ip6_size];
    static constexpr unsigned int packet_ip4_3addresses_size = 76;
    static const unsigned char packet_ip4_3addresses[packet_ip4_3addresses_size];
};

std::list<std::future<void>> Test_IfaceDnsSocket::eventQueue;
nsapi_error_t Test_IfaceDnsSocket::hostname_cb_result = NSAPI_ERROR_DEVICE_ERROR;
SocketAddress *Test_IfaceDnsSocket::hostname_cb_address = NULL;
int Test_IfaceDnsSocket::query_id = 1; // Default after nsapi_dns_reset()

// An actual google.com DNS resolution packet captured using a greentea test.
const unsigned char Test_IfaceDnsSocket::packet_ip4[Test_IfaceDnsSocket::packet_ip4_size] = {
    0x00, 0x01, // ID
    0x81, 0x80, // Flags
    0x00, 0x01, // qdcount
    0x00, 0x01, // ancount
    0x00, 0x00, // nscount
    0x00, 0x00, // arcount

    0x06,                               // question, qdcount = 1, first byte is len = 6
    0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, // body of the question
    0x03,                               // len = 3
    0x63, 0x6f, 0x6d,                   // body of the question qtype and qclass of the question
    0x00,                               // len = 0
    0x00, 0x01,                         // qtype
    0x00, 0x01,                         // qclass

    0xc0,                        // answer len = 192 (means this is a link)
    0x0c,                        // this gets scanned away, because of previous byte being link
    0x00, 0x01,                  // rtype: RR_A = 1, RR_AAAA = 28
    0x00, 0x01,                  // rclass
    0x00, 0x00, 0x00, 0x22,      // ttl
    0x00, 0x04,                  // rdlength (4 for IPv4 and 16 for IPv6)
    0xd8, 0x3a, 0xcf, 0xee       // Address bytes
};

// Same as previous packet, but with a fake IPv6 address instead of the IPv4 one
const unsigned char Test_IfaceDnsSocket::packet_ip6[Test_IfaceDnsSocket::packet_ip6_size] = {
    0x00, 0x01, // ID
    0x81, 0x80, // Flags
    0x00, 0x01, // qdcount
    0x00, 0x01, // ancount
    0x00, 0x00, // nscount
    0x00, 0x00, // arcount

    0x06,                               // question, qdcount = 1, first byte is len = 6
    0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, // body of the question
    0x03,                               // len = 3
    0x63, 0x6f, 0x6d,                   // body of the question qtype and qclass of the question
    0x00,                               // len = 0
    0x00, 0x01,                         // qtype
    0x00, 0x01,                         // qclass

    0xc0,                        // answer len = 192 (means this is a link)
    0x0c,                        // this gets scanned away, because of previous byte being link
    0x00, 0x1c,                  // rtype: RR_A = 1, RR_AAAA = 28
    0x00, 0x01,                  // rclass
    0x00, 0x00, 0x00, 0x22,      // ttl
    0x00, 0x10,                  // rdlength (4 for IPv4 and 16 for IPv6)
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
    0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 // Address bytes
};

// Same as packet_ipv4, but with two fake ipv4 appended at the end.
const unsigned char Test_IfaceDnsSocket::packet_ip4_3addresses[Test_IfaceDnsSocket::packet_ip4_3addresses_size] = {
    0x00, 0x01, // ID
    0x81, 0x80, // Flags
    0x00, 0x01, // qdcount
    0x00, 0x03, // ancount
    0x00, 0x00, // nscount
    0x00, 0x00, // arcount

    0x06,                               // question, qdcount = 1, first byte is len = 6
    0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, // body of the question
    0x03,                               // len = 3
    0x63, 0x6f, 0x6d,                   // body of the question qtype and qclass of the question
    0x00,                               // len = 0
    0x00, 0x01,                         // qtype
    0x00, 0x01,                         // qclass

    0xc0,                        // answer len = 192 (means this is a link)
    0x0c,                        // this gets scanned away, because of previous byte being link
    0x00, 0x01,                  // rtype: RR_A = 1, RR_AAAA = 28
    0x00, 0x01,                  // rclass
    0x00, 0x00, 0x00, 0x22,      // ttl
    0x00, 0x04,                  // rdlength (4 for IPv4 and 16 for IPv6)
    0xd8, 0x3a, 0xcf, 0xee,      // Address bytes

    0xc0,                        // answer len = 192 (means this is a link)
    0x0c,                        // this gets scanned away, because of previous byte being link
    0x00, 0x01,                  // rtype: RR_A = 1, RR_AAAA = 28
    0x00, 0x01,                  // rclass
    0x00, 0x00, 0x00, 0x22,      // ttl
    0x00, 0x04,                  // rdlength (4 for IPv4 and 16 for IPv6)
    0xde, 0xad, 0xbe, 0xef,      // Address bytes

    0xc0,                        // answer len = 192 (means this is a link)
    0x0c,                        // this gets scanned away, because of previous byte being link
    0x00, 0x01,                  // rtype: RR_A = 1, RR_AAAA = 28
    0x00, 0x01,                  // rclass
    0x00, 0x00, 0x00, 0x22,      // ttl
    0x00, 0x04,                  // rdlength (4 for IPv4 and 16 for IPv6)
    0x01, 0x02, 0x03, 0x04       // Address bytes
};

// We cannot use SetArgArray, because this is void* type.
// Use a manual for loop, to avoid depending on local implementation of strncpy (had some issues with it).
ACTION_P2(SetArg2ToCharPtr, value, size)
{
    for (int i = 0; i < size; i++) {
        static_cast<char *>(arg2)[i] = reinterpret_cast<const char *>(value)[i];
    }
}

TEST_F(Test_IfaceDnsSocket, single_query)
{
    SocketAddress addr;
    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)&stackMock()), Return(NSAPI_ERROR_OK)));

    // Returning UNSUPPORTED will cause the nsapi_dns addresses to be used.
    EXPECT_CALL(stackMock(), get_dns_server(_, _, _))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<1>(SocketAddress("1.2.3.4", 53)), Return(NSAPI_ERROR_OK)));

    EXPECT_CALL(stackMock(), socket_sendto(_, _, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    // Return a predefined google.com resolution packet.
    EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
    .Times(1)
    .WillOnce(DoAll(SetArg2ToCharPtr(Test_IfaceDnsSocket::packet_ip4, Test_IfaceDnsSocket::packet_ip4_size), Return(Test_IfaceDnsSocket::packet_ip4_size)));

    EXPECT_CALL(stackMock(), socket_close(_)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_query(iface, "www.google.com", &addr));
    EXPECT_EQ(addr.get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr.get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));

    // Do not set any return values. Second call should use cache.
    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_query(iface, "www.google.com", &addr));
    EXPECT_EQ(addr.get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr.get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));
}

TEST_F(Test_IfaceDnsSocket, single_query_ip6)
{
    SocketAddress addr;

    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)&stackMock()), Return(NSAPI_ERROR_OK)));

    // Returning UNSUPPORTED will cause the nsapi_dns addresses to be used.
    EXPECT_CALL(stackMock(), get_dns_server(_, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_UNSUPPORTED));

    // We want to send one query to the DNS server.
    EXPECT_CALL(stackMock(), socket_sendto(_, _, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    // Return a predefined google.com resolution packet.
    EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
    .Times(1)
    .WillOnce(DoAll(SetArg2ToCharPtr(Test_IfaceDnsSocket::packet_ip6, Test_IfaceDnsSocket::packet_ip6_size), Return(Test_IfaceDnsSocket::packet_ip6_size)));

    EXPECT_CALL(stackMock(), socket_close(_)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_query(iface, "www.google.com", &addr, NSAPI_IPv6));
    EXPECT_EQ(addr.get_ip_version(), NSAPI_IPv6);
    EXPECT_FALSE(strncmp(addr.get_ip_address(), "102:304:506:708:90a:b0c:d0e:f10", sizeof("102:304:506:708:90a:b0c:d0e:f10")));

    // Do not set any return values. Second call should use cache.
    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_query(iface, "www.google.com", &addr, NSAPI_IPv6));
    EXPECT_EQ(addr.get_ip_version(), NSAPI_IPv6);
    EXPECT_FALSE(strncmp(addr.get_ip_address(), "102:304:506:708:90a:b0c:d0e:f10", sizeof("102:304:506:708:90a:b0c:d0e:f10")));
}

TEST_F(Test_IfaceDnsSocket, multiple_queries)
{
    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)&stackMock()), Return(NSAPI_ERROR_OK)));

    EXPECT_CALL(stackMock(), get_dns_server(_, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_UNSUPPORTED));

    EXPECT_CALL(stackMock(), socket_sendto(_, _, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    // Return a predefined google.com resolution packet.
    EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
    .Times(1)
    .WillOnce(DoAll(SetArg2ToCharPtr(Test_IfaceDnsSocket::packet_ip4_3addresses, Test_IfaceDnsSocket::packet_ip4_3addresses_size), Return(Test_IfaceDnsSocket::packet_ip4_3addresses_size)));

    EXPECT_CALL(stackMock(), socket_close(_)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    SocketAddress *addr;
    SocketAddress hints{{NSAPI_UNSPEC}, 443};
    EXPECT_EQ(3, iface->getaddrinfo("www.google.com", &hints, &addr));

    EXPECT_EQ(addr[0].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr[0].get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));
    EXPECT_EQ(addr[1].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr[1].get_ip_address(), "222.173.190.239", sizeof("222.173.190.239")));
    EXPECT_EQ(addr[2].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr[2].get_ip_address(), "1.2.3.4", sizeof("1.2.3.4")));
    delete[] addr;

    SocketAddress *addr_cache;
    // Cache will only raturn one address.
    EXPECT_EQ(3, iface->getaddrinfo("www.google.com", &hints, &addr_cache));

    EXPECT_EQ(addr_cache[0].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr_cache[0].get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));
    EXPECT_EQ(addr_cache[1].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr_cache[1].get_ip_address(), "222.173.190.239", sizeof("222.173.190.239")));
    EXPECT_EQ(addr_cache[2].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr_cache[2].get_ip_address(), "1.2.3.4", sizeof("1.2.3.4")));
    delete[] addr_cache;
}

TEST_F(Test_IfaceDnsSocket, single_query_errors)
{
    testing::InSequence s;
    SocketAddress addr {};

    // Error - empty host name
    EXPECT_EQ(NSAPI_ERROR_PARAMETER, nsapi_dns_query(iface, "", &addr));
    EXPECT_EQ(NSAPI_ERROR_PARAMETER, nsapi_dns_query_multiple_async(&stackMock(), "", &Test_IfaceDnsSocket::hostbyname_cb, 5, &Test_IfaceDnsSocket::call_in, NULL));

    // Error - socket_open failing
    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)NULL), Return(NSAPI_ERROR_DEVICE_ERROR)));

    EXPECT_EQ(NSAPI_ERROR_DEVICE_ERROR, nsapi_dns_query(iface, "www.google.com", &addr));
}

TEST_F(Test_IfaceDnsSocket, simultaneous_query_async)
{
    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)&stackMock()), Return(NSAPI_ERROR_OK)));

    EXPECT_CALL(stackMock(), get_dns_server(_, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_UNSUPPORTED));

    EXPECT_CALL(stackMock(), socket_sendto(_, _, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    {
        testing::InSequence s;

        // Return a predefined google.com resolution packet.
        EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
        .Times(1)
        .WillOnce(DoAll(SetArg2ToCharPtr(Test_IfaceDnsSocket::packet_ip4, Test_IfaceDnsSocket::packet_ip4_size), Return(Test_IfaceDnsSocket::packet_ip4_size)));

        // There will be another try to recv from the socket, return 0, to show that no data is pending.
        EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
        .Times(1)
        .WillOnce(Return(0));
    }

    EXPECT_CALL(stackMock(), socket_close(_)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    EXPECT_EQ(Test_IfaceDnsSocket::query_id, nsapi_dns_query_async(&stackMock(), "www.google.com", &Test_IfaceDnsSocket::hostbyname_cb, Test_IfaceDnsSocket::call_in, NSAPI_IPv4));

    // Let another query to be stored in the queue.
    EXPECT_EQ(Test_IfaceDnsSocket::query_id + 1, nsapi_dns_query_async(&stackMock(), "www.amazon.com", &Test_IfaceDnsSocket::hostbyname_cb, Test_IfaceDnsSocket::call_in, NSAPI_IPv4));

    executeEventQueueCallbacks();

    // This will register the socket_callback.
    reinterpret_cast<mbed::Callback<void(void *)> *>((&stackMock())->socket_cb)->call((void *)packet_ip4);

    // Run through registered callbacks again, to execute socket_callback_handle
    executeEventQueueCallbacks();
    // Run again to execute response_handler
    executeEventQueueCallbacks();

    EXPECT_EQ(1, Test_IfaceDnsSocket::hostname_cb_result);
    EXPECT_EQ(Test_IfaceDnsSocket::hostname_cb_address[0].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(Test_IfaceDnsSocket::hostname_cb_address[0].get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));

    delete[] Test_IfaceDnsSocket::hostname_cb_address;

    // Second call should use cache.
    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_query_async(&stackMock(), "www.google.com", &Test_IfaceDnsSocket::hostbyname_cb, Test_IfaceDnsSocket::call_in, NSAPI_IPv4));
    EXPECT_EQ(Test_IfaceDnsSocket::hostname_cb_address[0].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(Test_IfaceDnsSocket::hostname_cb_address[0].get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));

    delete[] Test_IfaceDnsSocket::hostname_cb_address;

    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_query_async_cancel((intptr_t)Test_IfaceDnsSocket::query_id + 1));
}

// Imitate the getaddrinfo to make the example more real-life.
// No way to replace this with NetworkInterface:getaddrinfo_async, because we can't pass a call_in cb.
int getaddrinfo_async(NetworkStack *stack, const char *hostname, NetworkStack::hostbyname_cb_t cb)
{
    return nsapi_dns_query_multiple_async(stack, hostname, cb, 5, &Test_IfaceDnsSocket::call_in, NULL);
}

TEST_F(Test_IfaceDnsSocket, single_query_async_multiple)
{
    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)&stackMock()), Return(NSAPI_ERROR_OK)));

    EXPECT_CALL(stackMock(), get_dns_server(_, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_UNSUPPORTED));

    EXPECT_CALL(stackMock(), socket_sendto(_, _, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    {
        testing::InSequence s;

        // Return a predefined google.com resolution packet.
        EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
        .Times(1)
        .WillOnce(DoAll(SetArg2ToCharPtr(Test_IfaceDnsSocket::packet_ip4_3addresses, Test_IfaceDnsSocket::packet_ip4_3addresses_size), Return(Test_IfaceDnsSocket::packet_ip4_3addresses_size)));

        // There will be another try to recv from the socket, return 0, to show that no data is pending.
        EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
        .Times(1)
        .WillOnce(Return(0));
    }

    EXPECT_CALL(stackMock(), socket_close(_)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    EXPECT_EQ(Test_IfaceDnsSocket::query_id, getaddrinfo_async(&stackMock(), "www.google.com", &Test_IfaceDnsSocket::hostbyname_cb));

    executeEventQueueCallbacks();

    // This will register the socket_callback.
    reinterpret_cast<mbed::Callback<void(void *)> *>((&stackMock())->socket_cb)->call((void *)packet_ip4);

    // Run through registered callbacks again, to execute socket_callback_handle
    executeEventQueueCallbacks();
    // Run again to execute response_handler
    executeEventQueueCallbacks();

    EXPECT_EQ(3, Test_IfaceDnsSocket::hostname_cb_result);
    EXPECT_EQ(Test_IfaceDnsSocket::hostname_cb_address[0].get_ip_version(), NSAPI_IPv4);
    EXPECT_EQ(Test_IfaceDnsSocket::hostname_cb_address[1].get_ip_version(), NSAPI_IPv4);
    EXPECT_EQ(Test_IfaceDnsSocket::hostname_cb_address[2].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(Test_IfaceDnsSocket::hostname_cb_address[0].get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));
    EXPECT_FALSE(strncmp(Test_IfaceDnsSocket::hostname_cb_address[1].get_ip_address(), "222.173.190.239", sizeof("222.173.190.239")));
    EXPECT_FALSE(strncmp(Test_IfaceDnsSocket::hostname_cb_address[2].get_ip_address(), "1.2.3.4", sizeof("1.2.3.4")));

    delete[] Test_IfaceDnsSocket::hostname_cb_address;
    Test_IfaceDnsSocket::hostname_cb_result = NSAPI_ERROR_DEVICE_ERROR;

    // Do not set any return values. Second call should use cache.
    EXPECT_EQ(3, getaddrinfo_async(&stackMock(), "www.google.com", &Test_IfaceDnsSocket::hostbyname_cb));
    EXPECT_EQ(3, Test_IfaceDnsSocket::hostname_cb_result);
    EXPECT_EQ(Test_IfaceDnsSocket::hostname_cb_address[0].get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(Test_IfaceDnsSocket::hostname_cb_address[0].get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));
    EXPECT_FALSE(strncmp(Test_IfaceDnsSocket::hostname_cb_address[1].get_ip_address(), "222.173.190.239", sizeof("222.173.190.239")));
    EXPECT_FALSE(strncmp(Test_IfaceDnsSocket::hostname_cb_address[2].get_ip_address(), "1.2.3.4", sizeof("1.2.3.4")));

    delete[] Test_IfaceDnsSocket::hostname_cb_address;
}

TEST_F(Test_IfaceDnsSocket, async_cancel)
{
    EXPECT_EQ(Test_IfaceDnsSocket::query_id, nsapi_dns_query_async(&stackMock(), "www.google.com", &Test_IfaceDnsSocket::hostbyname_cb, Test_IfaceDnsSocket::call_in, NSAPI_IPv4));

    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_query_async_cancel((intptr_t)1));

    executeEventQueueCallbacks();

    EXPECT_EQ(NSAPI_ERROR_DEVICE_ERROR, Test_IfaceDnsSocket::hostname_cb_result);
    EXPECT_EQ(NULL, Test_IfaceDnsSocket::hostname_cb_address);

    // Try to cancel twice - should return error
    EXPECT_EQ(NSAPI_ERROR_PARAMETER, nsapi_dns_query_async_cancel((intptr_t)1));
}

TEST_F(Test_IfaceDnsSocket, add_server)
{
    SocketAddress server_address("1.2.3.4", 53);
    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_add_server(server_address, NULL));
    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_add_server(server_address, NULL)); // Duplicate add - no error.

    SocketAddress addr;
    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)&stackMock()), Return(NSAPI_ERROR_OK)));

    EXPECT_CALL(stackMock(), get_dns_server(_, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_UNSUPPORTED));

    EXPECT_CALL(stackMock(), socket_sendto(_, server_address, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    EXPECT_CALL(stackMock(), socket_close(_)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    // Return a predefined google.com resolution packet.
    EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
    .Times(1)
    .WillOnce(DoAll(SetArg2ToCharPtr(Test_IfaceDnsSocket::packet_ip4, Test_IfaceDnsSocket::packet_ip4_size), Return(Test_IfaceDnsSocket::packet_ip4_size)));

    EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_query(iface, "www.google.com", &addr));
    EXPECT_EQ(addr.get_ip_version(), NSAPI_IPv4);
    EXPECT_FALSE(strncmp(addr.get_ip_address(), "216.58.207.238", sizeof("216.58.207.238")));
}

TEST_F(Test_IfaceDnsSocket, attempts)
{
    constexpr int DNS_SERVER_SIZE = 5; // This must be kept equal to DNS_SERVER_SIZE macro define in nsapi_dns.cpp

    // Replace existing servers, so we can see that the switch to next server occurs.
    SocketAddress known_server_address[DNS_SERVER_SIZE];
    for (uint8_t i = DNS_SERVER_SIZE; i > 0; i--) {
        uint8_t bytes[NSAPI_IPv4_SIZE] = {i, i, i, i};
        known_server_address[i - 1] = SocketAddress(bytes, NSAPI_IPv4, 53);
        EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_add_server(known_server_address[i - 1], NULL));
    }

    SocketAddress addr {};

    testing::InSequence s;

    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)&stackMock()), Return(NSAPI_ERROR_OK)));

    EXPECT_CALL(stackMock(), get_dns_server(_, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_UNSUPPORTED));

    int attempt = 0;
    while (attempt < DNS_SERVER_SIZE) {
        EXPECT_CALL(stackMock(), socket_sendto(_, known_server_address[attempt % DNS_SERVER_SIZE], _, _))
        .Times(1)
        .WillRepeatedly(Return(NSAPI_ERROR_DEVICE_ERROR));

        attempt++;
        if (attempt >= DNS_SERVER_SIZE) {
            break;
        }
    }

    EXPECT_CALL(stackMock(), socket_close(_)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    EXPECT_EQ(NSAPI_ERROR_DNS_FAILURE, nsapi_dns_query(iface, "www.google.com", &addr));
}

TEST_F(Test_IfaceDnsSocket, retries_attempts)
{
    constexpr int DNS_SERVER_SIZE = 5; // This must be kept equal to DNS_SERVER_SIZE macro define in nsapi_dns.cpp

    // Replace existing servers, so we can see that the switch to next server occurs.
    SocketAddress known_server_address[DNS_SERVER_SIZE];
    for (uint8_t i = DNS_SERVER_SIZE; i > 0; i--) {
        uint8_t bytes[NSAPI_IPv4_SIZE] = {i, i, i, i};
        known_server_address[i - 1] = SocketAddress(bytes, NSAPI_IPv4, 53);
        EXPECT_EQ(NSAPI_ERROR_OK, nsapi_dns_add_server(known_server_address[i - 1], NULL));
    }

    SocketAddress addr {};

    testing::InSequence s;

    // Make sure socket opens successfully
    EXPECT_CALL(stackMock(), socket_open(_, NSAPI_UDP))
    .Times(1)
    .WillOnce(DoAll(SetArgPointee<0>((void **)&stackMock()), Return(NSAPI_ERROR_OK)));

    EXPECT_CALL(stackMock(), get_dns_server(_, _, _)).Times(1).WillOnce(Return(NSAPI_ERROR_UNSUPPORTED));

    // Set up all the expectation: sendto and recvfrom should come one after another, untill dns runs out of attempts.
    int attempt = 0;
    while (attempt < MBED_CONF_NSAPI_DNS_TOTAL_ATTEMPTS) {
        // We will retry the first server MBED_CONF_NSAPI_DNS_RETRIES times.
        int retry = 0;
        while (retry < MBED_CONF_NSAPI_DNS_RETRIES + 1) {
            int server_idx = attempt / (MBED_CONF_NSAPI_DNS_RETRIES + 1);
            EXPECT_CALL(stackMock(), socket_sendto(_, known_server_address[server_idx % DNS_SERVER_SIZE], _, _))
            .Times(1)
            .WillRepeatedly(Return(NSAPI_ERROR_OK));

            eventFlagsStubNextRetval.push_back(osFlagsError); // Break the wait loop

            // Make sure the retries will be necessary.
            EXPECT_CALL(stackMock(), socket_recvfrom(_, _, _, _))
            .Times(1)
            .WillRepeatedly(Return(NSAPI_ERROR_WOULD_BLOCK));

            attempt++;
            retry++;
            if (attempt >= MBED_CONF_NSAPI_DNS_TOTAL_ATTEMPTS) {
                break;
            }
        }
    }

    EXPECT_CALL(stackMock(), socket_close(_)).Times(1).WillOnce(Return(NSAPI_ERROR_OK));

    EXPECT_EQ(NSAPI_ERROR_DNS_FAILURE, nsapi_dns_query(iface, "www.google.com", &addr));
}