summaryrefslogtreecommitdiffstats
path: root/src/lib/dns/tests/rrcollator_unittest.cc
blob: 4247911d35ba87e1fc2445e989755fd883dc68c8 (plain)
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
// Copyright (C) 2012-2020 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 <exceptions/exceptions.h>

#include <dns/name.h>
#include <dns/master_loader.h>
#include <dns/master_loader_callbacks.h>
#include <dns/rrclass.h>
#include <dns/rrcollator.h>
#include <dns/rdata.h>
#include <dns/rrset.h>
#include <dns/rrttl.h>

#include <gtest/gtest.h>

#include <functional>
#include <sstream>
#include <vector>

using std::vector;
using namespace isc::dns;
using namespace isc::dns::rdata;
namespace ph = std::placeholders;

namespace {

typedef RRCollator::AddRRsetCallback AddRRsetCallback;

void
addRRset(const RRsetPtr& rrset, vector<ConstRRsetPtr>* to_append,
         const bool* do_throw) {
    if (*do_throw) {
        isc_throw(isc::Unexpected, "faked failure");
    }
    to_append->push_back(rrset);
}

class RRCollatorTest : public ::testing::Test {
protected:
    RRCollatorTest() :
        origin_("example.com"), rrclass_(RRClass::IN()), rrttl_(3600),
        throw_from_callback_(false),
        collator_(std::bind(addRRset, ph::_1, &rrsets_, &throw_from_callback_)),
        rr_callback_(collator_.getCallback()),
        a_rdata1_(createRdata(RRType::A(), rrclass_, "192.0.2.1")),
        a_rdata2_(createRdata(RRType::A(), rrclass_, "192.0.2.2")),
        txt_rdata_(createRdata(RRType::TXT(), rrclass_, "test")),
        sig_rdata1_(createRdata(RRType::RRSIG(), rrclass_,
                                "A 5 3 3600 20000101000000 20000201000000 "
                                "12345 example.com. FAKE")),
        sig_rdata2_(createRdata(RRType::RRSIG(), rrclass_,
                                "NS 5 3 3600 20000101000000 20000201000000 "
                                "12345 example.com. FAKE"))
    {}

    void checkRRset(const Name& expected_name, const RRClass& expected_class,
                    const RRType& expected_type, const RRTTL& expected_ttl,
                    const vector<ConstRdataPtr>& expected_rdatas) {
        SCOPED_TRACE(expected_name.toText(true) + "/" +
                     expected_class.toText() + "/" + expected_type.toText());

        // This test always clears rrsets_ to confirm RRsets are added
        // one-by-one
        ASSERT_EQ(1, rrsets_.size());

        ConstRRsetPtr actual = rrsets_[0];
        EXPECT_EQ(expected_name, actual->getName());
        EXPECT_EQ(expected_class, actual->getClass());
        EXPECT_EQ(expected_type, actual->getType());
        EXPECT_EQ(expected_ttl, actual->getTTL());
        ASSERT_EQ(expected_rdatas.size(), actual->getRdataCount());
        vector<ConstRdataPtr>::const_iterator it = expected_rdatas.begin();
        for (RdataIteratorPtr rit = actual->getRdataIterator();
             !rit->isLast();
             rit->next()) {
            EXPECT_EQ(0, rit->getCurrent().compare(**it));
            ++it;
        }

        rrsets_.clear();
    }

