1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
// Copyright (C) 2013-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <d2srv/dns_client.h>
#include <dns/opcode.h>
#include <asiodns/io_fetch.h>
#include <asiodns/logger.h>
#include <asiolink/interval_timer.h>
#include <d2srv/d2_update_message.h>
#include <d2srv/testutils/nc_test_utils.h>
#include <d2srv/testutils/stats_test_utils.h>
#include <dns/messagerenderer.h>

#include <boost/asio/ip/udp.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/asio/socket_base.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/scoped_ptr.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <functional><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <gtest/gtest.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::asiodns;
using namespace isc::d2;
using namespace isc::d2::test;
using namespace isc::data;
using namespace isc::dns;
using namespace isc::stats;
using namespace isc::util;
using namespace boost::asio;
using namespace boost::asio::ip;
namespace ph = std::placeholders;

namespace {

const char* TEST_ADDRESS = "127.0.0.1";
const uint16_t TEST_PORT = 5381;
const size_t MAX_SIZE = 1024;
const long TEST_TIMEOUT = 5 * 1000;
/// @brief Test Fixture class
//
// This test fixture class implements DNSClient::Callback so as it can be
// installed as a completion callback for tests it implements. This callback
// is called when a DDNS transaction (send and receive) completes. This allows
// for the callback function to directly access class members. In particular,
// the callback function can access IOService on which run() was called and
// call stop() on it.
//
// Many of the tests defined here schedule execution of certain tasks and block
// until tasks are completed or a timeout is hit. However, if timeout is not
// properly handled a task may be hanging for a long time. In order to prevent
// it, the asiolink::IntervalTimer is used to break a running test if test
// timeout is hit. This will result in test failure.
class DNSClientTest : public ::testing::Test, DNSClient::Callback,
                      public D2StatTest {
public:
    /// @brief The IOService which handles IO operations.
    IOServicePtr service_;

    /// @brief The UDP socket.
    std::unique_ptr<udp::socket> socket_;

    /// @brief The UDP socket endpoint.
    std::unique_ptr<udp::endpoint> endpoint_;

    /// @brief DNS client response.
    D2UpdateMessagePtr response_;

    /// @brief The status of the DNS client update callback.
    DNSClient::Status status_;

    /// @brief The receive buffer.
    uint8_t receive_buffer_[MAX_SIZE];

    /// @brief The DNS client performing DNS update.
    DNSClientPtr dns_client_;

    /// @brief The flag which specifies if the response should be corrupted.
    bool corrupt_response_;

    /// @brief The flag which specifies if a response is expected.
    bool expect_response_;

    /// @brief The timeout timer.
    asiolink::IntervalTimer test_timer_;

    /// @brief The number of received DNS updates.
    int received_;

    /// @brief The number of expected DNS updates.
    int expected_;

    /// @brief The flag which specifies if the server should continue with
    /// receiving DNS updates.
    bool go_on_;

    /// @brief Constructor
    ///
    /// This constructor overrides the default logging level of asiodns logger to
    /// prevent it from emitting debug messages from IOFetch class. Such an error
    /// message can be emitted if timeout occurs when DNSClient class is
    /// waiting for a response. Some of the tests are checking DNSClient behavior
    /// in case when response from the server is not received. Tests output would
    /// become messy if such errors were logged.
    DNSClientTest() : service_(new IOService()), socket_(), endpoint_(),
                      status_(DNSClient::SUCCESS), corrupt_response_(false),
                      expect_response_(true), test_timer_(service_),
                      received_(0), expected_(0), go_on_(false) {
        asiodns::logger.setSeverity(isc::log::INFO);
        response_.reset();
        dns_client_.reset(new DNSClient(response_, this));

        // Set the test timeout to break any running tasks if they hang.
        test_timer_.setup(std::bind(&DNSClientTest::testTimeoutHandler, this),
                          TEST_TIMEOUT);
    }

    /// @brief Destructor
    ///
    /// Sets the asiodns logging level back to DEBUG.
    virtual ~DNSClientTest() {
        test_timer_.cancel();
        dns_client_->stop();
        service_->stopAndPoll();
        asiodns::logger.setSeverity(isc::log::DEBUG);
    };

    /// @brief Exchange completion callback
    ///
    /// This callback is called when the exchange with the DNS server is
    /// complete or an error occurred. This includes the occurrence of a timeout.
    ///
    /// @param status A status code returned by DNSClient.
    virtual void operator()(DNSClient::Status status) {
        status_ = status;
        if (!expected_ || (expected_ == ++received_)) {
            service_->stop();
        }

        if (expect_response_) {
            if (!corrupt_response_) {
                // We should have received a response.
                EXPECT_EQ(DNSClient::SUCCESS, status_);

                ASSERT_TRUE(response_);
                EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
                ASSERT_EQ(1,
                          response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
                D2ZonePtr zone = response_->getZone();
                ASSERT_TRUE(zone);
                EXPECT_EQ("example.com.", zone->getName().toText());
                EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());

            } else {
                EXPECT_EQ(DNSClient::INVALID_RESPONSE, status_);

            }
        // If we don't expect a response, the status should indicate a timeout.
        } else {
            EXPECT_EQ(DNSClient::TIMEOUT, status_);

        }
    }

    /// @brief Handler invoked when test timeout is hit
    ///
    /// This callback stops all running (hanging) tasks on IO service.
    void testTimeoutHandler() {
        service_->stop();
        FAIL() << "Test timeout hit.";
    }

    /// @brief Handler invoked when test request is received
    ///
    /// This callback handler is installed when performing async read on a
    /// socket to emulate reception of the DNS Update request by a server.
    /// As a result, this handler will send an appropriate DNS Update response
    /// message back to the address from which the request has come.
    ///
    /// @param socket A pointer to a socket used to receive a query and send a
    /// response.
    /// @param remote A pointer to an object which specifies the host (address
    /// and port) from which a request has come.
    /// @param receive_length A length (in bytes) of the received data.
    /// @param corrupt_response A bool value which specifies if the server's
    /// response should be invalid (true) or valid (false).
    void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
                           size_t receive_length, const bool corrupt_response) {
        // The easiest way to create a response message is to copy the entire
        // request.
        OutputBuffer response_buf(receive_length);
        response_buf.writeData(receive_buffer_, receive_length);
        // If a response is to be valid, we have to modify it slightly. If not,
        // we leave it as is.
        if (!corrupt_response) {
            // For a valid response the QR bit must be set. This bit
            // differentiates both types of messages. Note that the 3rd byte of
            // the message header comprises this bit in the front followed by
            // the message code and reserved zeros. Therefore, this byte
            // has the following value:
            //             10101000,
            // where a leading bit is a QR flag. The hexadecimal value is 0xA8.
            // Write it at message offset 2.
            response_buf.writeUint8At(0xA8, 2);
        }
        // A response message is now ready to send. Send it!
        socket->send_to(boost::asio::buffer(response_buf.getData(),
                                            response_buf.getLength()),
                        *remote);

        if (go_on_) {
            socket_->async_receive_from(boost::asio::buffer(receive_buffer_,
                                                            sizeof(receive_buffer_)),
                                        *endpoint_,
                                        std::bind(&DNSClientTest::udpReceiveHandler,
                                                  this, socket_.get(),
                                                  endpoint_.get(), ph::_2,
                                                  corrupt_response));
        }
    }

