BIND 10 master, updated. 018edab669b4fa2dc12b7285d57e35f33e48a7f8 [master] Add ChangeLog for #2512

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Feb 19 14:37:51 UTC 2014


The branch, master has been updated
       via  018edab669b4fa2dc12b7285d57e35f33e48a7f8 (commit)
       via  2d33f5b862022d599017430083fe814678258643 (commit)
       via  0c6ca2779a9152db8f4d43a88faf382380769961 (commit)
       via  fd501b4f32b8d909b8f833876bc762a674d42c91 (commit)
       via  9b9a1771a28d416d7c5e954ef41799b6d9c02959 (commit)
       via  8d831e948945c913ac59424fa549fd7e2f18b117 (commit)
       via  5b61be1e6e567272b7bc372db457e2d1db1ddd49 (commit)
       via  8c77a05c8564a26113933d9cba6d5019cf1e75eb (commit)
       via  a6a6cb2e33f807593e6d457bfc822a9c31ef211e (commit)
       via  3c09cb5da05b2439201dc01b351f5003f1dd8557 (commit)
       via  a5d2806bff9656a3ea7900c8dba4101bf3c86ca8 (commit)
       via  7606f0759b4ae96e49edad0e46316d8d1784aaff (commit)
       via  cb1407f6e846eaa57940be4d13ff03d25c1728c8 (commit)
       via  79defea75c875181597598e880ee20af3016167f (commit)
       via  11052a822a6c329a087e5590ac525ab3107926a8 (commit)
       via  969a916daeffe16ba2c04f3f1eead285284b3162 (commit)
       via  e48ae4647c1dfbc827d3e4538e9f35dd1d3c96f9 (commit)
       via  2e80ea0623fc05edc4f6088b024f9ea3cd168dff (commit)
      from  affd720719e9a25eac1ec2e91c7c27f0fe28bd67 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 018edab669b4fa2dc12b7285d57e35f33e48a7f8
Author: Mukund Sivaraman <muks at isc.org>
Date:   Wed Feb 19 19:35:50 2014 +0530

    [master] Add ChangeLog for #2512

commit 2d33f5b862022d599017430083fe814678258643
Merge: affd720 0c6ca27
Author: Mukund Sivaraman <muks at isc.org>
Date:   Wed Feb 19 19:57:30 2014 +0530

    Merge branch 'trac2512'

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                          |    4 +
 src/lib/dns/Makefile.am                            |    2 +
 src/lib/dns/gen-rdatacode.py.in                    |    2 +-
 src/lib/dns/rdata/generic/caa_257.cc               |  306 ++++++++++++++++++
 src/lib/dns/rdata/generic/{tlsa_52.h => caa_257.h} |   33 +-
 src/lib/dns/rdata/generic/detail/char_string.cc    |   84 +++++
 src/lib/dns/rdata/generic/detail/char_string.h     |   37 +++
 src/lib/dns/tests/Makefile.am                      |    2 +
 src/lib/dns/tests/rdata_caa_unittest.cc            |  327 ++++++++++++++++++++
 .../dns/tests/rdata_char_string_data_unittest.cc   |  187 +++++++++++
 src/lib/dns/tests/testdata/.gitignore              |    4 +
 src/lib/dns/tests/testdata/Makefile.am             |    5 +
 .../dns/tests/testdata/rdata_caa_fromWire1.spec    |    6 +
 .../dns/tests/testdata/rdata_caa_fromWire2.spec    |    7 +
 .../dns/tests/testdata/rdata_caa_fromWire3.spec    |    7 +
 .../dns/tests/testdata/rdata_caa_fromWire4.spec    |    7 +
 src/lib/dns/tests/testdata/rdata_caa_fromWire5     |    6 +
 ...{rdata_sshfp_fromWire11 => rdata_caa_fromWire6} |    2 +-
 src/lib/util/python/gen_wiredata.py.in             |   28 +-
 19 files changed, 1040 insertions(+), 16 deletions(-)
 create mode 100644 src/lib/dns/rdata/generic/caa_257.cc
 copy src/lib/dns/rdata/generic/{tlsa_52.h => caa_257.h} (63%)
 create mode 100644 src/lib/dns/tests/rdata_caa_unittest.cc
 create mode 100644 src/lib/dns/tests/rdata_char_string_data_unittest.cc
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_caa_fromWire5
 copy src/lib/dns/tests/testdata/{rdata_sshfp_fromWire11 => rdata_caa_fromWire6} (66%)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 626850b..e6f1023 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+755.	[func]		muks
