Commit 67003d63 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

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
parent a9a70da8
......@@ -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));
}
......
......@@ -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
......@@ -68,6 +67,34 @@ class MessageRendererImpl;
/// At the moment we don't the strong need for it, so we rather avoid over
/// 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_;
};
}
......
......@@ -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
......
###
### 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
#
# 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
###
### 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
#
# 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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment