1 // Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <exceptions/exceptions.h>
10 #include <util/buffer.h>
11 #include <dns/name.h>
12 #include <dns/name_internal.h>
13 #include <dns/labelsequence.h>
14 #include <dns/messagerenderer.h>
15
16 #include <boost/array.hpp>
17 #include <boost/static_assert.hpp>
18
19 #include <limits>
20 #include <cassert>
21 #include <vector>
22
23 using namespace std;
24 using namespace isc::util;
25 using isc::dns::name::internal::maptolower;
26
27 namespace isc {
28 namespace dns {
29
30 namespace { // hide internal-only names from the public namespaces
31 ///
32 /// \brief The \c OffsetItem class represents a pointer to a name
33 /// rendered in the internal buffer for the \c MessageRendererImpl object.
34 ///
35 /// A \c MessageRendererImpl object maintains a set of \c OffsetItem
36 /// objects in a hash table, and searches the table for the position of the
37 /// longest match (ancestor) name against each new name to be rendered into
38 /// the buffer.
39 struct OffsetItem {
OffsetItemisc::dns::__anon5780fbdd0111::OffsetItem40 OffsetItem(size_t hash, size_t pos, size_t len) :
41 hash_(hash), pos_(pos), len_(len)
42 {}
43
44 /// The hash value for the stored name calculated by LabelSequence.getHash.
45 /// This will help make name comparison in \c NameCompare more efficient.
46 size_t hash_;
47
48 /// The position (offset from the beginning) in the buffer where the
49 /// name starts.
50 uint16_t pos_;
51
52 /// The length of the corresponding sequence (which is a domain name).
53 uint16_t len_;
54 };
55
56 /// \brief The \c NameCompare class is a functor that checks equality
57 /// between the name corresponding to an \c OffsetItem object and the name
58 /// consists of labels represented by a \c LabelSequence object.
59 ///
60 /// Template parameter CASE_SENSITIVE determines whether to ignore the case
61 /// of the names. This policy doesn't change throughout the lifetime of
62 /// this object, so we separate these using template to avoid unnecessary
63 /// condition check.
64 template <bool CASE_SENSITIVE>
65 struct NameCompare {
66 /// \brief Constructor
67 ///
68 /// \param buffer The buffer for rendering used in the caller renderer
69 /// \param name_buf An input buffer storing the wire-format data of the
70 /// name to be newly rendered (and only that data).
71 /// \param hash The hash value for the name.
NameCompareisc::dns::__anon5780fbdd0111::NameCompare72 NameCompare(const OutputBuffer& buffer, InputBuffer& name_buf,
73 size_t hash) :
74 buffer_(&buffer), name_buf_(&name_buf), hash_(hash)
75 {}
76
operator ()isc::dns::__anon5780fbdd0111::NameCompare77 bool operator()(const OffsetItem& item) const {
78 // Trivial inequality check. If either the hash or the total length
79 // doesn't match, the names are obviously different.
80 if (item.hash_ != hash_ || item.len_ != name_buf_->getLength()) {
81 return (false);
82 }
83
84 // Compare the name data, character-by-character.
85 // item_pos keeps track of the position in the buffer corresponding to
86 // the character to compare. item_label_len is the number of
87 // characters in the labels where the character pointed by item_pos
88 // belongs. When it reaches zero, nextPosition() identifies the
89 // position for the subsequent label, taking into account name
90 // compression, and resets item_label_len to the length of the new
91 // label.
92 name_buf_->setPosition(0); // buffer can be reused, so reset position
93 uint16_t item_pos = item.pos_;
94 uint16_t item_label_len = 0;
95 for (size_t i = 0; i < item.len_; ++i, ++item_pos) {
96 item_pos = nextPosition(*buffer_, item_pos, item_label_len);
97 const uint8_t ch1 = (*buffer_)[item_pos];
98 const uint8_t ch2 = name_buf_->readUint8();
99 if (CASE_SENSITIVE) {
100 if (ch1 != ch2) {
101 return (false);
102 }
103 } else {
104 if (maptolower[ch1] != maptolower[ch2]) {
105 return (false);
106 }
107 }
108 }
109
110 return (true);
111 }
112
113 private:
nextPositionisc::dns::__anon5780fbdd0111::NameCompare114 static uint16_t nextPosition(const OutputBuffer& buffer,
115 uint16_t pos, uint16_t& llen)
116 {
117 if (llen == 0) {
118 size_t i = 0;
119
120 while ((buffer[pos] & Name::COMPRESS_POINTER_MARK8) ==
121 Name::COMPRESS_POINTER_MARK8) {
122 pos = (buffer[pos] & ~Name::COMPRESS_POINTER_MARK8) *
123 256 + buffer[pos + 1];
124
125 // This loop should stop as long as the buffer has been
126 // constructed validly and the search/insert argument is based
127 // on a valid name, which is an assumption for this class.
128 // But we'll abort if a bug could cause an infinite loop.
129 i += 2;
130 assert(i < Name::MAX_WIRE);
131 }
132 llen = buffer[pos];
133 } else {
134 --llen;
135 }
136 return (pos);
137 }
138
139 const OutputBuffer* buffer_;
140 InputBuffer* name_buf_;
141 const size_t hash_;
142 };
143 }
144
145 ///
146 /// \brief The \c MessageRendererImpl class is the actual implementation of
147 /// \c MessageRenderer.
148 ///
149 /// The implementation is hidden from applications. We can refer to specific
150 /// members of this class only within the implementation source file.
151 ///
152 /// It internally holds a hash table for OffsetItem objects corresponding
153 /// to portions of names rendered in this renderer. The offset information
154 /// is used to compress subsequent names to be rendered.
155 struct MessageRenderer::MessageRendererImpl {
156 // The size of hash buckets and number of hash entries per bucket for
157 // which space is preallocated and kept reserved for subsequent rendering
158 // to provide better performance. These values are derived from the
159 // BIND 9 implementation that uses a similar hash table.
160 static const size_t BUCKETS = 64;
161 static const size_t RESERVED_ITEMS = 16;
162 static const uint16_t NO_OFFSET = 65535; // used as a marker of 'not found'
163
164 /// \brief Constructor
MessageRendererImplisc::dns::MessageRenderer::MessageRendererImpl165 MessageRendererImpl() :
166 msglength_limit_(512), truncated_(false),
167 compress_mode_(MessageRenderer::CASE_INSENSITIVE)
168 {
169 // Reserve some spaces for hash table items.
170 for (size_t i = 0; i < BUCKETS; ++i) {
171 table_[i].reserve(RESERVED_ITEMS);
172 }
173 }
174
findOffsetisc::dns::MessageRenderer::MessageRendererImpl175 uint16_t findOffset(const OutputBuffer& buffer, InputBuffer& name_buf,
176 size_t hash, bool case_sensitive) const
177 {
178 // Find a matching entry, if any. We use some heuristics here: often
179 // the same name appears consecutively (like repeating the same owner
180 // name for a single RRset), so in case there's a collision in the
181 // bucket it will be more likely to find it in the tail side of the
182 // bucket.
183 const size_t bucket_id = hash % BUCKETS;
184 vector<OffsetItem>::const_reverse_iterator found;
185 if (case_sensitive) {
186 found = find_if(table_[bucket_id].rbegin(),
187 table_[bucket_id].rend(),
188 NameCompare<true>(buffer, name_buf, hash));
189 } else {
190 found = find_if(table_[bucket_id].rbegin(),
191 table_[bucket_id].rend(),
192 NameCompare<false>(buffer, name_buf, hash));
193 }
194 if (found != table_[bucket_id].rend()) {
195 return (found->pos_);
196 }
197 return (NO_OFFSET);
198 }
199
addOffsetisc::dns::MessageRenderer::MessageRendererImpl200 void addOffset(size_t hash, size_t offset, size_t len) {
201 table_[hash % BUCKETS].push_back(OffsetItem(hash, offset, len));
202 }
203
204 // The hash table for the (offset + position in the buffer) entries
205 vector<OffsetItem> table_[BUCKETS];
206 /// The maximum length of rendered data that can fit without
207 /// truncation.
208 uint16_t msglength_limit_;
209 /// A boolean flag that indicates truncation has occurred while rendering
210 /// the data.
211 bool truncated_;
212 /// The name compression mode.
213 CompressMode compress_mode_;
214
215 // Placeholder for hash values as they are calculated in writeName().
216 // Note: we may want to make it a local variable of writeName() if it
217 // works more efficiently.
218 boost::array<size_t, Name::MAX_LABELS> seq_hashes_;
219 };
220
MessageRenderer()221 MessageRenderer::MessageRenderer() :
222 AbstractMessageRenderer(),
223 impl_(new MessageRendererImpl)
224 {}
225
~MessageRenderer()226 MessageRenderer::~MessageRenderer() {
227 delete impl_;
228 }
229
230 void
clear()231 MessageRenderer::clear() {
232 AbstractMessageRenderer::clear();
233 impl_->msglength_limit_ = 512;
234 impl_->truncated_ = false;
235 impl_->compress_mode_ = CASE_INSENSITIVE;
236
237 // Clear the hash table. We reserve the minimum space for possible
238 // subsequent use of the renderer.
239 for (size_t i = 0; i < MessageRendererImpl::BUCKETS; ++i) {
240 if (impl_->table_[i].size() > MessageRendererImpl::RESERVED_ITEMS) {
241 // Trim excessive capacity: swap ensures the new capacity is only
242 // reasonably large for the reserved space.
243 vector<OffsetItem> new_table;
244 new_table.reserve(MessageRendererImpl::RESERVED_ITEMS);
245 new_table.swap(impl_->table_[i]);
246 }
247 impl_->table_[i].clear();
248 }
249 }
250
251 size_t
getLengthLimit() const252 MessageRenderer::getLengthLimit() const {
253 return (impl_->msglength_limit_);
254 }
255
256 void
setLengthLimit(const size_t len)257 MessageRenderer::setLengthLimit(const size_t len) {
258 impl_->msglength_limit_ = len;
259 }
260
261 bool
isTruncated() const262 MessageRenderer::isTruncated() const {
263 return (impl_->truncated_);
264 }
265
266 void
setTruncated()267 MessageRenderer::setTruncated() {
268 impl_->truncated_ = true;
269 }
270
271 MessageRenderer::CompressMode
getCompressMode() const272 MessageRenderer::getCompressMode() const {
273 return (impl_->compress_mode_);
274 }
275
276 void
setCompressMode(const CompressMode mode)277 MessageRenderer::setCompressMode(const CompressMode mode) {
278 if (getLength() != 0) {
279 isc_throw(isc::InvalidParameter,
280 "compress mode cannot be changed during rendering");
281 }
282 impl_->compress_mode_ = mode;
283 }
284
285 void
writeName(const LabelSequence & ls,const bool compress)286 MessageRenderer::writeName(const LabelSequence& ls, const bool compress) {
287 LabelSequence sequence(ls);
288 const size_t nlabels = sequence.getLabelCount();
289 size_t data_len;
290 const uint8_t* data;
291
292 // Find the offset in the offset table whose name gives the longest
293 // match against the name to be rendered.
294 size_t nlabels_uncomp;
295 uint16_t ptr_offset = MessageRendererImpl::NO_OFFSET;
296 const bool case_sensitive = (impl_->compress_mode_ ==
297 MessageRenderer::CASE_SENSITIVE);
298 for (nlabels_uncomp = 0; nlabels_uncomp < nlabels; ++nlabels_uncomp) {
299 if (nlabels_uncomp > 0) {
300 sequence.stripLeft(1);
301 }
302
303 data = sequence.getData(&data_len);
304 if (data_len == 1) { // trailing dot.
305 ++nlabels_uncomp;
306 break;
307 }
308 // write with range check for safety
309 impl_->seq_hashes_.at(nlabels_uncomp) =
310 sequence.getHash(impl_->compress_mode_);
311 InputBuffer name_buf(data, data_len);
312 ptr_offset = impl_->findOffset(getBuffer(), name_buf,
313 impl_->seq_hashes_[nlabels_uncomp],
314 case_sensitive);
315 if (ptr_offset != MessageRendererImpl::NO_OFFSET) {
316 break;
317 }
318 }
319
320 // Record the current offset before updating the offset table
321 size_t offset = getLength();
322 // Write uncompress part:
323 if (nlabels_uncomp > 0 || !compress) {
324 LabelSequence uncomp_sequence(ls);
325 if (compress && nlabels > nlabels_uncomp) {
326 // If there's compressed part, strip off that part.
327 uncomp_sequence.stripRight(nlabels - nlabels_uncomp);
328 }
329 data = uncomp_sequence.getData(&data_len);
330 writeData(data, data_len);
331 }
332 // And write compression pointer if available:
333 if (compress && ptr_offset != MessageRendererImpl::NO_OFFSET) {
334 ptr_offset |= Name::COMPRESS_POINTER_MARK16;
335 writeUint16(ptr_offset);
336 }
337
338 // Finally, record the offset and length for each uncompressed sequence
339 // in the hash table. The renderer's buffer has just stored the
340 // corresponding data, so we use the rendered data to get the length
341 // of each label of the names.
342 size_t seqlen = ls.getDataLength();
343 for (size_t i = 0; i < nlabels_uncomp; ++i) {
344 const uint8_t label_len = getBuffer()[offset];
345 if (label_len == 0) { // offset for root doesn't need to be stored.
346 break;
347 }
348 if (offset > Name::MAX_COMPRESS_POINTER) {
349 break;
350 }
351 // Store the tuple of <hash, offset, len> to the table. Note that we
352 // already know the hash value for each name.
353 impl_->addOffset(impl_->seq_hashes_[i], offset, seqlen);
354 offset += (label_len + 1);
355 seqlen -= (label_len + 1);
356 }
357 }
358
359 void
writeName(const Name & name,const bool compress)360 MessageRenderer::writeName(const Name& name, const bool compress) {
361 const LabelSequence ls(name);
362 writeName(ls, compress);
363 }
364
AbstractMessageRenderer()365 AbstractMessageRenderer::AbstractMessageRenderer() :
366 local_buffer_(0), buffer_(&local_buffer_)
367 {
368 }
369
370 void
setBuffer(OutputBuffer * buffer)371 AbstractMessageRenderer::setBuffer(OutputBuffer* buffer) {
372 if (buffer != NULL && buffer_->getLength() != 0) {
373 isc_throw(isc::InvalidParameter,
374 "MessageRenderer buffer cannot be set when in use");
375 }
376 if (buffer == NULL && buffer_ == &local_buffer_) {
377 isc_throw(isc::InvalidParameter,
378 "Default MessageRenderer buffer cannot be reset");
379 }
380
381 if (buffer == NULL) {
382 // Reset to the default buffer, then clear other internal resources.
383 // The order is important; we need to keep the used buffer intact.
384 buffer_ = &local_buffer_;
385 clear();
386 } else {
387 buffer_ = buffer;
388 }
389 }
390
391 void
clear()392 AbstractMessageRenderer::clear() {
393 buffer_->clear();
394 }
395
396 }
397 }
398