    /// @brief Request handler for testing clients using TSIG
    ///
    /// This callback handler is installed when performing async read on a
    /// socket to emulate reception of the DNS Update request with TSIG by a
    /// server.  As a result, this handler will send an appropriate DNS Update
    /// response message back to the address from which the request has come.
    ///
    /// @param socket A pointer to a socket used to receive a query and send a
    /// response.
    /// @param remote A pointer to an object which specifies the host (address
    /// and port) from which a request has come.
    /// @param receive_length A length (in bytes) of the received data.
    /// @param client_key TSIG key the server should use to verify the inbound
    /// request.  If the pointer is NULL, the server will not attempt to
    /// verify the request.
    /// @param server_key TSIG key the server should use to sign the outbound
    /// request. If the pointer is NULL, the server will not sign the outbound
    /// response.  If the pointer is not NULL and not the same value as the
    /// client_key, the server will use a new context to sign the response then
    /// the one used to verify it.  This allows us to simulate the server
    /// signing with the wrong key.
    void TSIGReceiveHandler(udp::socket* socket, udp::endpoint* remote,
                            size_t receive_length,
                            D2TsigKeyPtr client_key,
                            D2TsigKeyPtr server_key) {

        TSIGContextPtr context;
        if (client_key) {
            context = client_key->createContext();
        }

        isc::util::InputBuffer received_data_buffer(receive_buffer_,
                                                    receive_length);

        dns::Message request(Message::PARSE);
        request.fromWire(received_data_buffer);

        // If context is not NULL, then we need to verify the message.
        if (context) {
            TSIGError error = context->verify(request.getTSIGRecord(),
                                              receive_buffer_, receive_length);
            if (error != TSIGError::NOERROR()) {
                isc_throw(TSIGVerifyError, "TSIG verification failed: "
                          << error.toText());
            }
        }

        dns::Message response(Message::RENDER);
        response.setOpcode(Opcode(Opcode::UPDATE_CODE));
        response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
        response.setQid(request.getQid());
        response.setRcode(Rcode::NOERROR());
        dns::Question question(Name("example.com."),
                                    RRClass::IN(), RRType::SOA());
        response.addQuestion(question);

        MessageRenderer renderer;

        if (!server_key) {
            // don't sign the response.
            context.reset();
        } else if (server_key != client_key) {
            // use a different key to sign the response.
            context.reset(new TSIGContext(*server_key));
        }  // otherwise use the context based on client_key.

        response.toWire(renderer, context.get());
        // A response message is now ready to send. Send it!
        socket->send_to(boost::asio::buffer(renderer.getData(),
                        renderer.getLength()),
                        *remote);
    }

