Browse Source

initial implementation (tests + code) of trac #423

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac423@3768 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 14 years ago
parent
commit
a66dd9815f

+ 1 - 0
src/lib/dns/Makefile.am

@@ -69,6 +69,7 @@ libdns___la_SOURCES += dnssectime.h dnssectime.cc
 libdns___la_SOURCES += edns.h edns.cc
 libdns___la_SOURCES += exceptions.h exceptions.cc
 libdns___la_SOURCES += util/hex.h
+libdns___la_SOURCES += master.h master.cc
 libdns___la_SOURCES += message.h message.cc
 libdns___la_SOURCES += messagerenderer.h messagerenderer.cc
 libdns___la_SOURCES += name.h name.cc

+ 134 - 0
src/lib/dns/master.cc

@@ -0,0 +1,134 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <istream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <cctype>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/master.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+
+using namespace std;
+using namespace boost;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace dns {
+void
+masterLoad(const char* const filename, const RRClass& zone_class,
+           MasterLoadCallback callback)
+{
+    ifstream ifs;
+
+    ifs.open(filename, ios_base::in);
+    if (ifs.fail()) {
+        isc_throw(MasterError, "Failed to open master file: " << filename);
+    }
+    masterLoad(ifs, zone_class, callback);
+    ifs.close();
+}
+
+void
+masterLoad(istream& input, const RRClass& zone_class,
+           MasterLoadCallback callback)
+{
+    RRsetPtr rrset;
+    ConstRRsetPtr prev_rrset;
+    string line;
+    unsigned int line_count = 1;
+
+    do {
+        getline(input, line);
+        if (input.bad() || (input.fail() && !input.eof())) {
+            isc_throw(MasterError, "Unexpectedly failed to read a line");
+        }
+
+        // blank/comment lines should be simply skipped.
+        if (line.empty() || line[0] == ';') {
+            continue;
+        }
+
+        // The line shouldn't have leading space (which means omitting the
+        // owner name).
+        if (isspace(line[0])) {
+            isc_throw(MasterError, "Leading space at line " << line_count);
+        }
+
+        istringstream iss(line);
+        string owner_txt, ttl_txt, rrclass_txt, rrtype_txt;
+        stringbuf rdatabuf;
+        iss >> owner_txt >> ttl_txt >> rrclass_txt >> rrtype_txt >> &rdatabuf;
+        if (iss.bad() || iss.fail()) {
+            isc_throw(MasterError, "Parse failure for a valid RR at line "
+                      << line_count);
+        }
+
+        // This simple version doesn't support relative owner names with a
+        // separate origin.
+        if (owner_txt.empty() || *(owner_txt.end() - 1) != '.') {
+            isc_throw(MasterError, "Owner name is not absolute at line "
+                      << line_count);
+        }
+
+        // XXX: this part is a bit tricky (and less efficient).
+        scoped_ptr<const Name> owner;
+        scoped_ptr<const RRTTL> ttl;
+        scoped_ptr<const RRClass> rrclass;
+        scoped_ptr<const RRType> rrtype;
+        ConstRdataPtr rdata;
+        try {
+            owner.reset(new Name(owner_txt));
+            ttl.reset(new RRTTL(ttl_txt));
+            rrclass.reset(new RRClass(rrclass_txt));
+            rrtype.reset(new RRType(rrtype_txt));
+            rdata = createRdata(*rrtype, *rrclass, rdatabuf.str());
+        } catch (const Exception& ex) {
+            isc_throw(MasterError, "Invalid RR text at line " << line_count
+                      << ": " << ex.what());
+        }
+
+        if (*rrclass != zone_class) {
+            isc_throw(MasterError, "RR class (" << rrclass_txt
+                      << ") does not match the zone class (" << zone_class
+                      << ") at line " << line_count);
+        }
+
+        if (!prev_rrset || prev_rrset->getType() != *rrtype ||
+            prev_rrset->getName() != *owner) {
+            if (rrset) {
+                callback(rrset);
+            }
+            rrset = RRsetPtr(new RRset(*owner, *rrclass, *rrtype, *ttl));
+        }
+        rrset->addRdata(rdata);
+        prev_rrset = rrset;
+    } while (++line_count, !input.eof());
+
+    if (rrset) {
+        callback(rrset);
+    }
+}
+} // namespace dns
+} // namespace isc

