Browse Source

Merge branch 'trac2185'

Mukund Sivaraman 11 years ago
parent
commit
a168170430

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

@@ -70,6 +70,8 @@ EXTRA_DIST += rdata/generic/spf_99.cc
 EXTRA_DIST += rdata/generic/spf_99.h
 EXTRA_DIST += rdata/generic/sshfp_44.cc
 EXTRA_DIST += rdata/generic/sshfp_44.h
+EXTRA_DIST += rdata/generic/tlsa_52.cc
+EXTRA_DIST += rdata/generic/tlsa_52.h
 EXTRA_DIST += rdata/generic/txt_16.cc
 EXTRA_DIST += rdata/generic/txt_16.h
 EXTRA_DIST += rdata/generic/minfo_14.cc
@@ -135,6 +137,7 @@ libb10_dns___la_SOURCES += master_loader.h
 libb10_dns___la_SOURCES += rrset_collection_base.h
 libb10_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
 libb10_dns___la_SOURCES += zone_checker.h zone_checker.cc
+libb10_dns___la_SOURCES += rdata_pimpl_holder.h
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h

+ 1 - 1
src/lib/dns/gen-rdatacode.py.in

@@ -41,7 +41,7 @@ meta_types = {
     '10': 'null', '11': 'wks', '19': 'x25', '21': 'rt', '22': 'nsap',
     '23': 'nsap-ptr', '24': 'sig', '20': 'isdn', '25': 'key', '26': 'px',
     '27': 'gpos', '29': 'loc', '36': 'kx', '37': 'cert', '42': 'apl',
-    '45': 'ipseckey', '52': 'tlsa', '55': 'hip', '103': 'unspec',
+    '45': 'ipseckey', '55': 'hip', '103': 'unspec',
     '104': 'nid', '105': 'l32', '106': 'l64', '107': 'lp', '249':  'tkey',
     '253': 'mailb', '256': 'uri', '257': 'caa'
     }

+ 350 - 0
src/lib/dns/rdata/generic/tlsa_52.cc

@@ -0,0 +1,350 @@
+// 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 <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata_pimpl_holder.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct TLSAImpl {
+    // straightforward representation of TLSA RDATA fields
+    TLSAImpl(uint8_t certificate_usage, uint8_t selector,
+             uint8_t matching_type, const vector<uint8_t>& data) :
+        certificate_usage_(certificate_usage),
+        selector_(selector),
+        matching_type_(matching_type),
+        data_(data)
+    {}
+
+    uint8_t certificate_usage_;
+    uint8_t selector_;
+    uint8_t matching_type_;
+    const vector<uint8_t> data_;
+};
+
+// helper function for string and lexer constructors
+TLSAImpl*
+TLSA::constructFromLexer(MasterLexer& lexer) {
+    const uint32_t certificate_usage =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    if (certificate_usage > 255) {
+        isc_throw(InvalidRdataText,
+                  "TLSA certificate usage field out of range");
+    }
+
+    const uint32_t selector =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    if (selector > 255) {
+        isc_throw(InvalidRdataText,
+                  "TLSA selector field out of range");
+    }
+
+    const uint32_t matching_type =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    if (matching_type > 255) {
+        isc_throw(InvalidRdataText,
+                  "TLSA matching type field out of range");
+    }
+
+    std::string certificate_assoc_data;
+    std::string data_substr;
+    while (true) {
+        const MasterToken& token =
+            lexer.getNextToken(MasterToken::STRING, true);
+        if ((token.getType() == MasterToken::END_OF_FILE) ||
+            (token.getType() == MasterToken::END_OF_LINE)) {
+            break;
+        }
+
+        token.getString(data_substr);
+        certificate_assoc_data.append(data_substr);
+    }
+    lexer.ungetToken();
+
+    if (certificate_assoc_data.empty()) {
+        isc_throw(InvalidRdataText, "Empty TLSA certificate association data");
+    }
+
+    vector<uint8_t> data;
+    try {
+        decodeHex(certificate_assoc_data, data);
+    } catch (const isc::BadValue& e) {
+        isc_throw(InvalidRdataText,
+                  "Bad TLSA certificate association data: " << e.what());
+    }
+
+    return (new TLSAImpl(certificate_usage, selector, matching_type, data));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid TLSA 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 Certificate Usage, Selector and Matching Type fields must be
+/// within their valid ranges, but are not constrained to the values
+/// defined in RFC6698.
+///
+/// The Certificate Association Data Field field may be absent, but if
+/// present it must contain a valid hex encoding of the data. Whitespace
+/// is allowed in the hex text.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their
+/// valid ranges, or are incorrect, or Certificate Association Data is
+/// not a valid hex string.
+///
+/// \param tlsa_str A string containing the RDATA to be created
+TLSA::TLSA(const string& tlsa_str) :
+    impl_(NULL)
+{
+    // We use a smart pointer here because if there is an exception in
+    // this constructor, the destructor is not called and there could be
+    // a leak of the TLSAImpl that constructFromLexer() returns.
+    RdataPimplHolder<TLSAImpl> impl_ptr;
+
+    try {
+        std::istringstream ss(tlsa_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 TLSA: "
+                      << tlsa_str);
+        }
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataText, "Failed to construct TLSA from '" <<
+                  tlsa_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 TLSA RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range, or are
+/// incorrect, or Certificate Association Data is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+TLSA::TLSA(MasterLexer& lexer, const Name*,
+             MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid TLSA RDATA.
+///
+/// The Certificate Usage, Selector and Matching Type fields must be
+/// within their valid ranges, but are not constrained to the values
+/// defined in RFC6698. It is okay for the certificate association data
+/// to be missing (see the description of the constructor from string).
+TLSA::TLSA(InputBuffer& buffer, size_t rdata_len) {
+    if (rdata_len < 3) {
+        isc_throw(InvalidRdataLength, "TLSA record too short");
+    }
+
+    const uint8_t certificate_usage = buffer.readUint8();
+    const uint8_t selector = buffer.readUint8();
+    const uint8_t matching_type = buffer.readUint8();
+
+    vector<uint8_t> data;
+    rdata_len -= 3;
+
+    if (rdata_len == 0) {
+        isc_throw(InvalidRdataLength,
+                  "Empty TLSA certificate association data");
+    }
+
+    data.resize(rdata_len);
+    buffer.readData(&data[0], rdata_len);
+
+    impl_ = new TLSAImpl(certificate_usage, selector, matching_type, data);
+}
+
+TLSA::TLSA(uint8_t certificate_usage, uint8_t selector,
+           uint8_t matching_type, const std::string& certificate_assoc_data) :
+    impl_(NULL)
+{
+    if (certificate_assoc_data.empty()) {
+        isc_throw(InvalidRdataText, "Empty TLSA certificate association data");
+    }
+
+    vector<uint8_t> data;
+    try {
+        decodeHex(certificate_assoc_data, data);
+    } catch (const isc::BadValue& e) {
+        isc_throw(InvalidRdataText,
+                  "Bad TLSA certificate association data: " << e.what());
+    }
+
+    impl_ = new TLSAImpl(certificate_usage, selector, matching_type, data);
+}
+
+TLSA::TLSA(const TLSA& other) :
+        Rdata(), impl_(new TLSAImpl(*other.impl_))
+{}
+
+TLSA&
+TLSA::operator=(const TLSA& source) {
+    if (this == &source) {
+        return (*this);
+    }
+
+    TLSAImpl* newimpl = new TLSAImpl(*source.impl_);
+    delete impl_;
+    impl_ = newimpl;
+
+    return (*this);
+}
+
+TLSA::~TLSA() {
+    delete impl_;
+}
+
+void
+TLSA::toWire(OutputBuffer& buffer) const {
+    buffer.writeUint8(impl_->certificate_usage_);
+    buffer.writeUint8(impl_->selector_);
+    buffer.writeUint8(impl_->matching_type_);
+
+    // The constructors must ensure that the certificate association
+    // data field is not empty.
+    assert(!impl_->data_.empty());
+    buffer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+void
+TLSA::toWire(AbstractMessageRenderer& renderer) const {
+    renderer.writeUint8(impl_->certificate_usage_);
+    renderer.writeUint8(impl_->selector_);
+    renderer.writeUint8(impl_->matching_type_);
+
+    // The constructors must ensure that the certificate association
+    // data field is not empty.
+    assert(!impl_->data_.empty());
+    renderer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+string
+TLSA::toText() const {
+    // The constructors must ensure that the certificate association
+    // data field is not empty.
+    assert(!impl_->data_.empty());
+
+    return (lexical_cast<string>(static_cast<int>(impl_->certificate_usage_)) + " " +
+            lexical_cast<string>(static_cast<int>(impl_->selector_)) + " " +
+            lexical_cast<string>(static_cast<int>(impl_->matching_type_)) + " " +
+            encodeHex(impl_->data_));
+}
+
+int
+TLSA::compare(const Rdata& other) const {
+    const TLSA& other_tlsa = dynamic_cast<const TLSA&>(other);
+
+    if (impl_->certificate_usage_ < other_tlsa.impl_->certificate_usage_) {
+        return (-1);
+    } else if (impl_->certificate_usage_ >
+               other_tlsa.impl_->certificate_usage_) {
+        return (1);
+    }
+
+    if (impl_->selector_ < other_tlsa.impl_->selector_) {
+        return (-1);
+    } else if (impl_->selector_ > other_tlsa.impl_->selector_) {
+        return (1);
+    }
+
+    if (impl_->matching_type_ < other_tlsa.impl_->matching_type_) {
+        return (-1);
+    } else if (impl_->matching_type_ >
+               other_tlsa.impl_->matching_type_) {
+        return (1);
+    }
+
+    const size_t this_len = impl_->data_.size();
+    const size_t other_len = other_tlsa.impl_->data_.size();
+    const size_t cmplen = min(this_len, other_len);
+
+    if (cmplen > 0) {
+        const int cmp = memcmp(&impl_->data_[0],
+                               &other_tlsa.impl_->data_[0],
+                               cmplen);
+        if (cmp != 0) {
+            return (cmp);
+        }
+    }
+
+    if (this_len == other_len) {
+        return (0);
+    } else if (this_len < other_len) {
+        return (-1);
+    } else {
+        return (1);
+    }
+}
+
+uint8_t
+TLSA::getCertificateUsage() const {
+    return (impl_->certificate_usage_);
+}
+
+uint8_t
+TLSA::getSelector() const {
+    return (impl_->selector_);
+}
+
+uint8_t
+TLSA::getMatchingType() const {
+    return (impl_->matching_type_);
+}
+
+const std::vector<uint8_t>&
+TLSA::getData() const {
+    return (impl_->data_);
+}
+
+size_t
+TLSA::getDataLength() const {
+    return (impl_->data_.size());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE

+ 65 - 0
src/lib/dns/rdata/generic/tlsa_52.h

@@ -0,0 +1,65 @@
+// 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 TLSAImpl;
+
+class TLSA : public Rdata {
+public:
+    // BEGIN_COMMON_MEMBERS
+    // END_COMMON_MEMBERS
+
+    TLSA(uint8_t certificate_usage, uint8_t selector,
+         uint8_t matching_type, const std::string& certificate_assoc_data);
+    TLSA& operator=(const TLSA& source);
+    ~TLSA();
+
+    ///
+    /// Specialized methods
+    ///
+    uint8_t getCertificateUsage() const;
+    uint8_t getSelector() const;
+    uint8_t getMatchingType() const;
+    const std::vector<uint8_t>& getData() const;
+    size_t getDataLength() const;
+
+private:
+    TLSAImpl* constructFromLexer(MasterLexer& lexer);
+
+    TLSAImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:

+ 60 - 0
src/lib/dns/rdata_pimpl_holder.h

@@ -0,0 +1,60 @@
+// 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.
+
+#ifndef DNS_RDATA_PIMPL_HOLDER_H
+#define DNS_RDATA_PIMPL_HOLDER_H 1
+
+#include <boost/noncopyable.hpp>
+
+#include <cstddef> // for NULL
+
+namespace isc {
+namespace dns {
+namespace rdata {
+
+template <typename T>
+class RdataPimplHolder : boost::noncopyable {
+public:
+    RdataPimplHolder(T* obj = NULL) :
+        obj_(obj)
+    {}
+
+    ~RdataPimplHolder() {
+        delete obj_;
+    }
+
+    void reset(T* obj = NULL) {
+        delete obj_;
+        obj_ = obj;
+    }
+
+    T* get() {
+        return (obj_);
+    }
+
+    T* release() {
+        T* obj = obj_;
+        obj_ = NULL;
+        return (obj);
+    }
+
+private:
+    T* obj_;
+};
+
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+
+#endif // DNS_RDATA_PIMPL_HOLDER_H

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

@@ -39,6 +39,7 @@ run_unittests_SOURCES += opcode_unittest.cc
 run_unittests_SOURCES += rcode_unittest.cc
 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_in_a_unittest.cc rdata_in_aaaa_unittest.cc
 run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
@@ -60,6 +61,7 @@ run_unittests_SOURCES += rdata_nsec3param_like_unittest.cc
 run_unittests_SOURCES += rdata_rrsig_unittest.cc
 run_unittests_SOURCES += rdata_rp_unittest.cc
 run_unittests_SOURCES += rdata_srv_unittest.cc
+run_unittests_SOURCES += rdata_tlsa_unittest.cc
 run_unittests_SOURCES += rdata_minfo_unittest.cc
 run_unittests_SOURCES += rdata_tsig_unittest.cc
 run_unittests_SOURCES += rdata_naptr_unittest.cc

+ 62 - 0
src/lib/dns/tests/rdata_pimpl_holder_unittest.cc

@@ -0,0 +1,62 @@
+// 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 <dns/rdata_pimpl_holder.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns::rdata;
+
+namespace {
+
+TEST(RdataPimplHolderTest, all) {
+    // Let's check with an integer
+    int* i1 = new int(42);
+    RdataPimplHolder<int> holder1(i1);
+    // The same pointer must be returned.
+    EXPECT_EQ(i1, holder1.get());
+    // Obviously the value should match too.
+    EXPECT_EQ(42, *holder1.get());
+    // We don't explictly delete i or holder1, so it should not leak
+    // anything when the test is done (checked by Valgrind).
+
+    // The following cases are similar:
+
+    // Test no-argument reset()
+    int* i2 = new int(43);
+    RdataPimplHolder<int> holder2(i2);
+    holder2.reset();
+    EXPECT_EQ(NULL, holder2.get());
+
+    // Test reset() with argument
+    int* i3 = new int(44);
+    int* i4 = new int(45);
+    RdataPimplHolder<int> holder3(i3);
+    EXPECT_EQ(i3, holder3.get());
+    holder3.reset(i4);
+    EXPECT_EQ(i4, holder3.get());
+    EXPECT_EQ(45, *holder3.get());
+
+    // Test release()
+    RdataPimplHolder<int> holder4(new int(46));
+    EXPECT_NE(static_cast<void*>(NULL), holder4.get());
+    EXPECT_EQ(46, *holder4.get());
+    int* i5 = holder4.release();
+    EXPECT_EQ(NULL, holder4.get());
+    EXPECT_NE(static_cast<void*>(NULL), i5);
+    EXPECT_EQ(46, *i5);
+    delete i5;
+}
+
+}

+ 282 - 0
src/lib/dns/tests/rdata_tlsa_unittest.cc

@@ -0,0 +1,282 @@
+// 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_TLSA_Test : public RdataTest {
+protected:
+        Rdata_TLSA_Test() :
+            tlsa_txt("0 0 1 d2abde240d7cd3ee6b4b28c54df034b9"
+                     "7983a1d16e8a410e4561cb106618e971"),
+            rdata_tlsa(tlsa_txt)
+        {}
+
+    void checkFromText_None(const string& rdata_str) {
+        checkFromText<generic::TLSA, isc::Exception, isc::Exception>(
+            rdata_str, rdata_tlsa, false, false);
+    }
+
+    void checkFromText_InvalidText(const string& rdata_str) {
+        checkFromText<generic::TLSA, InvalidRdataText, InvalidRdataText>(
+            rdata_str, rdata_tlsa, true, true);
+    }
+
+    void checkFromText_LexerError(const string& rdata_str) {
+        checkFromText
+            <generic::TLSA, InvalidRdataText, MasterLexer::LexerError>(
+                rdata_str, rdata_tlsa, true, true);
+    }
+
+    void checkFromText_BadString(const string& rdata_str) {
+        checkFromText
+            <generic::TLSA, InvalidRdataText, isc::Exception>(
+                rdata_str, rdata_tlsa, true, false);
+    }
+
+    const string tlsa_txt;
+    const generic::TLSA rdata_tlsa;
+};
+
+const uint8_t rdata_tlsa_wiredata[] = {
+    // certificate usage
+    0x00,
+    // selector
+    0x00,
+    // matching type
+    0x01,
+    // certificate association data
+    0xd2, 0xab, 0xde, 0x24, 0x0d, 0x7c, 0xd3, 0xee,
+    0x6b, 0x4b, 0x28, 0xc5, 0x4d, 0xf0, 0x34, 0xb9,
+    0x79, 0x83, 0xa1, 0xd1, 0x6e, 0x8a, 0x41, 0x0e,
+    0x45, 0x61, 0xcb, 0x10, 0x66, 0x18, 0xe9, 0x71
+};
+
+TEST_F(Rdata_TLSA_Test, createFromText) {
+    // Basic test
+    checkFromText_None(tlsa_txt);
+
+    // With different spacing
+    checkFromText_None("0 0 1    d2abde240d7cd3ee6b4b28c54df034b9"
+                       "7983a1d16e8a410e4561cb106618e971");
+
+    // Combination of lowercase and uppercase
+    checkFromText_None("0 0 1 D2ABDE240D7CD3EE6B4B28C54DF034B9"
+                       "7983a1d16e8a410e4561cb106618e971");
+
+    // spacing in the certificate association data field
+    checkFromText_None("0 0 1 d2abde240d7cd3ee6b4b28c54df034b9"
+                       "      7983a1d16e8a410e4561cb106618e971");
+
+    // multi-line certificate association data field
+    checkFromText_None("0 0 1 ( d2abde240d7cd3ee6b4b28c54df034b9\n"
+                       "        7983a1d16e8a410e4561cb106618e971 )");
+
+    // string constructor throws if there's extra text,
+    // but lexer constructor doesn't
+    checkFromText_BadString(tlsa_txt + "\n" + tlsa_txt);
+}
+
+TEST_F(Rdata_TLSA_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::TLSA rdata_tlsa("1 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("2 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("3 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("128 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("255 0 1 12ab"));
+
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 1 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 2 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 3 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 128 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 255 1 12ab"));
+
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 1 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 2 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 3 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 128 12ab"));
+    EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 255 12ab"));
+
+    // > 255 would be broken
+    EXPECT_THROW(const generic::TLSA rdata_tlsa("256 0 1 12ab"),
+                 InvalidRdataText);
+    EXPECT_THROW(const generic::TLSA rdata_tlsa("0 256 1 12ab"),
+                 InvalidRdataText);
+    EXPECT_THROW(const generic::TLSA rdata_tlsa("0 0 256 12ab"),
+                 InvalidRdataText);
+}
+
+TEST_F(Rdata_TLSA_Test, badText) {
+    checkFromText_LexerError("1");
+    checkFromText_LexerError("ONE 2 3 123456789abcdef67890123456789abcdef67890");
+    checkFromText_LexerError("1 TWO 3 123456789abcdef67890123456789abcdef67890");
+    checkFromText_LexerError("1 2 THREE 123456789abcdef67890123456789abcdef67890");
+    checkFromText_InvalidText("1 2 3 BAABAABLACKSHEEP");
+    checkFromText_InvalidText(tlsa_txt + " extra text");
+
+    // yes, these are redundant to the last test cases in the .fields
+    // test
+    checkFromText_InvalidText(
+        "2345 1 2 123456789abcdef67890123456789abcdef67890");
+    checkFromText_InvalidText(
+        "3 1234 4 123456789abcdef67890123456789abcdef67890");
+    checkFromText_InvalidText(
+        "5 6 1234 123456789abcdef67890123456789abcdef67890");
+
+    // negative values are trapped in the lexer rather than the
+    // constructor
+    checkFromText_LexerError("-2 0 1 123456789abcdef67890123456789abcdef67890");
+    checkFromText_LexerError("0 -2 1 123456789abcdef67890123456789abcdef67890");
+    checkFromText_LexerError("0 0 -2 123456789abcdef67890123456789abcdef67890");
+}
+
+TEST_F(Rdata_TLSA_Test, copyAndAssign) {
+    // Copy construct
+    generic::TLSA rdata_tlsa2(rdata_tlsa);
+    EXPECT_EQ(0, rdata_tlsa.compare(rdata_tlsa2));
+
+    // Assignment, mainly to confirm it doesn't cause disruption.
+    rdata_tlsa2 = rdata_tlsa;
+    EXPECT_EQ(0, rdata_tlsa.compare(rdata_tlsa2));
+}
+
+TEST_F(Rdata_TLSA_Test, createFromWire) {
+    // Basic test
+    EXPECT_EQ(0, rdata_tlsa.compare(
+                  *rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                        "rdata_tlsa_fromWire")));
+    // Combination of lowercase and uppercase
+    EXPECT_EQ(0, rdata_tlsa.compare(
+                  *rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                        "rdata_tlsa_fromWire2")));
+    // certificate_usage=0, selector=0, matching_type=1
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire3.wire"));
+
+    // certificate_usage=255, selector=0, matching_type=1
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire4.wire"));
+
+    // certificate_usage=0, selector=255, matching_type=1
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire5.wire"));
+
+    // certificate_usage=0, selector=0, matching_type=255
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire6.wire"));
+
+    // certificate_usage=3, selector=1, matching_type=2
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire7.wire"));
+
+    // short certificate association data
+    EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                         "rdata_tlsa_fromWire8.wire"));
+
+    // certificate association data is shorter than rdata len
+    EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                      "rdata_tlsa_fromWire9"),
+                 InvalidBufferPosition);
+
+    // certificate association data is missing
+    EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                      "rdata_tlsa_fromWire10"),
+                 InvalidBufferPosition);
+
+    // certificate association data is empty
+    EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                      "rdata_tlsa_fromWire12"),
+                 InvalidRdataLength);
+
+    // all RDATA is missing
+    EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+                                      "rdata_tlsa_fromWire11"),
+                 InvalidBufferPosition);
+}
+
+TEST_F(Rdata_TLSA_Test, createFromParams) {
+    const generic::TLSA rdata_tlsa2(
+        0, 0, 1, "d2abde240d7cd3ee6b4b28c54df034b9"
+                 "7983a1d16e8a410e4561cb106618e971");
+    EXPECT_EQ(0, rdata_tlsa2.compare(rdata_tlsa));
+
+    // empty certificate association data should throw
+    EXPECT_THROW(const generic::TLSA rdata_tlsa2(0, 0, 1, ""),
+                 InvalidRdataText);
+}
+
+TEST_F(Rdata_TLSA_Test, toText) {
+    EXPECT_TRUE(boost::iequals(tlsa_txt, rdata_tlsa.toText()));
+}
+
+TEST_F(Rdata_TLSA_Test, toWire) {
+    this->obuffer.clear();
+    rdata_tlsa.toWire(this->obuffer);
+
+    EXPECT_EQ(sizeof (rdata_tlsa_wiredata),
+              this->obuffer.getLength());
+
+    matchWireData(rdata_tlsa_wiredata, sizeof(rdata_tlsa_wiredata),
+                  obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_TLSA_Test, compare) {
+    const generic::TLSA rdata_tlsa2("0 0 0 d2abde240d7cd3ee6b4b28c54df034b9"
+                                    "7983a1d16e8a410e4561cb106618e971");
+    EXPECT_EQ(-1, rdata_tlsa2.compare(rdata_tlsa));
+    EXPECT_EQ(1, rdata_tlsa.compare(rdata_tlsa2));
+}
+
+TEST_F(Rdata_TLSA_Test, getCertificateUsage) {
+    EXPECT_EQ(0, rdata_tlsa.getCertificateUsage());
+}
+
+TEST_F(Rdata_TLSA_Test, getSelector) {
+    EXPECT_EQ(0, rdata_tlsa.getSelector());
+}
+
+TEST_F(Rdata_TLSA_Test, getMatchingType) {
+    EXPECT_EQ(1, rdata_tlsa.getMatchingType());
+}
+
+TEST_F(Rdata_TLSA_Test, getDataLength) {
+    EXPECT_EQ(32, rdata_tlsa.getDataLength());
+}
+}

+ 6 - 0
src/lib/dns/tests/testdata/.gitignore

@@ -87,6 +87,12 @@
 /rdata_sshfp_fromWire6.wire
 /rdata_sshfp_fromWire7.wire
 /rdata_sshfp_fromWire8.wire
+/rdata_tlsa_fromWire3.wire
+/rdata_tlsa_fromWire4.wire
+/rdata_tlsa_fromWire5.wire
+/rdata_tlsa_fromWire6.wire
+/rdata_tlsa_fromWire7.wire
+/rdata_tlsa_fromWire8.wire
 /rdata_tsig_fromWire1.wire
 /rdata_tsig_fromWire2.wire
 /rdata_tsig_fromWire3.wire

+ 9 - 0
src/lib/dns/tests/testdata/Makefile.am

@@ -56,6 +56,9 @@ BUILT_SOURCES += rdata_afsdb_toWire1.wire rdata_afsdb_toWire2.wire
 BUILT_SOURCES += rdata_soa_toWireUncompressed.wire
 BUILT_SOURCES += rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
 BUILT_SOURCES += rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire
+BUILT_SOURCES += rdata_tlsa_fromWire3.wire rdata_tlsa_fromWire4.wire
+BUILT_SOURCES += rdata_tlsa_fromWire5.wire rdata_tlsa_fromWire6.wire
+BUILT_SOURCES += rdata_tlsa_fromWire7.wire rdata_tlsa_fromWire8.wire
 BUILT_SOURCES += rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire
 BUILT_SOURCES += rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire
 BUILT_SOURCES += rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire
@@ -159,6 +162,12 @@ EXTRA_DIST += rrcode16_fromWire1 rrcode16_fromWire2
 EXTRA_DIST += rrcode32_fromWire1 rrcode32_fromWire2
 EXTRA_DIST += rrset_toWire1 rrset_toWire2
 EXTRA_DIST += rrset_toWire3 rrset_toWire4
+EXTRA_DIST += rdata_tlsa_fromWire rdata_tlsa_fromWire2
+EXTRA_DIST += rdata_tlsa_fromWire3.spec rdata_tlsa_fromWire4.spec
+EXTRA_DIST += rdata_tlsa_fromWire5.spec rdata_tlsa_fromWire6.spec
+EXTRA_DIST += rdata_tlsa_fromWire7.spec rdata_tlsa_fromWire8.spec
+EXTRA_DIST += rdata_tlsa_fromWire9 rdata_tlsa_fromWire10
+EXTRA_DIST += rdata_tlsa_fromWire11 rdata_tlsa_fromWire12
 EXTRA_DIST += rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec
 EXTRA_DIST += rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec
 EXTRA_DIST += rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec

+ 4 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire

@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=...
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971

+ 6 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire10

@@ -0,0 +1,6 @@
+# Test where certificate association data is missing.
+
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(missing)
+00 00 01

