Kea 2.7.5
messagerenderer.cc
Go to the documentation of this file.
1// Copyright (C) 2009-2024 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
11#include <dns/name.h>
12#include <dns/name_internal.h>
13#include <dns/labelsequence.h>
14#include <dns/messagerenderer.h>
15#include <util/buffer.h>
16
17#include <boost/array.hpp>
18#include <boost/static_assert.hpp>
19#include <limits>
20#include <cassert>
21#include <vector>
22
23using namespace isc::util;
25
26using namespace std;
27
28namespace isc {
29namespace dns {
30
31namespace { // hide internal-only names from the public namespaces
40struct OffsetItem {
41 OffsetItem(size_t hash, size_t pos, size_t len) :
42 hash_(hash), pos_(pos), len_(len) {
43 }
44
47 size_t hash_;
48
51 uint16_t pos_;
52
54 uint16_t len_;
55};
56
65template <bool CASE_SENSITIVE>
66struct NameCompare {
73 NameCompare(const OutputBuffer& buffer, InputBuffer& name_buf,
74 size_t hash) :
75 buffer_(&buffer), name_buf_(&name_buf), hash_(hash) {
76 }
77
78 bool operator()(const OffsetItem& item) const {
79 // Trivial inequality check. If either the hash or the total length
80 // doesn't match, the names are obviously different.
81 if (item.hash_ != hash_ || item.len_ != name_buf_->getLength()) {
82 return (false);
83 }
84
85 // Compare the name data, character-by-character.
86 // item_pos keeps track of the position in the buffer corresponding to
87 // the character to compare. item_label_len is the number of
88 // characters in the labels where the character pointed by item_pos
89 // belongs. When it reaches zero, nextPosition() identifies the
90 // position for the subsequent label, taking into account name
91 // compression, and resets item_label_len to the length of the new
92 // label.
93 name_buf_->setPosition(0); // buffer can be reused, so reset position
94 uint16_t item_pos = item.pos_;
95 uint16_t item_label_len = 0;
96 for (size_t i = 0; i < item.len_; ++i, ++item_pos) {
97 item_pos = nextPosition(*buffer_, item_pos, item_label_len);
98 const uint8_t ch1 = (*buffer_)[item_pos];
99 const uint8_t ch2 = name_buf_->readUint8();
100 if (CASE_SENSITIVE) {
101 if (ch1 != ch2) {
102 return (false);
103 }
104 } else {
105 if (maptolower[ch1] != maptolower[ch2]) {
106 return (false);
107 }
108 }
109 }
110
111 return (true);
112 }
113
114private:
115 static uint16_t nextPosition(const OutputBuffer& buffer,
116 uint16_t pos, uint16_t& llen) {
117 if (llen == 0) {
118 size_t i = 0;
119
120 while ((buffer[pos] & 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;
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
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
166 msglength_limit_(512), truncated_(false),
168 // Reserve some spaces for hash table items.
169 for (size_t i = 0; i < BUCKETS; ++i) {
170 table_[i].reserve(RESERVED_ITEMS);
171 }
172 }
173
174 uint16_t findOffset(const OutputBuffer& buffer, InputBuffer& name_buf,
175 size_t hash, bool case_sensitive) const {
176 // Find a matching entry, if any. We use some heuristics here: often
177 // the same name appears consecutively (like repeating the same owner
178 // name for a single RRset), so in case there's a collision in the
179 // bucket it will be more likely to find it in the tail side of the
180 // bucket.
181 const size_t bucket_id = hash % BUCKETS;
182 vector<OffsetItem>::const_reverse_iterator found;
183 if (case_sensitive) {
184 found = find_if(table_[bucket_id].rbegin(),
185 table_[bucket_id].rend(),
186 NameCompare<true>(buffer, name_buf, hash));
187 } else {
188 found = find_if(table_[bucket_id].rbegin(),
189 table_[bucket_id].rend(),
190 NameCompare<false>(buffer, name_buf, hash));
191 }
192 if (found != table_[bucket_id].rend()) {
193 return (found->pos_);
194 }
195 return (NO_OFFSET);
196 }
197
198 void addOffset(size_t hash, size_t offset, size_t len) {
199 table_[hash % BUCKETS].push_back(OffsetItem(hash, offset, len));
200 }
201
202 // The hash table for the (offset + position in the buffer) entries
203 vector<OffsetItem> table_[BUCKETS];
212
213 // Placeholder for hash values as they are calculated in writeName().
214 // Note: we may want to make it a local variable of writeName() if it
215 // works more efficiently.
216 boost::array<size_t, Name::MAX_LABELS> seq_hashes_;
217};
218
223
226
227void
230 impl_->msglength_limit_ = 512;
231 impl_->truncated_ = false;
232 impl_->compress_mode_ = CASE_INSENSITIVE;
233
234 // Clear the hash table. We reserve the minimum space for possible
235 // subsequent use of the renderer.
236 for (size_t i = 0; i < MessageRendererImpl::BUCKETS; ++i) {
237 if (impl_->table_[i].size() > MessageRendererImpl::RESERVED_ITEMS) {
238 // Trim excessive capacity: swap ensures the new capacity is only
239 // reasonably large for the reserved space.
240 vector<OffsetItem> new_table;
241 new_table.reserve(MessageRendererImpl::RESERVED_ITEMS);
242 new_table.swap(impl_->table_[i]);
243 }
244 impl_->table_[i].clear();
245 }
246}
247
248size_t
250 return (impl_->msglength_limit_);
251}
252
253void
255 impl_->msglength_limit_ = len;
256}
257
258bool
260 return (impl_->truncated_);
261}
262
263void
265 impl_->truncated_ = true;
266}
267
270 return (impl_->compress_mode_);
271}
272
273void
275 if (getLength() != 0) {
277 "compress mode cannot be changed during rendering");
278 }
279 impl_->compress_mode_ = mode;
280}
281
282void
283MessageRenderer::writeName(const LabelSequence& ls, const bool compress) {
284 LabelSequence sequence(ls);
285 const size_t nlabels = sequence.getLabelCount();
286 size_t data_len;
287 const uint8_t* data;
288
289 // Find the offset in the offset table whose name gives the longest
290 // match against the name to be rendered.
291 size_t nlabels_uncomp;
292 uint16_t ptr_offset = MessageRendererImpl::NO_OFFSET;
293 const bool case_sensitive = (impl_->compress_mode_ ==
295 for (nlabels_uncomp = 0; nlabels_uncomp < nlabels; ++nlabels_uncomp) {
296 if (nlabels_uncomp > 0) {
297 sequence.stripLeft(1);
298 }
299
300 data = sequence.getData(&data_len);
301 if (data_len == 1) { // trailing dot.
302 ++nlabels_uncomp;
303 break;
304 }
305 // write with range check for safety
306 impl_->seq_hashes_.at(nlabels_uncomp) =
307 sequence.getHash(impl_->compress_mode_);
308 InputBuffer name_buf(data, data_len);
309 ptr_offset = impl_->findOffset(getBuffer(), name_buf,
310 impl_->seq_hashes_[nlabels_uncomp],
311 case_sensitive);
312 if (ptr_offset != MessageRendererImpl::NO_OFFSET) {
313 break;
314 }
315 }
316
317 // Record the current offset before updating the offset table
318 size_t offset = getLength();
319 // Write uncompress part:
320 if (nlabels_uncomp > 0 || !compress) {
321 LabelSequence uncomp_sequence(ls);
322 if (compress && nlabels > nlabels_uncomp) {
323 // If there's compressed part, strip off that part.
324 uncomp_sequence.stripRight(nlabels - nlabels_uncomp);
325 }
326 data = uncomp_sequence.getData(&data_len);
327 writeData(data, data_len);
328 }
329 // And write compression pointer if available:
330 if (compress && ptr_offset != MessageRendererImpl::NO_OFFSET) {
331 ptr_offset |= Name::COMPRESS_POINTER_MARK16;
332 writeUint16(ptr_offset);
333 }
334
335 // Finally, record the offset and length for each uncompressed sequence
336 // in the hash table. The renderer's buffer has just stored the
337 // corresponding data, so we use the rendered data to get the length
338 // of each label of the names.
339 size_t seqlen = ls.getDataLength();
340 for (size_t i = 0; i < nlabels_uncomp; ++i) {
341 const uint8_t label_len = getBuffer()[offset];
342 if (label_len == 0) { // offset for root doesn't need to be stored.
343 break;
344 }
345 if (offset > Name::MAX_COMPRESS_POINTER) {
346 break;
347 }
348 // Store the tuple of <hash, offset, len> to the table. Note that we
349 // already know the hash value for each name.
350 impl_->addOffset(impl_->seq_hashes_[i], offset, seqlen);
351 offset += (label_len + 1);
352 seqlen -= (label_len + 1);
353 }
354}
355
356void
357MessageRenderer::writeName(const Name& name, const bool compress) {
358 const LabelSequence ls(name);
359 writeName(ls, compress);
360}
361
363 local_buffer_(0), buffer_(&local_buffer_) {
364}
365
366void
368 if (buffer && buffer_->getLength() != 0) {
370 "MessageRenderer buffer cannot be set when in use");
371 }
372 if (!buffer && buffer_ == &local_buffer_) {
374 "Default MessageRenderer buffer cannot be reset");
375 }
376
377 if (!buffer) {
378 // Reset to the default buffer, then clear other internal resources.
379 // The order is important; we need to keep the used buffer intact.
380 buffer_ = &local_buffer_;
381 clear();
382 } else {
383 buffer_ = buffer;
384 }
385}
386
387void
389 buffer_->clear();
390}
391
392}
393}
A generic exception that is thrown if a parameter given to a method or function is considered invalid...
The AbstractMessageRenderer class is an abstract base class that provides common interfaces for rende...
AbstractMessageRenderer()
The default constructor.
size_t getLength() const
Return the length of data written in the internal buffer.
virtual void clear()
Clear the internal buffer and other internal resources.
const isc::util::OutputBuffer & getBuffer() const
Return the output buffer we render into.
void writeUint16(uint16_t data)
Write an unsigned 16-bit integer in host byte order into the internal buffer in network byte order.
CompressMode
Compression mode constants.
void setBuffer(isc::util::OutputBuffer *buffer)
Set or reset a temporary output buffer.
void writeData(const void *data, size_t len)
Copy an arbitrary length of data into the internal buffer of the renderer object.
Light-weight Accessor to Name data.
The MessageRenderer is a concrete derived class of AbstractMessageRenderer as a general purpose imple...
@ CASE_SENSITIVE
Compress names case-sensitive manner.
virtual CompressMode getCompressMode() const
Return the compression mode of the renderer class object.
virtual void setLengthLimit(size_t len)
Set the maximum length of rendered data that can fit in the corresponding DNS message without truncat...
virtual void clear()
Clear the internal buffer and other internal resources.
virtual void setTruncated()
Mark the renderer to indicate truncation has occurred while rendering.
virtual bool isTruncated() const
Return whether truncation has occurred while rendering.
virtual void writeName(const Name &name, bool compress=true)
Write a Name object into the internal buffer in wire format, with or without name compression.
virtual size_t getLengthLimit() const
Return the maximum length of rendered data that can fit in the corresponding DNS message without trun...
virtual void setCompressMode(CompressMode mode)
This implementation does not allow this call in the middle of rendering (i.e.
@ CASE_INSENSITIVE
Compress names case-insensitive manner (default)
The Name class encapsulates DNS names.
Definition name.h:219
static const uint16_t MAX_COMPRESS_POINTER
Max possible pointer value for name compression.
Definition name.h:711
static const uint16_t COMPRESS_POINTER_MARK16
A 16-bit masked value indicating a start of compression pointer.
Definition name.h:715
static const size_t MAX_WIRE
Max allowable length of domain names.
Definition name.h:695
static const uint16_t COMPRESS_POINTER_MARK8
A 8-bit masked value indicating a start of compression pointer.
Definition name.h:713
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition buffer.h:81
void setPosition(size_t position)
Set the read position of the buffer to the given value.
Definition buffer.h:112
uint8_t readUint8()
Read an unsigned 8-bit integer from the buffer and return it.
Definition buffer.h:138
size_t getLength() const
Return the length of the data stored in the buffer.
Definition buffer.h:96
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition buffer.h:343
size_t getLength() const
Return the length of data written in the buffer.
Definition buffer.h:409
void clear()
Clear buffer content.
Definition buffer.h:466
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition isc_assert.h:18
size_t hash_
The hash value for the stored name calculated by LabelSequence.getHash.
uint16_t len_
The length of the corresponding sequence (which is a domain name).
uint16_t pos_
The position (offset from the beginning) in the buffer where the name starts.
const uint8_t maptolower[]
Definition name.cc:71
Defines the logger used by the top-level component of kea-lfc.
The MessageRendererImpl class is the actual implementation of MessageRenderer.
boost::array< size_t, Name::MAX_LABELS > seq_hashes_
void addOffset(size_t hash, size_t offset, size_t len)
uint16_t findOffset(const OutputBuffer &buffer, InputBuffer &name_buf, size_t hash, bool case_sensitive) const
CompressMode compress_mode_
The name compression mode.
bool truncated_
A boolean flag that indicates truncation has occurred while rendering the data.
uint16_t msglength_limit_
The maximum length of rendered data that can fit without truncation.