Browse Source

supported case-sensitive name compression in MessageRenderer.
test cases and documentation were also fully provided.

(this changeset includes a minor unrelated cleanup: move the pimpl structure
inside the MessageRenderer class declaration to clarify the intent)


git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@1704 e5f2f494-b856-4b98-b285-d166d9295462

JINMEI Tatuya 15 years ago
parent
commit
67003d63aa

+ 41 - 10
src/lib/dns/messagerenderer.cc

@@ -34,9 +34,14 @@ namespace {     // hide internal-only names from the public namespaces
 /// objects, and searches the set for the position of the longest match
 /// (ancestor) name against each new name to be rendered into the buffer.
 struct NameCompressNode {
-    NameCompressNode(const OutputBuffer& buffer, const size_t pos,
+    NameCompressNode(const MessageRenderer& renderer,
+                     const OutputBuffer& buffer, const size_t pos,
                      const size_t len) :
-        buffer_(buffer), pos_(pos), len_(len) {}
+        renderer_(renderer), buffer_(buffer), pos_(pos), len_(len) {}
+    /// The renderer that performs name compression using the node.
+    /// This is kept in each node to detect the compression mode
+    /// (case-sensitive or not) in the comparison functor (\c NameCompare).
+    const MessageRenderer& renderer_;
     /// The buffer in which the corresponding name is rendered.
     const OutputBuffer& buffer_;
     /// The position (offset from the beginning) in the buffer where the
@@ -78,6 +83,9 @@ struct NameCompare : public std::binary_function<NameCompressNode,
             return (false);
         }
 
+        const bool case_sensitive =
+            (n1.renderer_.getCompressMode() == MessageRenderer::CASE_SENSITIVE);
+
         uint16_t pos1 = n1.pos_;
         uint16_t pos2 = n2.pos_;
         uint16_t l1 = 0;
@@ -85,10 +93,19 @@ struct NameCompare : public std::binary_function<NameCompressNode,
         for (uint16_t i = 0; i < n1.len_; i++, pos1++, pos2++) {
             pos1 = nextPosition(n1.buffer_, pos1, l1);
             pos2 = nextPosition(n2.buffer_, pos2, l2);
-            if (tolower(n1.buffer_[pos1]) < tolower(n2.buffer_[pos2])) {
-                return (true);
-            } else if (tolower(n1.buffer_[pos1]) > tolower(n2.buffer_[pos2])) {
-                return (false);
+            if (case_sensitive) {
+                if (n1.buffer_[pos1] < n2.buffer_[pos2]) {
+                    return (true);
+                } else if (n1.buffer_[pos1] > n2.buffer_[pos2]) {
+                    return (false);
+                }
+            } else {
+                if (tolower(n1.buffer_[pos1]) < tolower(n2.buffer_[pos2])) {
+                    return (true);
+                } else if (tolower(n1.buffer_[pos1]) >
+                           tolower(n2.buffer_[pos2])) {
+                    return (false);
+                }
             }
         }
 
@@ -130,14 +147,14 @@ private:
 /// The implementation is hidden from applications.  We can refer to specific
 /// members of this class only within the implementation source file.
 ///
-struct MessageRendererImpl {
+struct MessageRenderer::MessageRendererImpl {
     /// \brief Constructor from an output buffer.
     ///
     /// \param buffer An \c OutputBuffer object to which wire format data is
     /// written.
     MessageRendererImpl(OutputBuffer& buffer) :
         buffer_(buffer), nbuffer_(Name::MAX_WIRE), msglength_limit_(512),
-        truncated_(false)
+        truncated_(false), compress_mode_(MessageRenderer::CASE_INSENSITIVE)
     {}
     /// The buffer that holds the entire DNS message.
     OutputBuffer& buffer_;
@@ -154,6 +171,8 @@ struct MessageRendererImpl {
     /// A boolean flag that indicates truncation has occurred while rendering
     /// the data.
     bool truncated_;
+    /// The name compression mode.
+    CompressMode compress_mode_;
 };
 
 MessageRenderer::MessageRenderer(OutputBuffer& buffer) :
@@ -181,6 +200,7 @@ MessageRenderer::clear() {
     impl_->nodeset_.clear();
     impl_->msglength_limit_ = 512;
     impl_->truncated_ = false;
+    impl_->compress_mode_ = CASE_INSENSITIVE;
 }
 
 void
@@ -238,6 +258,16 @@ MessageRenderer::setTruncated() {
     impl_->truncated_ = true;
 }
 
+MessageRenderer::CompressMode
+MessageRenderer::getCompressMode() const {
+    return (impl_->compress_mode_);
+}
+
+void
+MessageRenderer::setCompressMode(const CompressMode mode) {
+    impl_->compress_mode_ = mode;
+}
+
 void
 MessageRenderer::writeName(const Name& name, const bool compress) {
     impl_->nbuffer_.clear();
@@ -254,7 +284,7 @@ MessageRenderer::writeName(const Name& name, const bool compress) {
         if (impl_->nbuffer_[i] == 0) {
             continue;
         }
-        n = impl_->nodeset_.find(NameCompressNode(impl_->nbuffer_, i,
+        n = impl_->nodeset_.find(NameCompressNode(*this, impl_->nbuffer_, i,
                                                   impl_->nbuffer_.getLength() -
                                                   i));
         if (n != notfound) {
@@ -283,7 +313,8 @@ MessageRenderer::writeName(const Name& name, const bool compress) {
         if (offset + j > Name::MAX_COMPRESS_POINTER) {
             break;
         }
-        impl_->nodeset_.insert(NameCompressNode(impl_->buffer_, offset + j,
+        impl_->nodeset_.insert(NameCompressNode(*this, impl_->buffer_,
+                                                offset + j,
                                                 impl_->nbuffer_.getLength() -
                                                 j));
     }

+ 42 - 2
src/lib/dns/messagerenderer.h

@@ -22,7 +22,6 @@ namespace dns {
 // forward declarations
 class OutputBuffer;
 class Name;
-class MessageRendererImpl;
 
 ///
 /// \brief The \c MessageRenderer class encapsulates implementation details
@@ -38,7 +37,7 @@ class MessageRendererImpl;
 /// to care about this class.
 ///
 /// A \c MessageRenderer class object is constructed with a \c OutputBuffer
-/// object, which is the buffer into which the rendered data will be written.
+/// object, which is the buffer into which the rendered %data will be written.
 /// Normally the buffer is expected to be empty on construction, but it doesn't
 /// have to be so; the \c MessageRenderer object will start rendering from the
 /// end of the buffer at the time of construction.  However, if the
@@ -69,6 +68,34 @@ class MessageRendererImpl;
 /// abstraction and keep the definition simpler.
 class MessageRenderer {
 public:
+    /// \brief Compression mode constants.
+    ///
+    /// The \c CompressMode enum type represents the name compression mode
+    /// for the \c MessageRenderer.
+    /// \c CASE_INSENSITIVE means compress names in case-insensitive manner;
+    /// \c CASE_SENSITIVE means compress names in case-sensitive manner.
+    /// By default, \c MessageRenderer compresses names in case-insensitive
+    /// manner.
+    /// Compression mode can be dynamically modified by the
+    /// \c setCompressMode() method.
+    /// The mode can be changed even in the middle of rendering, although this
+    /// is not an intended usage.  In this case the names already compressed
+    /// are intact; only names being compressed after the mode change are
+    /// affected by the change.
+    /// If the internal \c MessageRenderer is reinitialized by the \c clear()
+    /// method, the compression mode will be reset to the default, which is
+    /// \c CASE_INSENSITIVE
+    ///
+    /// One specific case where case-sensitive compression is required is
+    /// AXFR as described in draft-ietf-dnsext-axfr-clarify.  A primary
+    /// authoritative DNS server implementation using this API would specify
+    /// \c CASE_SENSITIVE before rendering outgoing AXFR messages.
+    ///
+    enum CompressMode {
+        CASE_INSENSITIVE,  //!< Compress names case-insensitive manner (default)
+        CASE_SENSITIVE     //!< Compress names case-sensitive manner
+    };
+public:
     ///
     /// \name Constructors and Destructor
     //@{
@@ -116,6 +143,12 @@ public:
     ///
     /// \return The maximum length in bytes.
     size_t getLengthLimit() const;
+    /// \brief Return the compression mode of the \c MessageRenderer.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return The current compression mode.
+    CompressMode getCompressMode() const;
     //@}
 
     ///
@@ -134,6 +167,12 @@ public:
     ///
     /// \param len The maximum length in bytes.
     void setLengthLimit(size_t len);
+    /// \brief Set the compression mode of the \c MessageRenderer.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param mode A \c CompressMode value representing the compression mode.
+    void setCompressMode(CompressMode mode);
     //@}
 
     ///
@@ -220,6 +259,7 @@ public:
     /// \param compress A boolean indicating whether to enable name compression.
     void writeName(const Name& name, bool compress = true);
 private:
+    struct MessageRendererImpl;
     MessageRendererImpl* impl_;
 };
 }

+ 45 - 0
src/lib/dns/tests/messagerenderer_unittest.cc

@@ -98,7 +98,25 @@ TEST_F(MessageRendererTest, writeNamePointerChain) {
                         buffer.getLength(), &data[0], data.size());
 }
 
+TEST_F(MessageRendererTest, compressMode) {
+    // By default the render performs case insensitive compression.
+    EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode());
+
+    // The mode can be explicitly changed.
+    renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE);
+    EXPECT_EQ(MessageRenderer::CASE_SENSITIVE, renderer.getCompressMode());
+    renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE);
+    EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode());
+
+    // The clear() method resets the mode to the default.
+    renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE);
+    renderer.clear();
+    EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode());
+}
+
 TEST_F(MessageRendererTest, writeNameCaseCompress) {
+    // By default MessageRenderer performs case insensitive compression.
+
     UnitTestUtil::readWireData("testdata/name_toWire1", data);
     renderer.writeName(Name("a.example.com."));
     // this should match the first name in terms of compression:
@@ -108,6 +126,33 @@ TEST_F(MessageRendererTest, writeNameCaseCompress) {
                         buffer.getLength(), &data[0], data.size());
 }
 
+TEST_F(MessageRendererTest, writeNameCaseSensitiveCompress) {
+    // name compression in case sensitive manner.  See the data file
+    // description for details.
+    renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE);
+    UnitTestUtil::readWireData("testdata/name_toWire5", data);
+    renderer.writeName(Name("a.example.com."));
+    renderer.writeName(Name("b.eXample.com."));
+    renderer.writeName(Name("c.eXample.com."));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
+                        buffer.getLength(), &data[0], data.size());
+}
+
+TEST_F(MessageRendererTest, writeNameMixedCaseCompress) {
+    renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE);
+    UnitTestUtil::readWireData("testdata/name_toWire6", data);
+    renderer.writeName(Name("a.example.com."));
+    renderer.writeName(Name("b.eXample.com."));
+
+    // Change the compression mode in the middle of rendering.  This is an
+    // unusual operation and is unlikely to happen in practice, but is still
+    // allowed in this API.
+    renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE);
+    renderer.writeName(Name("c.b.EXAMPLE.com."));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
+                        buffer.getLength(), &data[0], data.size());
+}
+
 TEST_F(MessageRendererTest, writeRootName) {
     // root name is special: it never causes compression or can (reasonably)
     // be a compression pointer.  So it makes sense to check this case

+ 12 - 0
src/lib/dns/tests/testdata/name_toWire5

@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from name_toWire5.spec
+###
+
+# DNS Name: a.example.com
+0161076578616d706c6503636f6d00
+
+# DNS Name: b.eXample + compression pointer: 10
+0162076558616d706c65 c00a
+
+# DNS Name: c + compression pointer: 17
+0163 c011

+ 19 - 0
src/lib/dns/tests/testdata/name_toWire5.spec

@@ -0,0 +1,19 @@
+#
+# A sequence of names that would be compressed case-sensitive manner.
+# First name: "a.example.com"
+# Second name: "b.eXample.com".  Due to case-sensitive comparison only "com"
+# can be compressed.
+# Third name: "c.eXample.com".  "eXample.com" part matches that of the second
+# name and can be compressed.
+#
+
+[custom]
+sections: name/1:name/2:name/3
+[name/1]
+name: a.example.com
+[name/2]
+name: b.eXample
+pointer: 10
+[name/3]
+name: c
+pointer: 17

+ 12 - 0
src/lib/dns/tests/testdata/name_toWire6

@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from name_toWire6.spec
+###
+
+# DNS Name: a.example.com
+0161076578616d706c6503636f6d00
+
+# DNS Name: b.eXample + compression pointer: 10
+0162076558616d706c65 c00a
+
+# DNS Name: c + compression pointer: 15
+0163 c00f

+ 19 - 0
src/lib/dns/tests/testdata/name_toWire6.spec

@@ -0,0 +1,19 @@
+#
+# A sequence of names that would be compressed both case-sensitive and
+# case-insensitive manner (unusual, but allowed).
+# First and second name: see name_toWire5.spec.
+# Third name: "c.b.EXAMPLE.com".  This is rendered with case-insensitive
+# compression, so "b.EXAMPLE.com" part of the name matches that of the
+# second name.
+#
+
+[custom]
+sections: name/1:name/2:name/3
+[name/1]
+name: a.example.com
+[name/2]
+name: b.eXample
+pointer: 10
+[name/3]
+name: c
+pointer: 15