+ 4 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire11

@@ -0,0 +1,4 @@
+# Test where RDATA is completely missing
+
+# TLSA RDATA, RDLEN=35
+0023

+ 4 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire12

@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=3
+0003
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(none)
+03 01 02

+ 4 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire2

@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=...
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983A1D16E8A410E4561CB106618E971

+ 7 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec

@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 0

+ 7 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec

@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 255

+ 7 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec

@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+selector: 255

+ 7 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec

@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+matching_type: 255

+ 9 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec

@@ -0,0 +1,9 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 3
+selector: 1
+matching_type: 2

+ 7 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec

@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_association_data: '0123'

+ 7 - 0
src/lib/dns/tests/testdata/rdata_tlsa_fromWire9

@@ -0,0 +1,7 @@
+# Test where certificate association data length is smaller than what
+# RDATA length indicates.
+
+# TLSA RDATA, RDLEN=64
+0040
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(32 bytes)
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971

+ 27 - 1
src/lib/util/python/gen_wiredata.py.in

@@ -352,7 +352,7 @@ dict_rrtype = { 'none' : 0, 'a' : 1, 'ns' : 2, 'md' : 3, 'mf' : 4, 'cname' : 5,
                 'srv' : 33, 'naptr' : 35, 'kx' : 36, 'cert' : 37, 'a6' : 38,
                 'dname' : 39, 'opt' : 41, 'apl' : 42, 'ds' : 43, 'sshfp' : 44,
                 'ipseckey' : 45, 'rrsig' : 46, 'nsec' : 47, 'dnskey' : 48,
-                'dhcid' : 49, 'nsec3' : 50, 'nsec3param' : 51, 'hip' : 55,
+                '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 }
@@ -1156,6 +1156,32 @@ class RRSIG(RR):
         f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
         f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
 
+class TLSA(RR):
+    '''Implements rendering TLSA RDATA in the test data format.
+
+    Configurable parameters are as follows (see the description of the
+    same name of attribute for the default value):
+    - certificate_usage (int): The certificate usage field value.
+    - selector (int): The selector field value.
+    - matching_type (int): The matching type field value.
+    - certificate_association_data (string): The certificate association data.
+    '''
+    certificate_usage = 0
+    selector = 0
+    matching_type = 1
+    certificate_association_data = 'd2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971'
+    def dump(self, f):
+        if self.rdlen is None:
+            self.rdlen = 2 + (len(self.certificate_association_data) / 2)
+        else:
+            self.rdlen = int(self.rdlen)
+        self.dump_header(f, self.rdlen)
+        f.write('# CERTIFICATE_USAGE=%d SELECTOR=%d MATCHING_TYPE=%d CERTIFICATE_ASSOCIATION_DATA=%s\n' %\
+                (self.certificate_usage, self.selector, self.matching_type,\
+                 self.certificate_association_data))
+        f.write('%02x %02x %02x %s\n' % (self.certificate_usage, self.selector, self.matching_type,\
+                                         self.certificate_association_data))
+
 class TSIG(RR):
     '''Implements rendering TSIG RDATA in the test data format.