summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/dns/Makefile.am2
-rwxr-xr-xsrc/lib/dns/gen-rdatacode.py.in2
-rw-r--r--src/lib/dns/rdata/generic/caa_257.cc313
-rw-r--r--src/lib/dns/rdata/generic/caa_257.h61
-rw-r--r--src/lib/dns/tests/Makefile.am1
-rw-r--r--src/lib/dns/tests/rdata_caa_unittest.cc300
-rw-r--r--src/lib/dns/tests/testdata/.gitignore4
-rw-r--r--src/lib/dns/tests/testdata/Makefile.am5
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire56
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire64
-rwxr-xr-xsrc/lib/util/python/gen_wiredata.py.in28
15 files changed, 751 insertions, 2 deletions
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index bda4e85333..bbc214d131 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -76,6 +76,8 @@ EXTRA_DIST += rdata/generic/minfo_14.cc
EXTRA_DIST += rdata/generic/minfo_14.h
EXTRA_DIST += rdata/generic/afsdb_18.cc
EXTRA_DIST += rdata/generic/afsdb_18.h
+EXTRA_DIST += rdata/generic/caa_257.cc
+EXTRA_DIST += rdata/generic/caa_257.h
EXTRA_DIST += rdata/hs_4/a_1.cc
EXTRA_DIST += rdata/hs_4/a_1.h
EXTRA_DIST += rdata/in_1/a_1.cc
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index 3fd3b33112..84b55f8146 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -43,7 +43,7 @@ meta_types = {
'27': 'gpos', '29': 'loc', '36': 'kx', '37': 'cert', '42': 'apl',
'45': 'ipseckey', '52': 'tlsa', '55': 'hip', '103': 'unspec',
'104': 'nid', '105': 'l32', '106': 'l64', '107': 'lp', '249': 'tkey',
- '253': 'mailb', '256': 'uri', '257': 'caa'
+ '253': 'mailb', '256': 'uri'
}
# Classes that don't have any known types. This is a dict from type code
# values (as string) to textual mnemonic.
diff --git a/src/lib/dns/rdata/generic/caa_257.cc b/src/lib/dns/rdata/generic/caa_257.cc
new file mode 100644
index 0000000000..47366d651a
--- /dev/null
+++ b/src/lib/dns/rdata/generic/caa_257.cc
@@ -0,0 +1,313 @@
+// Copyright (C) 2014 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 <config.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/char_string.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct CAAImpl {
+ // straightforward representation of CAA RDATA fields
+ CAAImpl(uint8_t flags, const std::string& tag,
+ const std::vector<uint8_t>& value) :
+ flags_(flags),
+ tag_(tag),
+ value_(value)
+ {}
+
+ uint8_t flags_;
+ const std::string tag_;
+
+ // The first byte of this vector contains the length of the rest of
+ // the vector. This byte is actually unused and is skipped when
+ // reading the vector.
+ const detail::CharString value_;
+};
+
+// helper function for string and lexer constructors
+CAAImpl*
+CAA::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t flags =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (flags > 255) {
+ isc_throw(InvalidRdataText,
+ "CAA flags field out of range");
+ }
+
+ // Tag field must not be empty.
+ const std::string tag =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ if (tag.empty()) {
+ isc_throw(InvalidRdataText, "CAA tag field is empty");
+ } else if (tag.size() > 255) {
+ isc_throw(InvalidRdataText,
+ "CAA tag field is too large: " << tag.size());
+ }
+
+ // Value field may be empty.
+ std::vector<uint8_t> value;
+ MasterToken token = lexer.getNextToken(MasterToken::QSTRING, true);
+ if ((token.getType() != MasterToken::END_OF_FILE) &&
+ (token.getType() != MasterToken::END_OF_LINE))
+ {
+ detail::stringToCharString(token.getStringRegion(), value);
+ } else {
+ // Convert it into a CharString.
+ value.push_back(0);
+ }
+
+ return (new CAAImpl(flags, tag, value));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid CAA RDATA. There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The Flags, Tag and Value fields must be within their valid ranges,
+/// but are not constrained to the values defined in RFC6844. The Tag
+/// field must not be empty.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their
+/// valid ranges, incorrect, or empty.
+///
+/// \param caa_str A string containing the RDATA to be created
+CAA::CAA(const string& caa_str) :
+ impl_(NULL)
+{
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the CAAImpl that constructFromLexer() returns.
+ std::auto_ptr<CAAImpl> impl_ptr(NULL);
+
+ try {
+ std::istringstream ss(caa_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for CAA: "
+ << caa_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct CAA from '" <<
+ caa_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an CAA RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing
+/// field.
+/// \throw InvalidRdataText Fields are out of their valid ranges,
+/// incorrect, or empty.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+CAA::CAA(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid CAA RDATA.
+///
+/// The Flags, Tag and Value fields must be within their valid ranges,
+/// but are not constrained to the values defined in RFC6844. The Tag
+/// field must not be empty.
+CAA::CAA(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 2) {
+ isc_throw(InvalidRdataLength, "CAA record too short");
+ }
+
+ const uint8_t flags = buffer.readUint8();
+ const uint8_t tag_length = buffer.readUint8();
+ rdata_len -= 2;
+ if (tag_length == 0) {
+ isc_throw(InvalidRdataText, "CAA tag field is empty");
+ }
+
+ if (rdata_len < tag_length) {
+ isc_throw(InvalidRdataLength,
+ "RDATA is too short for CAA tag field");
+ }
+
+ vector<uint8_t> tag_vec;
+ tag_vec.resize(tag_length + 1);
+ tag_vec[0] = tag_length;
+ buffer.readData(&tag_vec[1], tag_length);
+ rdata_len -= tag_length;
+
+ const std::string tag = detail::charStringToString(tag_vec);
+
+ if (rdata_len > 255) {
+ isc_throw(InvalidRdataLength,
+ "CAA value field is too long: " << rdata_len);
+ }
+
+ vector<uint8_t> value;
+ value.resize(rdata_len + 1);
+ value[0] = rdata_len;
+ if (rdata_len > 0) {
+ buffer.readData(&value[1], rdata_len);
+ }
+
+ impl_ = new CAAImpl(flags, tag, value);
+}
+
+CAA::CAA(uint8_t flags, const std::string& tag, const std::string& value) :
+ impl_(NULL)
+{
+ if (tag.empty()) {
+ isc_throw(isc::InvalidParameter,
+ "CAA tag field is empty");
+ } else if (tag.size() > 255) {
+ isc_throw(isc::InvalidParameter,
+ "CAA tag field is too large: " << tag.size());
+ }
+
+ if (value.size() > 255) {
+ isc_throw(isc::InvalidParameter,
+ "CAA value field is too long: " << value.size());
+ }
+
+ std::vector<uint8_t> value_vec;
+ value_vec.reserve(value.size() + 1);
+ value_vec.push_back(value.size());
+ value_vec.insert(value_vec.end(), value.begin(), value.end());
+
+ impl_ = new CAAImpl(flags, tag, value_vec);
+}
+
+CAA::CAA(const CAA& other) :
+ Rdata(), impl_(new CAAImpl(*other.impl_))
+{}
+
+CAA&
+CAA::operator=(const CAA& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ CAAImpl* newimpl = new CAAImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+CAA::~CAA() {
+ delete impl_;
+}
+
+void
+CAA::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint8(impl_->flags_);
+ buffer.writeUint8(impl_->tag_.size());
+ if (!impl_->tag_.empty()) {
+ buffer.writeData(&impl_->tag_[0],
+ impl_->tag_.size());
+ }
+ if (impl_->value_.size() > 1) {
+ buffer.writeData(&impl_->value_[1],
+ impl_->value_.size() - 1);
+ }
+}
+
+void
+CAA::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint8(impl_->flags_);
+ renderer.writeUint8(impl_->tag_.size());
+ if (!impl_->tag_.empty()) {
+ renderer.writeData(&impl_->tag_[0],
+ impl_->tag_.size());
+ }
+ if (impl_->value_.size() > 1) {
+ renderer.writeData(&impl_->value_[1],
+ impl_->value_.size() - 1);
+ }
+}
+
+std::string
+CAA::toText() const {
+ std::string result;
+
+ result = lexical_cast<std::string>(static_cast<int>(impl_->flags_));
+ result += " " + impl_->tag_;
+ result += " \"" + detail::charStringToString(impl_->value_) + "\"";
+
+ return (result);
+}
+
+int
+CAA::compare(const Rdata& other) const {
+ const CAA& other_caa = dynamic_cast<const CAA&>(other);
+
+ if (impl_->flags_ < other_caa.impl_->flags_) {
+ return (-1);
+ } else if (impl_->flags_ > other_caa.impl_->flags_) {
+ return (1);
+ }
+
+ // Do a case-insensitive compare of the tag strings.
+ const int result = boost::ilexicographical_compare
+ <std::string, std::string>(impl_->tag_, other_caa.impl_->tag_);
+ if (result != 0) {
+ return (result);
+ }
+
+ return (detail::compareCharStrings(impl_->value_,
+ other_caa.impl_->value_));
+}
+
+uint8_t
+CAA::getFlags() const {
+ return (impl_->flags_);
+}
+
+const std::string&
+CAA::getTag() const {
+ return (impl_->tag_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/caa_257.h b/src/lib/dns/rdata/generic/caa_257.h
new file mode 100644
index 0000000000..69e3936d82
--- /dev/null
+++ b/src/lib/dns/rdata/generic/caa_257.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2014 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.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <string>
+#include <vector>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct CAAImpl;
+
+class CAA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ CAA(uint8_t flags, const std::string& tag, const std::string& value);
+ CAA& operator=(const CAA& source);
+ ~CAA();
+
+ ///
+ /// Specialized methods
+ ///
+ uint8_t getFlags() const;
+ const std::string& getTag() const;
+
+private:
+ CAAImpl* constructFromLexer(MasterLexer& lexer);
+
+ CAAImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 5029681a32..46fcfb100d 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -63,6 +63,7 @@ run_unittests_SOURCES += rdata_minfo_unittest.cc
run_unittests_SOURCES += rdata_tsig_unittest.cc
run_unittests_SOURCES += rdata_naptr_unittest.cc
run_unittests_SOURCES += rdata_hinfo_unittest.cc
+run_unittests_SOURCES += rdata_caa_unittest.cc
run_unittests_SOURCES += rrset_unittest.cc
run_unittests_SOURCES += question_unittest.cc
run_unittests_SOURCES += rrparamregistry_unittest.cc
diff --git a/src/lib/dns/tests/rdata_caa_unittest.cc b/src/lib/dns/tests/rdata_caa_unittest.cc
new file mode 100644
index 0000000000..afa1c3c760
--- /dev/null
+++ b/src/lib/dns/tests/rdata_caa_unittest.cc
@@ -0,0 +1,300 @@
+// Copyright (C) 2014 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 <algorithm>
+#include <string>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <boost/algorithm/string.hpp>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+namespace {
+class Rdata_CAA_Test : public RdataTest {
+protected:
+ Rdata_CAA_Test() :
+ caa_txt("0 issue \"ca.example.net\""),
+ rdata_caa(caa_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::CAA, isc::Exception, isc::Exception>(
+ rdata_str, rdata_caa, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::CAA, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_caa, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::CAA, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_caa, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::CAA, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_caa, true, false);
+ }
+
+ const string caa_txt;
+ const generic::CAA rdata_caa;
+};
+
+const uint8_t rdata_caa_wiredata[] = {
+ // flags
+ 0x00,
+ // tag length
+ 0x5,
+ // tag
+ 'i', 's', 's', 'u', 'e',
+ // value
+ 'c', 'a', '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ '.', 'n', 'e', 't'
+};
+
+TEST_F(Rdata_CAA_Test, createFromText) {
+ // Basic test
+ checkFromText_None(caa_txt);
+
+ // With different spacing
+ checkFromText_None("0 issue \"ca.example.net\"");
+
+ // Combination of lowercase and uppercase
+ checkFromText_None("0 IssUE \"ca.example.net\"");
+
+ // string constructor throws if there's extra text,
+ // but lexer constructor doesn't
+ checkFromText_BadString(caa_txt + "\n" + caa_txt);
+
+ // Missing value field
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 issue"));
+}
+
+TEST_F(Rdata_CAA_Test, fields) {
+ // Some of these may not be RFC conformant, but we relax the check
+ // in our code to work with other field values that may show up in
+ // the future.
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("1 issue \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("2 issue \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("3 issue \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("128 issue \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("255 issue \"ca.example.net\""));
+
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 foo \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 bar \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 12345 \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 w0x1y2z3 \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 relaxed-too \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 RELAXED.too \"ca.example.net\""));
+
+ // No value
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 issue"));
+
+ // > 255 would be broken
+ EXPECT_THROW(const generic::CAA rdata_caa2("256 issue \"ca.example.net\""),
+ InvalidRdataText);
+
+ // Missing tag actually passes because it parses the value as tag
+ // and assumes that the value is empty instead.
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 \"ca.example.net\""));
+
+ // Tag is too long
+ const std::string tag(256, 'a');
+ const std::string rdata_txt("0 " + tag + " \"ca.example.net\"");
+ EXPECT_THROW(const generic::CAA rdata_caa2(rdata_txt), InvalidRdataText);
+}
+
+TEST_F(Rdata_CAA_Test, badText) {
+ checkFromText_LexerError("0");
+ checkFromText_LexerError("ZERO issue \"ca.example.net\"");
+ EXPECT_THROW(const generic::CAA rdata_caa2(caa_txt + " extra text"),
+ InvalidRdataText);
+
+ // Yes, this is redundant to the last test cases in the .fields test
+ checkFromText_InvalidText("2345 issue \"ca.example.net\"");
+
+ // negative values are trapped in the lexer rather than the
+ // constructor
+ checkFromText_LexerError("-2 issue \"ca.example.net\"");
+}
+
+TEST_F(Rdata_CAA_Test, copyAndAssign) {
+ // Copy construct
+ generic::CAA rdata_caa2(rdata_caa);
+ EXPECT_EQ(0, rdata_caa.compare(rdata_caa2));
+
+ // Assignment, mainly to confirm it doesn't cause disruption.
+ rdata_caa2 = rdata_caa;
+ EXPECT_EQ(0, rdata_caa.compare(rdata_caa2));
+}
+
+TEST_F(Rdata_CAA_Test, createFromWire) {
+ // Basic test
+ EXPECT_EQ(0, rdata_caa.compare(
+ *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire1.wire")));
+
+ // Combination of lowercase and uppercase
+ EXPECT_EQ(0, rdata_caa.compare(
+ *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire2.wire")));
+
+ // Value field is empty
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire3.wire"));
+
+ // Tag field is empty
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire4.wire"),
+ InvalidRdataText);
+
+ // Value field is shorter than rdata len
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire5"),
+ InvalidBufferPosition);
+
+ // all RDATA is missing
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire6"),
+ InvalidBufferPosition);
+}
+
+TEST_F(Rdata_CAA_Test, createFromParams) {
+ const generic::CAA rdata_caa2(0, "issue", "ca.example.net");
+ EXPECT_EQ(0, rdata_caa2.compare(rdata_caa));
+
+ // Tag is empty
+ EXPECT_THROW(const generic::CAA rdata_caa3(0, "", "ca.example.net"),
+ isc::InvalidParameter);
+
+ // Tag is too long
+ const std::string tag(256, 'a');
+ EXPECT_THROW(const generic::CAA rdata_caa3(0, tag, "ca.example.net"),
+ isc::InvalidParameter);
+
+ // Value is too long
+ const std::string value(256, 'a');
+ EXPECT_THROW(const generic::CAA rdata_caa3(0, "issue", value),
+ isc::InvalidParameter);
+}
+
+TEST_F(Rdata_CAA_Test, toText) {
+ EXPECT_TRUE(boost::iequals(caa_txt, rdata_caa.toText()));
+
+ const string caa_txt2("1 issue \"\"");
+ const generic::CAA rdata_caa2(caa_txt2);
+ EXPECT_TRUE(boost::iequals(caa_txt2, rdata_caa2.toText()));
+}
+
+TEST_F(Rdata_CAA_Test, toWire) {
+ this->obuffer.clear();
+ rdata_caa.toWire(this->obuffer);
+
+ EXPECT_EQ(sizeof (rdata_caa_wiredata),
+ this->obuffer.getLength());
+
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(),
+ this->obuffer.getLength(),
+ rdata_caa_wiredata, sizeof(rdata_caa_wiredata));
+}
+
+TEST_F(Rdata_CAA_Test, compare) {
+ const generic::CAA rdata_caa2("1 issue \"ca.example.net\"");
+
+ EXPECT_EQ(1, rdata_caa2.compare(rdata_caa));
+ EXPECT_EQ(-1, rdata_caa.compare(rdata_caa2));
+}
+
+TEST_F(Rdata_CAA_Test, getFlags) {
+ EXPECT_EQ(0, rdata_caa.getFlags());
+}
+
+TEST_F(Rdata_CAA_Test, getTag) {
+ EXPECT_EQ("issue", rdata_caa.getTag());
+}
+
+TEST_F(Rdata_CAA_Test, emptyValueFromWire) {
+ const uint8_t rdf_wiredata[] = {
+ // flags
+ 0x00,
+ // tag length
+ 0x5,
+ // tag
+ 'i', 's', 's', 'u', 'e'
+ };
+
+ const generic::CAA rdf =
+ dynamic_cast<const generic::CAA&>
+ (*rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire3.wire"));
+
+ EXPECT_EQ(0, rdf.getFlags());
+ EXPECT_EQ("issue", rdf.getTag());
+
+ this->obuffer.clear();
+ rdf.toWire(this->obuffer);
+
+ EXPECT_EQ(sizeof(rdf_wiredata), this->obuffer.getLength());
+
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(),
+ this->obuffer.getLength(),
+ rdf_wiredata, sizeof(rdf_wiredata));
+}
+
+TEST_F(Rdata_CAA_Test, emptyValueFromString) {
+ const generic::CAA rdata_caa2("0 issue");
+ const uint8_t rdata_caa2_wiredata[] = {
+ // flags
+ 0x00,
+ // tag length
+ 0x5,
+ // tag
+ 'i', 's', 's', 'u', 'e'
+ };
+
+ EXPECT_EQ(0, rdata_caa2.getFlags());
+ EXPECT_EQ("issue", rdata_caa2.getTag());
+
+ this->obuffer.clear();
+ rdata_caa2.toWire(this->obuffer);
+
+ EXPECT_EQ(sizeof(rdata_caa2_wiredata),
+ this->obuffer.getLength());
+
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(),
+ this->obuffer.getLength(),
+ rdata_caa2_wiredata, sizeof(rdata_caa2_wiredata));
+}
+}
diff --git a/src/lib/dns/tests/testdata/.gitignore b/src/lib/dns/tests/testdata/.gitignore
index a0a14f4a19..12be2c4d7a 100644
--- a/src/lib/dns/tests/testdata/.gitignore
+++ b/src/lib/dns/tests/testdata/.gitignore
@@ -105,6 +105,10 @@
/rdata_txt_fromWire3.wire
/rdata_txt_fromWire4.wire
/rdata_txt_fromWire5.wire
+/rdata_caa_fromWire1.wire
+/rdata_caa_fromWire2.wire
+/rdata_caa_fromWire3.wire
+/rdata_caa_fromWire4.wire
/rdatafields1.wire
/rdatafields2.wire
/rdatafields3.wire
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index b6d7e35ada..8ade682a76 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -64,6 +64,8 @@ BUILT_SOURCES += rdata_tsig_fromWire9.wire
BUILT_SOURCES += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
BUILT_SOURCES += rdata_tsig_toWire5.wire
+BUILT_SOURCES += rdata_caa_fromWire1.wire rdata_caa_fromWire2.wire
+BUILT_SOURCES += rdata_caa_fromWire3.wire rdata_caa_fromWire4.wire
BUILT_SOURCES += tsigrecord_toWire1.wire tsigrecord_toWire2.wire
BUILT_SOURCES += tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire
BUILT_SOURCES += tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire
@@ -167,6 +169,9 @@ EXTRA_DIST += rdata_tsig_fromWire9.spec
EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
EXTRA_DIST += rdata_tsig_toWire5.spec
+EXTRA_DIST += rdata_caa_fromWire1.spec rdata_caa_fromWire2.spec
+EXTRA_DIST += rdata_caa_fromWire3.spec rdata_caa_fromWire4.spec
+EXTRA_DIST += rdata_caa_fromWire5 rdata_caa_fromWire6
EXTRA_DIST += tsigrecord_toWire1.spec tsigrecord_toWire2.spec
EXTRA_DIST += tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec
EXTRA_DIST += tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec
new file mode 100644
index 0000000000..d21987c7eb
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# The simplest form of CAA: all default parameters
+#
+[custom]
+sections: caa
+[caa]
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec
new file mode 100644
index 0000000000..867e43d048
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec
@@ -0,0 +1,7 @@
+#
+# Mixed case CAA tag field.
+#
+[custom]
+sections: caa
+[caa]
+tag: 'ISSue'
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec
new file mode 100644
index 0000000000..9297151df0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec
@@ -0,0 +1,7 @@
+#
+# Missing CAA value field.
+#
+[custom]
+sections: caa
+[caa]
+value: ''
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec
new file mode 100644
index 0000000000..53e16b12a3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec
@@ -0,0 +1,7 @@
+#
+# Missing CAA value field.
+#
+[custom]
+sections: caa
+[caa]
+tag: ''
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire5 b/src/lib/dns/tests/testdata/rdata_caa_fromWire5
new file mode 100644
index 0000000000..123011fdb3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire5
@@ -0,0 +1,6 @@
+# Test where CAA value field is shorter than the RDATA length
+
+# CAA RDATA, RDLEN=32
+0020
+# FLAGS=0 TAG=c VALUE=ca.example.net
+00 01 63 63612e6578616d706c652e6e6574
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire6 b/src/lib/dns/tests/testdata/rdata_caa_fromWire6
new file mode 100644
index 0000000000..1a35a1aef7
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire6
@@ -0,0 +1,4 @@
+# Test where RDATA is completely missing
+
+# CAA RDATA, RDLEN=32
+0020
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
index b3858b6b26..b2b6d206cc 100755
--- a/src/lib/util/python/gen_wiredata.py.in
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -355,7 +355,7 @@ dict_rrtype = { 'none' : 0, 'a' : 1, 'ns' : 2, 'md' : 3, 'mf' : 4, 'cname' : 5,
'dhcid' : 49, 'nsec3' : 50, 'nsec3param' : 51, 'hip' : 55,
'spf' : 99, 'unspec' : 103, 'tkey' : 249, 'tsig' : 250,
'dlv' : 32769, 'ixfr' : 251, 'axfr' : 252, 'mailb' : 253,
- 'maila' : 254, 'any' : 255 }
+ 'maila' : 254, 'any' : 255, 'caa' : 257 }
rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
@@ -893,6 +893,32 @@ class AFSDB(RR):
f.write('# SUBTYPE=%d SERVER=%s\n' % (self.subtype, self.server))
f.write('%04x %s\n' % (self.subtype, server_wire))
+class CAA(RR):
+ '''Implements rendering CAA RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - flags (int): The flags field.
+ - tag (string): The tag field.
+ - value (string): The value field.
+ '''
+ flags = 0
+ tag = 'issue'
+ value = 'ca.example.net'
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 1 + 1 + len(self.tag) + len(self.value)
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# FLAGS=%d TAG=%s VALUE=%s\n' % \
+ (self.flags, self.tag, self.value))
+ f.write('%02x %02x ' % \
+ (self.flags, len(self.tag)))
+ f.write(encode_string(self.tag))
+ f.write(encode_string(self.value))
+ f.write('\n')
+
class DNSKEY(RR):
'''Implements rendering DNSKEY RDATA in the test data format.