+	Add support for the CAA RR type (RFC 6844).
+	(Trac #2512, git 39162608985e5c904448f308951c73bb9c32da8f)
+
 754.	[func]		muks
 	Add support for the TLSA RR type (RFC 6698).
 	(Trac #2185, git a168170430f6927f28597b2a6debebe31cf39b13)
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 2c57b5d..7691d31 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -78,6 +78,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 0c5bdf4..1cb1e37 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', '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 0000000..7d46a57
--- /dev/null
+++ b/src/lib/dns/rdata/generic/caa_257.cc
@@ -0,0 +1,306 @@
+// 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 detail::CharStringData& value) :
+        flags_(flags),
+        tag_(tag),
+        value_(value)
+    {
+        if ((sizeof(flags) + 1 + tag_.size() + value_.size()) > 65535) {
+            isc_throw(InvalidRdataLength,
+                      "CAA Value field is too large: " << value_.size());
+        }
+    }
+
+    uint8_t flags_;
+    const std::string tag_;
+    const detail::CharStringData 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.
+    detail::CharStringData value;
+    MasterToken token = lexer.getNextToken(MasterToken::QSTRING, true);
+    if ((token.getType() != MasterToken::END_OF_FILE) &&
+        (token.getType() != MasterToken::END_OF_LINE))
+    {
+        detail::stringToCharStringData(token.getStringRegion(), value);
+    }
+
+    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");
+    }
+
+    std::vector<uint8_t> tag_vec(tag_length);
+    buffer.readData(&tag_vec[0], tag_length);
+    std::string tag(tag_vec.begin(), tag_vec.end());
+    rdata_len -= tag_length;
+
+    detail::CharStringData value;
+    value.resize(rdata_len);
+    if (rdata_len > 0) {
+        buffer.readData(&value[0], 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());
+    }
+
+    MasterToken::StringRegion region;
+    region.beg = &value[0]; // note std ensures this works even if str is empty
+    region.len = value.size();
+
+    detail::CharStringData value_vec;
+    detail::stringToCharStringData(region, value_vec);
+
+    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_);
+
+    // The constructors must ensure that the tag field is not empty.
+    assert(!impl_->tag_.empty());
+    buffer.writeUint8(impl_->tag_.size());
+    buffer.writeData(&impl_->tag_[0], impl_->tag_.size());
+
+    if (!impl_->value_.empty()) {
+        buffer.writeData(&impl_->value_[0],
+                         impl_->value_.size());
+    }
+}
+
+void
+CAA::toWire(AbstractMessageRenderer& renderer) const {
+    renderer.writeUint8(impl_->flags_);
+
+    // The constructors must ensure that the tag field is not empty.
+    assert(!impl_->tag_.empty());
+    renderer.writeUint8(impl_->tag_.size());
+    renderer.writeData(&impl_->tag_[0], impl_->tag_.size());
+
+    if (!impl_->value_.empty()) {
+        renderer.writeData(&impl_->value_[0],
+                           impl_->value_.size());
+    }
+}
+
+std::string
+CAA::toText() const {
+    std::string result;
+
+    result = lexical_cast<std::string>(static_cast<int>(impl_->flags_));
+    result += " " + impl_->tag_;
+    result += " \"" + detail::charStringDataToString(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::compareCharStringDatas(impl_->value_,
+                                           other_caa.impl_->value_));
+}
+
+uint8_t
+CAA::getFlags() const {
+    return (impl_->flags_);
+}
+
+const std::string&
+CAA::getTag() const {
+    return (impl_->tag_);
+}
+
+const std::vector<uint8_t>&
+CAA::getValue() const {
+    return (impl_->value_);
+}
+
+// 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 0000000..47a1369
--- /dev/null
+++ b/src/lib/dns/rdata/generic/caa_257.h
@@ -0,0 +1,72 @@
+// 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
+    ///
+
+    /// \brief Return the Flags field of the CAA RDATA.
+    uint8_t getFlags() const;
+
+    /// \brief Return the Tag field of the CAA RDATA.
+    const std::string& getTag() const;
+
+    /// \brief Return the Value field of the CAA RDATA.
+    ///
+    /// Note: The const reference which is returned is valid only during
+    /// the lifetime of this \c generic::CAA object. It should not be
+    /// used afterwards.
+    const std::vector<uint8_t>& getValue() 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/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
index 4c8965a..328fa7b 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.cc
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -93,6 +93,39 @@ stringToCharString(const MasterToken::StringRegion& str_region,
     result[0] = result.size() - 1;
 }
 
