diff options
-rw-r--r-- | src/lib/dns/Makefile.am | 2 | ||||
-rwxr-xr-x | src/lib/dns/gen-rdatacode.py.in | 2 | ||||
-rw-r--r-- | src/lib/dns/rdata/generic/caa_257.cc | 313 | ||||
-rw-r--r-- | src/lib/dns/rdata/generic/caa_257.h | 61 | ||||
-rw-r--r-- | src/lib/dns/tests/Makefile.am | 1 | ||||
-rw-r--r-- | src/lib/dns/tests/rdata_caa_unittest.cc | 300 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/.gitignore | 4 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/Makefile.am | 5 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec | 6 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec | 7 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec | 7 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec | 7 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/rdata_caa_fromWire5 | 6 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/rdata_caa_fromWire6 | 4 | ||||
-rwxr-xr-x | src/lib/util/python/gen_wiredata.py.in | 28 |
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. |