    const Name origin_;
    const RRClass rrclass_;
    const RRTTL rrttl_;
    vector<ConstRRsetPtr> rrsets_;
    bool throw_from_callback_;
    RRCollator collator_;
    AddRRCallback rr_callback_;
    const RdataPtr a_rdata1_, a_rdata2_, txt_rdata_, sig_rdata1_, sig_rdata2_;
    vector<ConstRdataPtr> rdatas_; // placeholder for expected data
};

TEST_F(RRCollatorTest, basicCases) {
    // Add two RRs belonging to the same RRset.  These will be buffered.
    rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata1_);
    EXPECT_TRUE(rrsets_.empty()); // not yet given as an RRset
    rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata2_);
    EXPECT_TRUE(rrsets_.empty()); // still not given

    // Add another type of RR.  This completes the construction of the A RRset,
    // which will be given via the callback.
    rr_callback_(origin_, rrclass_, RRType::TXT(), rrttl_, txt_rdata_);
    rdatas_.push_back(a_rdata1_);
    rdatas_.push_back(a_rdata2_);
    checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);

    // Add the same type of RR but of different name.  This should make another
    // callback for the previous TXT RR.
    rr_callback_(Name("txt.example.com"), rrclass_, RRType::TXT(), rrttl_,
                 txt_rdata_);
    rdatas_.clear();
    rdatas_.push_back(txt_rdata_);
    checkRRset(origin_, rrclass_, RRType::TXT(), rrttl_, rdatas_);

    // Add the same type and name of RR but of different class (rare case
    // in practice)
    rr_callback_(Name("txt.example.com"), RRClass::CH(), RRType::TXT(), rrttl_,
                 txt_rdata_);
    rdatas_.clear();
    rdatas_.push_back(txt_rdata_);
    checkRRset(Name("txt.example.com"), rrclass_, RRType::TXT(), rrttl_,
               rdatas_);

    // Tell the collator we are done, then we'll see the last RR as an RRset.
    collator_.flush();
    checkRRset(Name("txt.example.com"), RRClass::CH(), RRType::TXT(), rrttl_,
               rdatas_);

    // Redundant flush() will be no-op.
    collator_.flush();
    EXPECT_TRUE(rrsets_.empty());
}

TEST_F(RRCollatorTest, minTTLFirst) {
    // RRs of the same RRset but has different TTLs.  The first RR has
    // the smaller TTL, which should be used for the TTL of the RRset.
    rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(10), a_rdata1_);
    rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(20), a_rdata2_);
    rdatas_.push_back(a_rdata1_);
    rdatas_.push_back(a_rdata2_);
    collator_.flush();
    checkRRset(origin_, rrclass_, RRType::A(), RRTTL(10), rdatas_);
}

TEST_F(RRCollatorTest, maxTTLFirst) {
    // RRs of the same RRset but has different TTLs.  The second RR has
    // the smaller TTL, which should be used for the TTL of the RRset.
    rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(20), a_rdata1_);
    rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(10), a_rdata2_);
    rdatas_.push_back(a_rdata1_);
    rdatas_.push_back(a_rdata2_);
    collator_.flush();
    checkRRset(origin_, rrclass_, RRType::A(), RRTTL(10), rdatas_);
}

TEST_F(RRCollatorTest, addRRSIGs) {
    // RRSIG is special; they are also distinguished by their covered types.
    rr_callback_(origin_, rrclass_, RRType::RRSIG(), rrttl_, sig_rdata1_);
    rr_callback_(origin_, rrclass_, RRType::RRSIG(), rrttl_, sig_rdata2_);

    rdatas_.push_back(sig_rdata1_);
    checkRRset(origin_, rrclass_, RRType::RRSIG(), rrttl_, rdatas_);
}

TEST_F(RRCollatorTest, emptyFlush) {
    collator_.flush();
    EXPECT_TRUE(rrsets_.empty());
}

TEST_F(RRCollatorTest, throwFromCallback) {
    // Adding an A RR
    rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata1_);

    // Adding a TXT RR, which would trigger RRset callback, but in this test
    // it throws.  The added TXT RR will be effectively lost.
    throw_from_callback_ = true;
    EXPECT_THROW(rr_callback_(origin_, rrclass_, RRType::TXT(), rrttl_,
                              txt_rdata_), isc::Unexpected);

    // We'll only see the A RR.
    throw_from_callback_ = false;
    collator_.flush();
    rdatas_.push_back(a_rdata1_);
    checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);
}

TEST_F(RRCollatorTest, withMasterLoader) {
    // Test a simple case with MasterLoader.  There shouldn't be anything
    // special, but that's the mainly intended usage of the collator, so we
    // check it explicitly.
    std::istringstream ss("example.com. 3600 IN A 192.0.2.1\n");
    MasterLoader loader(ss, origin_, rrclass_,
                        MasterLoaderCallbacks::getNullCallbacks(),
                        collator_.getCallback());
    loader.load();
    collator_.flush();
    rdatas_.push_back(a_rdata1_);
    checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);
}

}