+void
+stringToCharStringData(const MasterToken::StringRegion& str_region,
+                       CharStringData& result)
+{
+    bool escape = false;
+    const char* s = str_region.beg;
+    const char* const s_end = str_region.beg + str_region.len;
+
+    for (size_t n = str_region.len; n != 0; --n, ++s) {
+        int c = (*s & 0xff);
+        if (escape && std::isdigit(c) != 0) {
+            c = decimalToNumber(s, s_end);
+            // decimalToNumber() already throws if (s_end - s) is less
+            // than 3, so the following assertion is unnecessary. But we
+            // assert it anyway. 'n' is an unsigned type (size_t) and
+            // can underflow.
+            assert(n >= 3);
+            // 'n' and 's' are also updated by 1 in the for statement's
+            // expression, so we update them by 2 instead of 3 here.
+            n -= 2;
+            s += 2;
+        } else if (!escape && c == '\\') {
+            escape = true;
+            continue;
+        }
+        escape = false;
+        result.push_back(c);
+    }
+    if (escape) {               // terminated by non-escaped '\'
+        isc_throw(InvalidRdataText, "character-string ends with '\\'");
+    }
+}
+
 std::string
 charStringToString(const CharString& char_string) {
     std::string s;
@@ -116,6 +149,29 @@ charStringToString(const CharString& char_string) {
     return (s);
 }
 
+std::string
+charStringDataToString(const CharStringData& char_string) {
+    std::string s;
+    for (CharString::const_iterator it = char_string.begin();
+         it != char_string.end(); ++it) {
+        const uint8_t ch = *it;
+        if ((ch < 0x20) || (ch >= 0x7f)) {
+            // convert to escaped \xxx (decimal) format
+            s.push_back('\\');
+            s.push_back('0' + ((ch / 100) % 10));
+            s.push_back('0' + ((ch / 10) % 10));
+            s.push_back('0' + (ch % 10));
+            continue;
+        }
+        if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+            s.push_back('\\');
+        }
+        s.push_back(ch);
+    }
+
+    return (s);
+}
+
 int compareCharStrings(const detail::CharString& self,
                        const detail::CharString& other) {
     if (self.size() == 0 && other.size() == 0) {
@@ -144,6 +200,34 @@ int compareCharStrings(const detail::CharString& self,
     }
 }
 
+int compareCharStringDatas(const detail::CharStringData& self,
+                           const detail::CharStringData& other) {
+    if (self.size() == 0 && other.size() == 0) {
+        return (0);
+    }
+    if (self.size() == 0) {
+        return (-1);
+    }
+    if (other.size() == 0) {
+        return (1);
+    }
+    const size_t self_len = self.size();
+    const size_t other_len = other.size();
+    const size_t cmp_len = std::min(self_len, other_len);
+    const int cmp = std::memcmp(&self[0], &other[0], cmp_len);
+    if (cmp < 0) {
+        return (-1);
+    } else if (cmp > 0) {
+        return (1);
+    } else if (self_len < other_len) {
+        return (-1);
+    } else if (self_len > other_len) {
+        return (1);
+    } else {
+        return (0);
+    }
+}
+
 size_t
 bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
                    CharString& target) {
diff --git a/src/lib/dns/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
index 8e3e294..01fccad 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.h
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -34,6 +34,9 @@ namespace detail {
 /// be the bare char basis.
 typedef std::vector<uint8_t> CharString;
 
+/// \brief Type for DNS character string without the length prefix.
+typedef std::vector<uint8_t> CharStringData;
+
 /// \brief Convert a DNS character-string into corresponding binary data.
 ///
 /// This helper function takes a string object that is expected to be a
@@ -53,6 +56,20 @@ typedef std::vector<uint8_t> CharString;
 void stringToCharString(const MasterToken::StringRegion& str_region,
                         CharString& result);
 
+/// \brief Convert a DNS character-string into corresponding binary data.
+///
+/// This method functions similar to \c stringToCharString() except it
+/// does not include the 1-octet length prefix in the \c result, and the
+/// result is not limited to MAX_CHARSTRING_LEN octets.
+///
+/// \throw InvalidRdataText Upon syntax errors.
+///
+/// \brief str_region A string that represents a character-string.
+/// \brief result A placeholder vector where the resulting data are to be
+/// stored.  Expected to be empty, but it's not checked.
+void stringToCharStringData(const MasterToken::StringRegion& str_region,
+                            CharStringData& result);
+
 /// \brief Convert a CharString into a textual DNS character-string.
 ///
 /// This method converts a binary 8-bit representation of a DNS
@@ -67,6 +84,15 @@ void stringToCharString(const MasterToken::StringRegion& str_region,
 /// \return A string representation of \c char_string.
 std::string charStringToString(const CharString& char_string);
 
+/// \brief Convert a CharStringData into a textual DNS character-string.
+///
+/// Reverse of \c stringToCharStringData(). See \c stringToCharString()
+/// vs. \c stringToCharStringData().
+///
+/// \param char_string The \c CharStringData to convert.
+/// \return A string representation of \c char_string.
+std::string charStringDataToString(const CharStringData& char_string);
+
 /// \brief Compare two CharString objects
 ///
 /// \param self The CharString field to compare
@@ -77,6 +103,17 @@ std::string charStringToString(const CharString& char_string);
 ///          0 if \c self and \c other are equal
 int compareCharStrings(const CharString& self, const CharString& other);
 
+/// \brief Compare two CharStringData objects
+///
+/// \param self The CharStringData field to compare
+/// \param other The CharStringData field to compare to
+///
+/// \return -1 if \c self would be sorted before \c other
+///          1 if \c self would be sorted after \c other
+///          0 if \c self and \c other are equal
+int compareCharStringDatas(const CharStringData& self,
+                           const CharStringData& other);
+
 /// \brief Convert a buffer containing a character-string to CharString
 ///
 /// This method reads one character-string from the given buffer (in wire
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 0b8b70d..415700a 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -41,6 +41,7 @@ run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
 run_unittests_SOURCES += rdatafields_unittest.cc
 run_unittests_SOURCES += rdata_pimpl_holder_unittest.cc
 run_unittests_SOURCES += rdata_char_string_unittest.cc
+run_unittests_SOURCES += rdata_char_string_data_unittest.cc
 run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
 run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
 run_unittests_SOURCES += rdata_txt_like_unittest.cc
@@ -66,6 +67,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 0000000..b88aba3
--- /dev/null
+++ b/src/lib/dns/tests/rdata_caa_unittest.cc
@@ -0,0 +1,327 @@
+// 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 <util/unittests/wiredata.h>
+
+#include <boost/algorithm/string.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+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 (this is redundant to the last test case in the
+    // .createFromText test
+    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, characterStringValue) {
+    const generic::CAA rdata_caa_unquoted("0 issue ca.example.net");
+    EXPECT_EQ(0, rdata_caa_unquoted.compare(rdata_caa));
+
+    const generic::CAA rdata_caa_escape_X("0 issue ca.e\\xample.net");
+    EXPECT_EQ(0, rdata_caa_escape_X.compare(rdata_caa));
+
+    const generic::CAA rdata_caa_escape_DDD("0 issue ca.e\\120ample.net");
+    EXPECT_EQ(0, rdata_caa_escape_DDD.compare(rdata_caa));
+
+    const generic::CAA rdata_caa_multiline("0 issue (\nca.example.net)");
+    EXPECT_EQ(0, rdata_caa_multiline.compare(rdata_caa));
+}
+
+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));
+
+    const generic::CAA rdata_caa4(0, "issue", "ca.e\\xample.net");
+    EXPECT_EQ(0, rdata_caa4.compare(rdata_caa));
+
+    const generic::CAA rdata_caa5(0, "issue", "ca.e\\120ample.net");
+    EXPECT_EQ(0, rdata_caa5.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(65536, 'a');
+    EXPECT_THROW(const generic::CAA rdata_caa3(0, "issue", value),
+                 InvalidRdataLength);
+}
+
+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) {
+    obuffer.clear();
+    rdata_caa.toWire(obuffer);
+
+    matchWireData(rdata_caa_wiredata, sizeof(rdata_caa_wiredata),
+                  obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_CAA_Test, compare) {
+    // Equality test is repeated from createFromWire tests above.
+    EXPECT_EQ(0, rdata_caa.compare(
+                  *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+                                        "rdata_caa_fromWire1.wire")));
+
+    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, getValue) {
+    const uint8_t value_data[] = {
+        'c', 'a', '.',
+        'e', 'x', 'a', 'm', 'p', 'l', 'e', '.',
+        'n', 'e', 't'
+    };
+
+    const std::vector<uint8_t>& value = rdata_caa.getValue();
+    matchWireData(value_data, sizeof(value_data),
+                  &value[0], value.size());
+}
+
+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());
+
+    obuffer.clear();
+    rdf.toWire(obuffer);
+
+    matchWireData(rdf_wiredata, sizeof(rdf_wiredata),
+                  obuffer.getData(), obuffer.getLength());
+}
+
+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());
+
+    obuffer.clear();
+    rdata_caa2.toWire(obuffer);
+
+    matchWireData(rdata_caa2_wiredata, sizeof(rdata_caa2_wiredata),
+                  obuffer.getData(), obuffer.getLength());
+}
+}
diff --git a/src/lib/dns/tests/rdata_char_string_data_unittest.cc b/src/lib/dns/tests/rdata_char_string_data_unittest.cc
new file mode 100644
index 0000000..88a00a9
--- /dev/null
+++ b/src/lib/dns/tests/rdata_char_string_data_unittest.cc
@@ -0,0 +1,187 @@
+// 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 <util/unittests/wiredata.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::dns::rdata::generic::detail::CharStringData;
+using isc::dns::rdata::generic::detail::stringToCharStringData;
+using isc::dns::rdata::generic::detail::charStringDataToString;
+using isc::dns::rdata::generic::detail::compareCharStringDatas;
+using isc::util::unittests::matchWireData;
+
+namespace {
+const uint8_t test_charstr[] = {
+    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+class CharStringDataTest : public ::testing::Test {
+protected:
+    CharStringDataTest() :
+        // char-string representation for test data using two types of escape
+        // ('r' = 114)
+        test_str("Test\\ St\\114ing")
+    {
+        str_region.beg = &test_str[0];
+        str_region.len = test_str.size();
+    }
+    CharStringData chstr;           // place holder
+    const std::string test_str;
+    MasterToken::StringRegion str_region;
+};
+
+MasterToken::StringRegion
+createStringRegion(const std::string& str) {
+    MasterToken::StringRegion region;
+    region.beg = &str[0]; // note std ensures this works even if str is empty
+    region.len = str.size();
+    return (region);
+}
+
+TEST_F(CharStringDataTest, normalConversion) {
+    uint8_t tmp[3];             // placeholder for expected sequence
+
+    stringToCharStringData(str_region, chstr);
+    matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
+
+    // Empty string
+    chstr.clear();
+    stringToCharStringData(createStringRegion(""), chstr);
+    EXPECT_TRUE(chstr.empty());
+
+    // Possible largest char string
+    chstr.clear();
+    std::string long_str(255, 'x');
+    stringToCharStringData(createStringRegion(long_str), chstr);
+    std::vector<uint8_t> expected;
+    expected.insert(expected.end(), long_str.begin(), long_str.end());
+    matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+    // Escaped '\'
+    chstr.clear();
+    tmp[0] = '\\';
+    stringToCharStringData(createStringRegion("\\\\"), chstr);
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    // Boundary values for \DDD
+    chstr.clear();
+    tmp[0] = 0;
+    stringToCharStringData(createStringRegion("\\000"), chstr);
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    chstr.clear();
+    stringToCharStringData(createStringRegion("\\255"), chstr);
+    tmp[0] = 255;
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    // Another digit follows DDD; it shouldn't cause confusion
+    chstr.clear();
+    stringToCharStringData(createStringRegion("\\2550"), chstr);
+    tmp[1] = '0';
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+}
+
+TEST_F(CharStringDataTest, badConversion) {
+    // input string ending with (non escaped) '\'
+    chstr.clear();
+    EXPECT_THROW(stringToCharStringData(createStringRegion("foo\\"), chstr),
+                 InvalidRdataText);
+}
+
+TEST_F(CharStringDataTest, badDDD) {
+    // Check various type of bad form of \DDD
+
+    // Not a number
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\1a2"), chstr),
+                 InvalidRdataText);
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\12a"), chstr),
+                 InvalidRdataText);
+
+    // Not in the range of uint8_t
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\256"), chstr),
+                 InvalidRdataText);
+
+    // Short buffer
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\42"), chstr),
+                 InvalidRdataText);
+}
+
+const struct TestData {
+    const char *data;
+    const char *expected;
+} conversion_data[] = {
+    {"Test\"Test", "Test\\\"Test"},
+    {"Test;Test", "Test\\;Test"},
+    {"Test\\Test", "Test\\\\Test"},
+    {"Test\x1fTest", "Test\\031Test"},
+    {"Test ~ Test", "Test ~ Test"},
+    {"Test\x7fTest", "Test\\127Test"},
+    {NULL, NULL}
+};
+
+TEST_F(CharStringDataTest, charStringDataToString) {
+    for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+        uint8_t idata[32];
+        size_t length = std::strlen(cur->data);
+        ASSERT_LT(length, sizeof(idata));
+        std::memcpy(idata, cur->data, length);
+        const CharStringData test_data(idata, idata + length);
+        EXPECT_EQ(cur->expected, charStringDataToString(test_data));
+    }
+}
+
+TEST_F(CharStringDataTest, compareCharStringData) {
+    CharStringData charstr;
+    CharStringData charstr2;
+    CharStringData charstr_small1;
+    CharStringData charstr_small2;
+    CharStringData charstr_large1;
+    CharStringData charstr_large2;
+    CharStringData charstr_empty;
+
+    stringToCharStringData(createStringRegion("test string"), charstr);
+    stringToCharStringData(createStringRegion("test string"), charstr2);
+    stringToCharStringData(createStringRegion("test strin"), charstr_small1);
+    stringToCharStringData(createStringRegion("test strina"), charstr_small2);
+    stringToCharStringData(createStringRegion("test stringa"), charstr_large1);
+    stringToCharStringData(createStringRegion("test strinz"), charstr_large2);
+
+    EXPECT_EQ(0, compareCharStringDatas(charstr, charstr2));
+    EXPECT_EQ(0, compareCharStringDatas(charstr2, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small1));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small2));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large1));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large2));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_small1, charstr));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_small2, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr_large1, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr_large2, charstr));
+
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_empty, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_empty));
+    EXPECT_EQ(0, compareCharStringDatas(charstr_empty, charstr_empty));
+}
+
+} // unnamed namespace
diff --git a/src/lib/dns/tests/testdata/.gitignore b/src/lib/dns/tests/testdata/.gitignore
index 8079354..f18ac2f 100644
--- a/src/lib/dns/tests/testdata/.gitignore
+++ b/src/lib/dns/tests/testdata/.gitignore
@@ -111,6 +111,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 15c3b3e..3d10337 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -67,6 +67,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
@@ -176,6 +178,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 0000000..d21987c
--- /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 0000000..867e43d
--- /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 0000000..9297151
--- /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 0000000..53e16b1
--- /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 0000000..123011f
--- /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 0000000..1a35a1a
--- /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 1251f92..76e2a5d 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, 'tlsa' : 52, '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.
 



More information about the bind10-changes mailing list