    /// @brief This test verifies that when invalid response placeholder object
    /// is passed to a constructor which throws the appropriate exception.
    /// It also verifies that the constructor will not throw if the supplied
    /// callback object is NULL.
    void runConstructorTest() {
        EXPECT_NO_THROW(DNSClient(response_, NULL, DNSClient::UDP));

        // The TCP Transport is not supported right now. So, we return exception
        // if caller specified TCP as a preferred protocol. This test will be
        // removed once TCP is supported.
        EXPECT_THROW(DNSClient(response_, NULL, DNSClient::TCP),
                     isc::NotImplemented);
    }

    /// @brief This test verifies that it accepted timeout values belong to the
    /// range of <0, DNSClient::getMaxTimeout()>.
    void runInvalidTimeoutTest() {
        expect_response_ = false;

        // Create outbound message. Simply set the required message fields:
        // error code and Zone section. This is enough to create on-wire format
        // of this message and send it.
        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));

        // Start with a valid timeout equal to maximal allowed. This way we will
        // ensure that doUpdate doesn't throw an exception for valid timeouts.
        unsigned int timeout = DNSClient::getMaxTimeout();
        EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
                                              TEST_PORT, message, timeout));

        // Cross the limit and expect that exception is thrown this time.
        timeout = DNSClient::getMaxTimeout() + 1;
        EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
                                           TEST_PORT, message, timeout),
                     isc::BadValue);
    }

    /// @brief This test verifies the DNSClient behavior when a server does not
    /// respond do the DNS Update message. In such case, the callback function
    /// is expected to be called and the TIME_OUT error code should be returned.
    void runSendNoReceiveTest() {
        // We expect no response from a server.
        expect_response_ = false;

        // Create outgoing message. Simply set the required message fields:
        // error code and Zone section. This is enough to create on-wire format
        // of this message and send it.
        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));

        /// @todo The timeout value could be set to 0 to trigger timeout
        /// instantly. However, it may lead to situations that the message sent
        /// in one test will not be dropped by the kernel by the time, the next
        /// test starts. This will lead to intermittent unit test errors.
        /// Increasing the timeout to a non-zero value mitigates this problem.
        /// The proper way to solve this problem is to receive the packet
        /// on our own and drop it. Such a fix will need to be applied not only
        /// to this test but also for other tests that rely on arbitrary timeout
        /// values.
        const int timeout = 500;
        // The doUpdate() function starts asynchronous message exchange with DNS
        // server. When message exchange is done or timeout occurs, the
        // completion callback will be triggered. The doUpdate function returns
        // immediately.
        EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
                                              TEST_PORT, message, timeout));

        // This starts the execution of tasks posted to IOService. run() blocks
        // until stop() is called in the completion callback function.
        service_->run();
    }

    /// @brief This test verifies that DNSClient can send DNS Update and receive
    /// a corresponding response from a server.
    void runSendReceiveTest(const bool corrupt_response,
                            const bool two_sends) {
        go_on_ = two_sends;
        corrupt_response_ = corrupt_response;

        // Create a request DNS Update message.
        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));

        // In order to perform the full test, when the client sends the request
        // and receives a response from the server, we have to emulate the
        // server's response in the test. A request will be sent via loopback
        // interface to 127.0.0.1 and known test port. Response must be sent
        // to 127.0.0.1 and the source port which has been used to send the
        // request. A new socket is created, specifically to handle sending
        // responses. The reuse address option is set so as both sockets can
        // use the same address. This new socket is bound to the test address
        // and port, where requests will be sent.
        socket_.reset(new udp::socket(service_->getInternalIOService(),
                                      boost::asio::ip::udp::v4()));
        socket_->set_option(socket_base::reuse_address(true));
        socket_->bind(udp::endpoint(address::from_string(TEST_ADDRESS),
                                    TEST_PORT));
        // Once socket is created, we can post an IO request to receive some
        // packet from this socket. This is asynchronous operation and
        // nothing is received until another IO request to send a query is
        // posted and the run() is invoked on this IO. A callback function is
        // attached to this asynchronous read. This callback function requires
        // that a socket object used to receive the request is passed to it,
        // because the same socket will be used by the callback function to send
        // a response. Also, the remote object is passed to the callback,
        // because it holds a source address and port where request originated.
        // Callback function will send a response to this address and port.
        // The last parameter holds a length of the received request. It is
        // required to construct a response.
        endpoint_.reset(new udp::endpoint());
        socket_->async_receive_from(boost::asio::buffer(receive_buffer_,
                                                        sizeof(receive_buffer_)),
                                    *endpoint_,
                                    std::bind(&DNSClientTest::udpReceiveHandler,
                                              this, socket_.get(),
                                              endpoint_.get(), ph::_2,
                                              corrupt_response));

        // The socket is now ready to receive the data. Let's post some request
        // message then. Set timeout to some reasonable value to make sure that
        // there is sufficient amount of time for the test to generate a
        // response.
        const int timeout = 500;
        expected_++;
        dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
                              message, timeout);

        // It is possible to request that two packets are sent concurrently.
        if (two_sends) {
            expected_++;
            dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
                                  message, timeout);

        }

        // Kick off the message exchange by actually running the scheduled
        // "send" and "receive" operations.
        service_->run();

        socket_->close();

        // Since the callback, operator(), calls stop() on the io_service,
        // we must reset it in order for subsequent calls to run() or
        // runOne() to work.
        service_->stopAndPoll();
    }

    /// @brief Performs a single request-response exchange with or without TSIG.
    ///
    /// @param client_key TSIG passed to dns_client and also used by the
    /// "server" to verify the request.
    /// @param server_key TSIG key the "server" should use to sign the response.
    /// If this is NULL, then client_key is used.
    /// @param should_pass indicates if the test should pass.
    void runTSIGTest(D2TsigKeyPtr client_key, D2TsigKeyPtr server_key,
                     bool should_pass = true) {
        // Tell operator() method if we expect an invalid response.
        corrupt_response_ = !should_pass;

        // Create a request DNS Update message.
        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));

        // Setup our "loopback" server.
        udp::socket udp_socket(service_->getInternalIOService(), boost::asio::ip::udp::v4());
        udp_socket.set_option(socket_base::reuse_address(true));
        udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
                                      TEST_PORT));
        udp::endpoint remote;
        udp_socket.async_receive_from(boost::asio::buffer(receive_buffer_,
                                                   sizeof(receive_buffer_)),
                                      remote,
                                      std::bind(&DNSClientTest::
                                                TSIGReceiveHandler, this,
                                                &udp_socket, &remote, ph::_2,
                                                client_key, server_key));

        // The socket is now ready to receive the data. Let's post some request
        // message then. Set timeout to some reasonable value to make sure that
        // there is sufficient amount of time for the test to generate a
        // response.
        const int timeout = 500;
        expected_++;
        dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
                              message, timeout, client_key);

        // Kick of the message exchange by actually running the scheduled
        // "send" and "receive" operations.
        service_->run();

        udp_socket.close();

        // Since the callback, operator(), calls stop() on the io_service,
        // we must reset it in order for subsequent calls to run() or
        // runOne() to work.
        service_->stopAndPoll();
    }
};

