summaryrefslogtreecommitdiffstats
path: root/src/lib/dns/messagerenderer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dns/messagerenderer.cc')
-rw-r--r--src/lib/dns/messagerenderer.cc397
1 files changed, 397 insertions, 0 deletions
diff --git a/src/lib/dns/messagerenderer.cc b/src/lib/dns/messagerenderer.cc
new file mode 100644
index 0000000..81b2c92
--- /dev/null
+++ b/src/lib/dns/messagerenderer.cc
@@ -0,0 +1,397 @@
+// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/name_internal.h>
+#include <dns/labelsequence.h>
+#include <dns/messagerenderer.h>
+
+#include <boost/array.hpp>
+#include <boost/static_assert.hpp>
+
+#include <limits>
+#include <cassert>
+#include <vector>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::name::internal::maptolower;
+
+namespace isc {
+namespace dns {
+
+namespace { // hide internal-only names from the public namespaces
+///
+/// \brief The \c OffsetItem class represents a pointer to a name
+/// rendered in the internal buffer for the \c MessageRendererImpl object.
+///
+/// A \c MessageRendererImpl object maintains a set of \c OffsetItem
+/// objects in a hash table, and searches the table for the position of the
+/// longest match (ancestor) name against each new name to be rendered into
+/// the buffer.
+struct OffsetItem {
+ OffsetItem(size_t hash, size_t pos, size_t len) :
+ hash_(hash), pos_(pos), len_(len)
+ {}
+
+ /// The hash value for the stored name calculated by LabelSequence.getHash.
+ /// This will help make name comparison in \c NameCompare more efficient.
+ size_t hash_;
+
+ /// The position (offset from the beginning) in the buffer where the
+ /// name starts.
+ uint16_t pos_;
+
+ /// The length of the corresponding sequence (which is a domain name).
+ uint16_t len_;
+};
+
+/// \brief The \c NameCompare class is a functor that checks equality
+/// between the name corresponding to an \c OffsetItem object and the name
+/// consists of labels represented by a \c LabelSequence object.
+///
+/// Template parameter CASE_SENSITIVE determines whether to ignore the case
+/// of the names. This policy doesn't change throughout the lifetime of
+/// this object, so we separate these using template to avoid unnecessary
+/// condition check.
+template <bool CASE_SENSITIVE>
+struct NameCompare {
+ /// \brief Constructor
+ ///
+ /// \param buffer The buffer for rendering used in the caller renderer
+ /// \param name_buf An input buffer storing the wire-format data of the
+ /// name to be newly rendered (and only that data).
+ /// \param hash The hash value for the name.
+ NameCompare(const OutputBuffer& buffer, InputBuffer& name_buf,
+ size_t hash) :
+ buffer_(&buffer), name_buf_(&name_buf), hash_(hash)
+ {}
+
+ bool operator()(const OffsetItem& item) const {
+ // Trivial inequality check. If either the hash or the total length
+ // doesn't match, the names are obviously different.
+ if (item.hash_ != hash_ || item.len_ != name_buf_->getLength()) {
+ return (false);
+ }
+
+ // Compare the name data, character-by-character.
+ // item_pos keeps track of the position in the buffer corresponding to
+ // the character to compare. item_label_len is the number of
+ // characters in the labels where the character pointed by item_pos
+ // belongs. When it reaches zero, nextPosition() identifies the
+ // position for the subsequent label, taking into account name
+ // compression, and resets item_label_len to the length of the new
+ // label.
+ name_buf_->setPosition(0); // buffer can be reused, so reset position
+ uint16_t item_pos = item.pos_;
+ uint16_t item_label_len = 0;
+ for (size_t i = 0; i < item.len_; ++i, ++item_pos) {
+ item_pos = nextPosition(*buffer_, item_pos, item_label_len);
+ const uint8_t ch1 = (*buffer_)[item_pos];
+ const uint8_t ch2 = name_buf_->readUint8();
+ if (CASE_SENSITIVE) {
+ if (ch1 != ch2) {
+ return (false);
+ }
+ } else {
+ if (maptolower[ch1] != maptolower[ch2]) {
+ return (false);
+ }
+ }
+ }
+
+ return (true);
+ }
+
+private:
+ static uint16_t nextPosition(const OutputBuffer& buffer,
+ uint16_t pos, uint16_t& llen)
+ {
+ if (llen == 0) {
+ size_t i = 0;
+
+ while ((buffer[pos] & Name::COMPRESS_POINTER_MARK8) ==
+ Name::COMPRESS_POINTER_MARK8) {
+ pos = (buffer[pos] & ~Name::COMPRESS_POINTER_MARK8) *
+ 256 + buffer[pos + 1];
+
+ // This loop should stop as long as the buffer has been
+ // constructed validly and the search/insert argument is based
+ // on a valid name, which is an assumption for this class.
+ // But we'll abort if a bug could cause an infinite loop.
+ i += 2;
+ assert(i < Name::MAX_WIRE);
+ }
+ llen = buffer[pos];
+ } else {
+ --llen;
+ }
+ return (pos);
+ }
+
+ const OutputBuffer* buffer_;
+ InputBuffer* name_buf_;
+ const size_t hash_;
+};
+}
+
+///
+/// \brief The \c MessageRendererImpl class is the actual implementation of
+/// \c MessageRenderer.
+///
+/// The implementation is hidden from applications. We can refer to specific
+/// members of this class only within the implementation source file.
+///
+/// It internally holds a hash table for OffsetItem objects corresponding
+/// to portions of names rendered in this renderer. The offset information
+/// is used to compress subsequent names to be rendered.
+struct MessageRenderer::MessageRendererImpl {
+ // The size of hash buckets and number of hash entries per bucket for
+ // which space is preallocated and kept reserved for subsequent rendering
+ // to provide better performance. These values are derived from the
+ // BIND 9 implementation that uses a similar hash table.
+ static const size_t BUCKETS = 64;
+ static const size_t RESERVED_ITEMS = 16;
+ static const uint16_t NO_OFFSET = 65535; // used as a marker of 'not found'
+
+ /// \brief Constructor
+ MessageRendererImpl() :
+ msglength_limit_(512), truncated_(false),
+ compress_mode_(MessageRenderer::CASE_INSENSITIVE)
+ {
+ // Reserve some spaces for hash table items.
+ for (size_t i = 0; i < BUCKETS; ++i) {
+ table_[i].reserve(RESERVED_ITEMS);
+ }
+ }
+
+ uint16_t findOffset(const OutputBuffer& buffer, InputBuffer& name_buf,
+ size_t hash, bool case_sensitive) const
+ {
+ // Find a matching entry, if any. We use some heuristics here: often
+ // the same name appears consecutively (like repeating the same owner
+ // name for a single RRset), so in case there's a collision in the
+ // bucket it will be more likely to find it in the tail side of the
+ // bucket.
+ const size_t bucket_id = hash % BUCKETS;
+ vector<OffsetItem>::const_reverse_iterator found;
+ if (case_sensitive) {
+ found = find_if(table_[bucket_id].rbegin(),
+ table_[bucket_id].rend(),
+ NameCompare<true>(buffer, name_buf, hash));
+ } else {
+ found = find_if(table_[bucket_id].rbegin(),
+ table_[bucket_id].rend(),
+ NameCompare<false>(buffer, name_buf, hash));
+ }
+ if (found != table_[bucket_id].rend()) {
+ return (found->pos_);
+ }
+ return (NO_OFFSET);
+ }
+
+ void addOffset(size_t hash, size_t offset, size_t len) {
+ table_[hash % BUCKETS].push_back(OffsetItem(hash, offset, len));
+ }
+
+ // The hash table for the (offset + position in the buffer) entries
+ vector<OffsetItem> table_[BUCKETS];
+ /// The maximum length of rendered data that can fit without
+ /// truncation.
+ uint16_t msglength_limit_;
+ /// A boolean flag that indicates truncation has occurred while rendering
+ /// the data.
+ bool truncated_;
+ /// The name compression mode.
+ CompressMode compress_mode_;
+
+ // Placeholder for hash values as they are calculated in writeName().
+ // Note: we may want to make it a local variable of writeName() if it
+ // works more efficiently.
+ boost::array<size_t, Name::MAX_LABELS> seq_hashes_;
+};
+
+MessageRenderer::MessageRenderer() :
+ AbstractMessageRenderer(),
+ impl_(new MessageRendererImpl)
+{}
+
+MessageRenderer::~MessageRenderer() {
+ delete impl_;
+}
+
+void
+MessageRenderer::clear() {
+ AbstractMessageRenderer::clear();
+ impl_->msglength_limit_ = 512;
+ impl_->truncated_ = false;
+ impl_->compress_mode_ = CASE_INSENSITIVE;
+
+ // Clear the hash table. We reserve the minimum space for possible
+ // subsequent use of the renderer.
+ for (size_t i = 0; i < MessageRendererImpl::BUCKETS; ++i) {
+ if (impl_->table_[i].size() > MessageRendererImpl::RESERVED_ITEMS) {
+ // Trim excessive capacity: swap ensures the new capacity is only
+ // reasonably large for the reserved space.
+ vector<OffsetItem> new_table;
+ new_table.reserve(MessageRendererImpl::RESERVED_ITEMS);
+ new_table.swap(impl_->table_[i]);
+ }
+ impl_->table_[i].clear();
+ }
+}
+
+size_t
+MessageRenderer::getLengthLimit() const {
+ return (impl_->msglength_limit_);
+}
+
+void
+MessageRenderer::setLengthLimit(const size_t len) {
+ impl_->msglength_limit_ = len;
+}
+
+bool
+MessageRenderer::isTruncated() const {
+ return (impl_->truncated_);
+}
+
+void
+MessageRenderer::setTruncated() {
+ impl_->truncated_ = true;
+}
+
+MessageRenderer::CompressMode
+MessageRenderer::getCompressMode() const {
+ return (impl_->compress_mode_);
+}
+
+void
+MessageRenderer::setCompressMode(const CompressMode mode) {
+ if (getLength() != 0) {
+ isc_throw(isc::InvalidParameter,
+ "compress mode cannot be changed during rendering");
+ }
+ impl_->compress_mode_ = mode;
+}
+
+void
+MessageRenderer::writeName(const LabelSequence& ls, const bool compress) {
+ LabelSequence sequence(ls);
+ const size_t nlabels = sequence.getLabelCount();
+ size_t data_len;
+ const uint8_t* data;
+
+ // Find the offset in the offset table whose name gives the longest
+ // match against the name to be rendered.
+ size_t nlabels_uncomp;
+ uint16_t ptr_offset = MessageRendererImpl::NO_OFFSET;
+ const bool case_sensitive = (impl_->compress_mode_ ==
+ MessageRenderer::CASE_SENSITIVE);
+ for (nlabels_uncomp = 0; nlabels_uncomp < nlabels; ++nlabels_uncomp) {
+ if (nlabels_uncomp > 0) {
+ sequence.stripLeft(1);
+ }
+
+ data = sequence.getData(&data_len);
+ if (data_len == 1) { // trailing dot.
+ ++nlabels_uncomp;
+ break;
+ }
+ // write with range check for safety
+ impl_->seq_hashes_.at(nlabels_uncomp) =
+ sequence.getHash(impl_->compress_mode_);
+ InputBuffer name_buf(data, data_len);
+ ptr_offset = impl_->findOffset(getBuffer(), name_buf,
+ impl_->seq_hashes_[nlabels_uncomp],
+ case_sensitive);
+ if (ptr_offset != MessageRendererImpl::NO_OFFSET) {
+ break;
+ }
+ }
+
+ // Record the current offset before updating the offset table
+ size_t offset = getLength();
+ // Write uncompress part:
+ if (nlabels_uncomp > 0 || !compress) {
+ LabelSequence uncomp_sequence(ls);
+ if (compress && nlabels > nlabels_uncomp) {
+ // If there's compressed part, strip off that part.
+ uncomp_sequence.stripRight(nlabels - nlabels_uncomp);
+ }
+ data = uncomp_sequence.getData(&data_len);
+ writeData(data, data_len);
+ }
+ // And write compression pointer if available:
+ if (compress && ptr_offset != MessageRendererImpl::NO_OFFSET) {
+ ptr_offset |= Name::COMPRESS_POINTER_MARK16;
+ writeUint16(ptr_offset);
+ }
+
+ // Finally, record the offset and length for each uncompressed sequence
+ // in the hash table. The renderer's buffer has just stored the
+ // corresponding data, so we use the rendered data to get the length
+ // of each label of the names.
+ size_t seqlen = ls.getDataLength();
+ for (size_t i = 0; i < nlabels_uncomp; ++i) {
+ const uint8_t label_len = getBuffer()[offset];
+ if (label_len == 0) { // offset for root doesn't need to be stored.
+ break;
+ }
+ if (offset > Name::MAX_COMPRESS_POINTER) {
+ break;
+ }
+ // Store the tuple of <hash, offset, len> to the table. Note that we
+ // already know the hash value for each name.
+ impl_->addOffset(impl_->seq_hashes_[i], offset, seqlen);
+ offset += (label_len + 1);
+ seqlen -= (label_len + 1);
+ }
+}
+
+void
+MessageRenderer::writeName(const Name& name, const bool compress) {
+ const LabelSequence ls(name);
+ writeName(ls, compress);
+}
+
+AbstractMessageRenderer::AbstractMessageRenderer() :
+ local_buffer_(0), buffer_(&local_buffer_)
+{
+}
+
+void
+AbstractMessageRenderer::setBuffer(OutputBuffer* buffer) {
+ if (buffer != NULL && buffer_->getLength() != 0) {
+ isc_throw(isc::InvalidParameter,
+ "MessageRenderer buffer cannot be set when in use");
+ }
+ if (buffer == NULL && buffer_ == &local_buffer_) {
+ isc_throw(isc::InvalidParameter,
+ "Default MessageRenderer buffer cannot be reset");
+ }
+
+ if (buffer == NULL) {
+ // Reset to the default buffer, then clear other internal resources.
+ // The order is important; we need to keep the used buffer intact.
+ buffer_ = &local_buffer_;
+ clear();
+ } else {
+ buffer_ = buffer;
+ }
+}
+
+void
+AbstractMessageRenderer::clear() {
+ buffer_->clear();
+}
+
+}
+}