Kea 3.1.9
messagerenderer.cc
Go to the documentation of this file.
1// Copyright (C) 2009-2026 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 <limits>
19#include <cassert>
20#include <vector>
21
22using namespace isc::util;
24
25using namespace std;
26
27namespace isc {
28namespace dns {
29
30namespace { // hide internal-only names from the public namespaces
39struct OffsetItem {
40 OffsetItem(size_t hash, size_t pos, size_t len) :
41 hash_(hash), pos_(pos), len_(len) {
42 }
43
46 size_t hash_;
47
50 uint16_t pos_;
51
53 uint16_t len_;
54};
55
64template <bool CASE_SENSITIVE>
65struct NameCompare {
72 NameCompare(const OutputBuffer& buffer, InputBuffer& name_buf,
73 size_t hash) :
74 buffer_(&buffer), name_buf_(&name_buf), hash_(hash) {
75 }
76
77 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
113private:
114 static uint16_t nextPosition(const OutputBuffer& buffer,
115 uint16_t pos, uint16_t& llen) {
116 if (llen == 0) {
117 size_t i = 0;
118
119 while ((buffer[pos] & Name::COMPRESS_POINTER_MARK8) ==
121 pos = (buffer[pos] & ~Name::COMPRESS_POINTER_MARK8) *
122 256 + buffer[pos + 1];
123
124 // This loop should stop as long as the buffer has been
125 // constructed validly and the search/insert argument is based
126 // on a valid name, which is an assumption for this class.
127 // But we'll abort if a bug could cause an infinite loop.
128 i += 2;
130 }
131 llen = buffer[pos];
132 } else {
133 --llen;
134 }
135 return (pos);
136 }
137
138 const OutputBuffer* buffer_;
139 InputBuffer* name_buf_;
140 const size_t hash_;
141};
142}
143
155 // The size of hash buckets and number of hash entries per bucket for
156 // which space is preallocated and kept reserved for subsequent rendering
157 // to provide better performance. These values are derived from the
158 // BIND 9 implementation that uses a similar hash table.
159 static const size_t BUCKETS = 64;
160 static const size_t RESERVED_ITEMS = 16;
161 static const uint16_t NO_OFFSET = 65535; // used as a marker of 'not found'
162
165 msglength_limit_(512), truncated_(false),
167 // Reserve some spaces for hash table items.
168 for (size_t i = 0; i < BUCKETS; ++i) {
169 table_[i].reserve(RESERVED_ITEMS);
170 }
171 }
172
173 uint16_t findOffset(const OutputBuffer& buffer, InputBuffer& name_buf,
174 size_t hash, bool case_sensitive) const {
175 // Find a matching entry, if any. We use some heuristics here: often
176 // the same name appears consecutively (like repeating the same owner
177 // name for a single RRset), so in case there's a collision in the
178 // bucket it will be more likely to find it in the tail side of the
179 // bucket.
180 const size_t bucket_id = hash % BUCKETS;
182 if (case_sensitive) {
183 found = find_if(table_[bucket_id].rbegin(),
184 table_[bucket_id].rend(),
185 NameCompare<true>(buffer, name_buf, hash));
186 } else {
187 found = find_if(table_[bucket_id].rbegin(),
188 table_[bucket_id].rend(),
189 NameCompare<false>(buffer, name_buf, hash));
190 }
191 if (found != table_[bucket_id].rend()) {
192 return (found->pos_);
193 }
194 return (NO_OFFSET);
195 }
196
197 void addOffset(size_t hash, size_t offset, size_t len) {
198 table_[hash % BUCKETS].push_back(OffsetItem(hash, offset, len));
199 }
200
201 // The hash table for the (offset + position in the buffer) entries
202 vector<OffsetItem> table_[BUCKETS];
211
212 // Placeholder for hash values as they are calculated in writeName().
213 // Note: we may want to make it a local variable of writeName() if it
214 // works more efficiently.
215 boost::array<size_t, Name::MAX_LABELS> seq_hashes_;
216};
217
222
225
226void
229 impl_->msglength_limit_ = 512;
230 impl_->truncated_ = false;
231 impl_->compress_mode_ = CASE_INSENSITIVE;
232
233 // Clear the hash table. We reserve the minimum space for possible
234 // subsequent use of the renderer.
235 for (size_t i = 0; i < MessageRendererImpl::BUCKETS; ++i) {
236 if (impl_->table_[i].size() > MessageRendererImpl::RESERVED_ITEMS) {
237 // Trim excessive capacity: swap ensures the new capacity is only
238 // reasonably large for the reserved space.
239 vector<OffsetItem> new_table;
240 new_table.reserve(MessageRendererImpl::RESERVED_ITEMS);
241 new_table.swap(impl_->table_[i]);
242 }
243 impl_->table_[i].clear();
244 }
245}
246
247size_t
249 return (impl_->msglength_limit_);
250}
251
252void
254 impl_->msglength_limit_ = len;
255}
256
257bool
259 return (impl_->truncated_);
260}
261
262void
264 impl_->truncated_ = true;
265}
266
269 return (impl_->compress_mode_);
270}
271
272void
274 if (getLength() != 0) {
276 "compress mode cannot be changed during rendering");
277 }
278 impl_->compress_mode_ = mode;
279}
280
281void
282MessageRenderer::writeName(const LabelSequence& ls, const bool compress) {
283 LabelSequence sequence(ls);
284 const size_t nlabels = sequence.getLabelCount();
285 size_t data_len;
286 const uint8_t* data;
287
288 // Find the offset in the offset table whose name gives the longest
289 // match against the name to be rendered.
290 size_t nlabels_uncomp;
291 uint16_t ptr_offset = MessageRendererImpl::NO_OFFSET;
292 const bool case_sensitive = (impl_->compress_mode_ ==
294 for (nlabels_uncomp = 0; nlabels_uncomp < nlabels; ++nlabels_uncomp) {
295 if (nlabels_uncomp > 0) {
296 sequence.stripLeft(1);
297 }
298
299 data = sequence.getData(&data_len);
300 if (data_len == 1) { // trailing dot.
301 ++nlabels_uncomp;
302 break;
303 }
304 // write with range check for safety
305 impl_->seq_hashes_.at(nlabels_uncomp) =
306 sequence.getHash(impl_->compress_mode_);
307 InputBuffer name_buf(data, data_len);
308 ptr_offset = impl_->findOffset(getBuffer(), name_buf,
309 impl_->seq_hashes_[nlabels_uncomp],
310 case_sensitive);
311 if (ptr_offset != MessageRendererImpl::NO_OFFSET) {
312 break;
313 }
314 }
315
316 // Record the current offset before updating the offset table
317 size_t offset = getLength();
318 // Write uncompress part:
319 if (nlabels_uncomp > 0 || !compress) {
320 LabelSequence uncomp_sequence(ls);
321 if (compress && nlabels > nlabels_uncomp) {
322 // If there's compressed part, strip off that part.
323 uncomp_sequence.stripRight(nlabels - nlabels_uncomp);
324 }
325 data = uncomp_sequence.getData(&data_len);
326 writeData(data, data_len);
327 }
328 // And write compression pointer if available:
329 if (compress && ptr_offset != MessageRendererImpl::NO_OFFSET) {
330 ptr_offset |= Name::COMPRESS_POINTER_MARK16;
331 writeUint16(ptr_offset);
332 }
333
334 // Finally, record the offset and length for each uncompressed sequence
335 // in the hash table. The renderer's buffer has just stored the
336 // corresponding data, so we use the rendered data to get the length
337 // of each label of the names.
338 size_t seqlen = ls.getDataLength();
339 for (size_t i = 0; i < nlabels_uncomp; ++i) {
340 const uint8_t label_len = getBuffer()[offset];
341 if (label_len == 0) { // offset for root doesn't need to be stored.
342 break;
343 }
344 if (offset > Name::MAX_COMPRESS_POINTER) {
345 break;
346 }
347 // Store the tuple of <hash, offset, len> to the table. Note that we
348 // already know the hash value for each name.
349 impl_->addOffset(impl_->seq_hashes_[i], offset, seqlen);
350 offset += (label_len + 1);
351 seqlen -= (label_len + 1);
352 }
353}
354
355void
356MessageRenderer::writeName(const Name& name, const bool compress) {
357 const LabelSequence ls(name);
358 writeName(ls, compress);
359}
360
362 local_buffer_(0), buffer_(&local_buffer_) {
363}
364
365void
367 if (buffer && buffer_->getLength() != 0) {
369 "MessageRenderer buffer cannot be set when in use");
370 }
371 if (!buffer && buffer_ == &local_buffer_) {
373 "Default MessageRenderer buffer cannot be reset");
374 }
375
376 if (!buffer) {
377 // Reset to the default buffer, then clear other internal resources.
378 // The order is important; we need to keep the used buffer intact.
379 buffer_ = &local_buffer_;
380 clear();
381 } else {
382 buffer_ = buffer;
383 }
384}
385
386void
388 buffer_->clear();
389}
390
391}
392}
A generic exception that is thrown if a parameter given to a method or function is considered invalid...
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.
size_t getHash(bool case_sensitive) const
Calculate a simple hash for the label sequence.
size_t getLabelCount() const
Returns the current number of labels for this LabelSequence.
void stripLeft(size_t i)
Remove labels from the front of this LabelSequence.
const uint8_t * getData(size_t *len) const
Return the wire-format data for this LabelSequence.
void stripRight(size_t i)
Remove labels from the end of this LabelSequence.
size_t getDataLength() const
Return the length of the wire-format data of this LabelSequence.
@ 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
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition buffer.h:346
size_t getLength() const
Return the length of data written in the buffer.
Definition buffer.h:412
#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
const uint8_t maptolower[]
Definition name.cc:71
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.