+ 100 - 0
src/lib/dns/master.h

@@ -0,0 +1,100 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __MASTER_H
+#define __MASTER_H 1
+
+#include <functional>
+#include <istream>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrset.h>
+
+namespace isc {
+namespace dns {
+class RRClass;
+
+/// TBD documentation
+class MasterError : public Exception {
+public:
+    MasterError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+class MasterLoadCallback {
+public:
+    /// note: this constructor is intentionally not defined as "explicit"
+    /// for the convenience of the caller of \c masterLoad().
+    template <typename FUNC>
+    MasterLoadCallback(FUNC func) :
+        func_(new FUNC(func)),
+        invoker_(invoke<FUNC>),
+        deleter_(cleanup<FUNC>),
+        copier_(copyFunctor<FUNC>)
+    {}
+    MasterLoadCallback(const MasterLoadCallback& source) :
+        func_((*source.copier_)(source.func_)),
+        invoker_(source.invoker_),
+        deleter_(source.deleter_),
+        copier_(source.copier_)
+    {}
+    ~MasterLoadCallback() { (*deleter_)(func_); }
+    void operator()(RRsetPtr rrset) {
+        (*invoker_)(func_, rrset);
+    }
+private:
+    // For our purpose, we don't need this.  Simply hide it for now.
+    MasterLoadCallback& operator=(const MasterLoadCallback& source);
+    
+    template <typename FUNC>
+    static void invoke(void* func, RRsetPtr rrset) {
+        FUNC* funcobj = reinterpret_cast<FUNC*>(func);
+        return ((*funcobj)(rrset));
+    }
+    template <typename FUNC>
+    static void cleanup(void* func) {
+        delete reinterpret_cast<FUNC*>(func);
+    }
+    template <typename FUNC>
+    static void* copyFunctor(void* func) {
+        return (new FUNC(*reinterpret_cast<FUNC*>(func)));
+    }
+    void* func_;
+    void (*invoker_)(void*, RRsetPtr);
+    void (*deleter_)(void*);
+    void* (*copier_)(void*);
+};
+
+/// If the RR class of a given RR is different from \c zone_class,
+/// an exception of class \c MasterError will be thrown.
+///
+/// RRSIG consideration (later)?
+// may eventually want to make it a class; for now, it's not clear whether
+// we need that level of abstraction.
+// may want to have load options; may want to support incremental load.
+// may want to support "generous" mode.
+void masterLoad(const char* const filename, const RRClass& zone_class,
+                MasterLoadCallback callback);
+
+void masterLoad(std::istream& input, const RRClass& zone_class,
+                MasterLoadCallback callback);
+}
+}
+
+#endif  // __MASTER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 0
src/lib/dns/tests/Makefile.am

@@ -42,6 +42,7 @@ run_unittests_SOURCES += rdata_tsig_unittest.cc
 run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
+run_unittests_SOURCES += master_unittest.cc
 run_unittests_SOURCES += message_unittest.cc
 run_unittests_SOURCES += base32hex_unittest.cc
 run_unittests_SOURCES += base64_unittest.cc

+ 217 - 0
src/lib/dns/tests/master_unittest.cc

@@ -0,0 +1,217 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <functional>
+#include <ios>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <dns/master.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace {
+class TestCallback : public unary_function<ConstRRsetPtr, void> {
+public:
+    TestCallback(vector<ConstRRsetPtr>& rrsets) : rrsets_(rrsets) {}
+    void operator()(ConstRRsetPtr rrset) {
+        rrsets_.push_back(rrset);
+    }
+private:
+    vector<ConstRRsetPtr>& rrsets_;
+};
+
+void
+testCallback(ConstRRsetPtr rrset, vector<ConstRRsetPtr>* rrsets) {
+    rrsets->push_back(rrset);
+}
+
+class MasterTest : public ::testing::Test {
+protected:
+    MasterTest() : callback(results) {}
+    vector<ConstRRsetPtr> results;
+    stringstream rr_stream;
+    TestCallback callback;
+};
+
+const char* const txt_rr = "example.com. 3600 IN TXT \"test data\"\n";
+const char* const a_rr1 = "www.example.com. 60 IN A 192.0.2.1\n";
+const char* const a_rr2 = "www.example.com. 60 IN A 192.0.2.2\n";
+const char* const a_rr3 = "ftp.example.com. 60 IN A 192.0.2.3\n";
+// multi-field RR case
+const char* const mx_rr = "example.com. 7200 IN MX 10 mail.example.com.\n";
+
+TEST_F(MasterTest, loadRRs) {
+    // a simple case: loading 3 RRs, each consists of a single RRset.
+    rr_stream << txt_rr << a_rr1 << mx_rr;
+    masterLoad(rr_stream, RRClass::IN(), callback);
+    ASSERT_EQ(3, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+    EXPECT_EQ(a_rr1, results[1]->toText());
+    EXPECT_EQ(mx_rr, results[2]->toText());
+}
+
+TEST_F(MasterTest, loadWithFunctionCallback) {
+    // The same test as loadRRs but using a normal function (not a functor
+    // object)
+    rr_stream << txt_rr << a_rr1 << mx_rr;
+    masterLoad(rr_stream, RRClass::IN(),
+               bind2nd(ptr_fun(testCallback), &results));
+    ASSERT_EQ(3, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+    EXPECT_EQ(a_rr1, results[1]->toText());
+    EXPECT_EQ(mx_rr, results[2]->toText());
+}
+
+TEST_F(MasterTest, loadComments) {
+    rr_stream << ";; comment line, should be skipped\n"
+              << "\n"           // blank line (should be skipped)
+              << txt_rr;
+    masterLoad(rr_stream, RRClass::IN(), callback);
+    ASSERT_EQ(1, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+}
+
+TEST_F(MasterTest, loadRRset) {
+    // load an RRset containing two RRs
+    rr_stream << a_rr1 << a_rr2;
+    masterLoad(rr_stream, RRClass::IN(), callback);
+    ASSERT_EQ(1, results.size());
+    EXPECT_EQ(string(a_rr1) + string(a_rr2), results[0]->toText());
+}
+
+TEST_F(MasterTest, loadRRsetsOfSameType) {
+    // load two RRsets with the same RR type and different owner names.
+    // the loader must distinguish them as separate RRsets.
+    rr_stream << a_rr1 << a_rr3;
+    masterLoad(rr_stream, RRClass::IN(), callback);
+    ASSERT_EQ(2, results.size());
+    EXPECT_EQ(a_rr1, results[0]->toText());
+    EXPECT_EQ(a_rr3, results[1]->toText());
+}
+
+TEST_F(MasterTest, loadRRsetsInterleaved) {
+    // two RRs that belongs to the same RRset (rr1 and rr2) are interleaved
+    // by another.  This is an unexpected case for this loader, but it's
+    // not considered an error.  The loader will simply treat them separate
+    // RRsets.
+    rr_stream << a_rr1 << a_rr3 << a_rr2;
+    masterLoad(rr_stream, RRClass::IN(), callback);
+    ASSERT_EQ(3, results.size());
+    EXPECT_EQ(a_rr1, results[0]->toText());
+    EXPECT_EQ(a_rr3, results[1]->toText());
+    EXPECT_EQ(a_rr2, results[2]->toText());
+}
+
+TEST_F(MasterTest, loadWithNoEOF) {
+    // the input stream doesn't end with a new line (and the following blank
+    // line).  It should be accepted.
+    string rr_string(a_rr1);
+    rr_string.erase(rr_string.end() - 1);
+    rr_stream << rr_string;
+    masterLoad(rr_stream, RRClass::IN(), callback);
+    ASSERT_EQ(1, results.size());
+    EXPECT_EQ(a_rr1, results[0]->toText());
+}
+
+TEST_F(MasterTest, loadEmpty) {
+    // an unusual case: empty input.  load must succeed with an empty result.
+    masterLoad(rr_stream, RRClass::IN(), callback);
+    EXPECT_EQ(0, results.size());   
+}
+
+TEST_F(MasterTest, loadWithBeginningSpace) {
+    rr_stream << " " << a_rr1;
+    EXPECT_THROW(masterLoad(rr_stream, RRClass::IN(), callback), MasterError);
+}
+
+TEST_F(MasterTest, loadWithBeginningTab) {
+    rr_stream << "\t" << a_rr1;
+    EXPECT_THROW(masterLoad(rr_stream, RRClass::IN(), callback), MasterError);
+}
+
+TEST_F(MasterTest, loadInvalidRRClass) {
+    rr_stream << "example.com. 3600 CH TXT \"test text\"";
+    EXPECT_THROW(masterLoad(rr_stream, RRClass::IN(), callback), MasterError);
+}
+
+TEST_F(MasterTest, loadBadRRText) {
+    rr_stream << "example..com. 3600 IN A 192.0.2.1"; // bad owner name
+    EXPECT_THROW(masterLoad(rr_stream, RRClass::IN(), callback), MasterError);
+
+    // currently we only support numeric TTLs
+    stringstream rr_stream2("example.com. 1D IN A 192.0.2.1");
+    EXPECT_THROW(masterLoad(rr_stream2, RRClass::IN(), callback), MasterError);
+
+    // bad RR class text
+    stringstream rr_stream3("example.com. 3600 BAD A 192.0.2.1");
+    EXPECT_THROW(masterLoad(rr_stream3, RRClass::IN(), callback), MasterError);
+
+    // bad RR type text
+    stringstream rr_stream4("example.com. 3600 IN BAD 192.0.2.1");
+    EXPECT_THROW(masterLoad(rr_stream4, RRClass::IN(), callback), MasterError);
+
+    // bad RDATA text
+    stringstream rr_stream5("example.com. 3600 IN A 2001:db8::1");
+    EXPECT_THROW(masterLoad(rr_stream5, RRClass::IN(), callback), MasterError);
+
+    // incomplete RR text
+    stringstream rr_stream6("example.com. 3600 IN A");
+    EXPECT_THROW(masterLoad(rr_stream6, RRClass::IN(), callback), MasterError);
+
+    // owner name is not absolute
+    stringstream rr_stream7("example.com 3600 IN A 192.0.2.1");
+    EXPECT_THROW(masterLoad(rr_stream7, RRClass::IN(), callback), MasterError);
+}
+
+// This is a helper callback to test the case the input stream becomes bad
+// in the middle of processing.
+class StreamInvalidator : public unary_function<ConstRRsetPtr, void> {
+public:
+    StreamInvalidator(stringstream& ss) : ss_(ss) {}
+    void operator()(ConstRRsetPtr) {
+        ss_.setstate(ios::badbit);
+    }
+private:
+    stringstream& ss_;
+};
+
+TEST_F(MasterTest, loadBadStream) {
+    rr_stream << txt_rr << a_rr1;
+    StreamInvalidator invalidator(rr_stream);
+    EXPECT_THROW(masterLoad(rr_stream, RRClass::IN(), invalidator),
+                 MasterError);
+}
+
+TEST_F(MasterTest, loadFromFile) {
+    // The main parser is shared with the stream version, so we simply test
+    // file I/O specific parts.
+    masterLoad(TEST_DATA_BUILDDIR "/masterload.txt", RRClass::IN(), callback);
+    ASSERT_EQ(2, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+    EXPECT_EQ(string(a_rr1) + string(a_rr2), results[1]->toText());
+}
+
+TEST_F(MasterTest, loadFromFileFailure) {
+    EXPECT_THROW(masterLoad(TEST_DATA_BUILDDIR "/notexistent.txt",
+                            RRClass::IN(), callback), MasterError);
+}
+} // end namespace

+ 5 - 0
src/lib/dns/tests/testdata/masterload.txt

@@ -0,0 +1,5 @@
+;; a simple (incomplete) zone file
+
+example.com. 3600 IN TXT "test data"
+www.example.com. 60 IN A 192.0.2.1
+www.example.com. 60 IN A 192.0.2.2