// Verify that the DNSClient object can be created if provided parameters are
// valid. Constructor should throw exceptions when parameters are invalid.
TEST_F(DNSClientTest, constructor) {<--- syntax error
    runConstructorTest();
    StatMap stats_upd = {
        { "update-sent", 0},
        { "update-signed", 0},
        { "update-unsigned", 0},
        { "update-success", 0},
        { "update-timeout", 0},
        { "update-error", 0}
    };
    checkStats(stats_upd);
}

// This test verifies that the maximal allowed timeout value is maximal int
// value.
TEST_F(DNSClientTest, getMaxTimeout) {
    EXPECT_EQ(std::numeric_limits<int>::max(), DNSClient::getMaxTimeout());
}

// Verify that timeout is reported when no response is received from DNS.
TEST_F(DNSClientTest, timeout) {
    runSendNoReceiveTest();
    StatMap stats_upd = {
        { "update-sent", 1},
        { "update-signed", 0},
        { "update-unsigned", 1},
        { "update-success", 0},
        { "update-timeout", 1},
        { "update-error", 0}
    };
    checkStats(stats_upd);
}

// Verify that exception is thrown when invalid (too high) timeout value is
// specified for asynchronous DNS Update.
TEST_F(DNSClientTest, invalidTimeout) {
    runInvalidTimeoutTest();
}

// Verifies that TSIG can be used to sign requests and verify responses.
TEST_F(DNSClientTest, runTSIGTest) {
    std::string secret ("key number one");
    D2TsigKeyPtr key_one;
    ASSERT_NO_THROW(key_one.reset(new
                                    D2TsigKey(Name("one.com"),
                                              TSIGKey::HMACMD5_NAME(),
                                              secret.c_str(), secret.size())));
    StatMap stats_key = {
        { "update-sent", 0},
        { "update-success", 0},
        { "update-timeout", 0},
        { "update-error", 0}
    };
    checkStats("one.com.", stats_key);
    secret = "key number two";
    D2TsigKeyPtr key_two;
    ASSERT_NO_THROW(key_two.reset(new
                                    D2TsigKey(Name("two.com"),
                                              TSIGKey::HMACMD5_NAME(),
                                              secret.c_str(), secret.size())));
    checkStats("two.com.", stats_key);
    D2TsigKeyPtr nokey;

    // Should be able to send and receive with no keys.
    // Neither client nor server will attempt to sign or verify.
    runTSIGTest(nokey, nokey);

    // Client signs the request, server verifies but doesn't sign.
    runTSIGTest(key_one, nokey, false);

    // Client and server use the same key to sign and verify.
    runTSIGTest(key_one, key_one);

    // Server uses different key to sign the response.
    runTSIGTest(key_one, key_two, false);

    // Client neither signs nor verifies, server responds with a signed answer
    // Since we are "liberal" in what we accept this should be ok.
    runTSIGTest(nokey, key_two);

    // Check statistics.
    StatMap stats_one = {
        { "update-sent", 3},
        { "update-success", 1},
        { "update-timeout", 0},
        { "update-error", 2}
    };
    checkStats("one.com.", stats_one);
    checkStats("two.com.", stats_key);
    StatMap stats_upd = {
        { "update-sent", 5},
        { "update-signed", 3},
        { "update-unsigned", 2},
        { "update-success", 3},
        { "update-timeout", 0},
        { "update-error", 2}
    };
    checkStats(stats_upd);
}

// Verify that the DNSClient receives the response from DNS and the received
// buffer can be decoded as DNS Update Response.
TEST_F(DNSClientTest, sendReceive) {
    // false means that server response is not corrupted.
    runSendReceiveTest(false, false);
    StatMap stats_upd = {
        { "update-sent", 1},
        { "update-signed", 0},
        { "update-unsigned", 1},
        { "update-success", 1},
        { "update-timeout", 0},
        { "update-error", 0}
    };
    checkStats(stats_upd);
}

// Verify that the DNSClient reports an error when the response is received from
// a DNS and this response is corrupted.
TEST_F(DNSClientTest, sendReceiveCorrupted) {
    // true means that server's response is corrupted.
    runSendReceiveTest(true, false);
    StatMap stats_upd = {
        { "update-sent", 1},
        { "update-signed", 0},
        { "update-unsigned", 1},
        { "update-success", 0},
        { "update-timeout", 0},
        { "update-error", 1}
    };
    checkStats(stats_upd);
}

// Verify that it is possible to use the same DNSClient instance to
// perform the following sequence of message exchanges:
// 1. send
// 2. receive
// 3. send
// 4. receive
TEST_F(DNSClientTest, sendReceiveTwice) {
    runSendReceiveTest(false, false);
    runSendReceiveTest(false, false);
    EXPECT_EQ(2, received_);
    StatMap stats_upd = {
        { "update-sent", 2},
        { "update-signed", 0},
        { "update-unsigned", 2},
        { "update-success", 2},
        { "update-timeout", 0},
        { "update-error", 0}
    };
    checkStats(stats_upd);
}

// Verify that it is possible to use the DNSClient instance to perform the
// following  sequence of message exchanges:
// 1. send
// 2. send
// 3. receive
// 4. receive
TEST_F(DNSClientTest, concurrentSendReceive) {
    runSendReceiveTest(false, true);
    StatMap stats_upd = {
        { "update-sent", 2},
        { "update-signed", 0},
        { "update-unsigned", 2},
        { "update-success", 2},
        { "update-timeout", 0},
        { "update-error", 0}
    };
    checkStats(stats_upd);
}

} // End of anonymous namespace