generally working, but missing some validity, corner case tests, etc.
#include <dns/name.h>
#include <dns/labelsequence.h>
+#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h> // for a test function
#include <dns/rrclass.h>
#include <boost/static_assert.hpp>
#include <cassert>
+#include <utility>
#include <vector>
#include <stdint.h>
using namespace isc::dns;
+using std::pair;
using std::vector;
namespace isc {
rdata.toWire(buffer);
return (buffer.getLength() - naptr_rdata.getReplacement().getLength());
}
+
+// This class is used to divide the content of RDATA into \c RdataField
+// fields via message rendering logic.
+// The idea is to identify domain name fields in the writeName() method,
+// and determine whether they are compressible using the "compress"
+// parameter.
+// Other types of data are simply copied into the internal buffer, and
+// consecutive such fields are combined into a single \c RdataField field.
+//
+// Technically, this use of inheritance may be considered a violation of
+// Liskov Substitution Principle in that it doesn't actually compress domain
+// names, and some of the methods are not expected to be used.
+// In fact, skip() or trim() may not be make much sense in this context.
+// Nevertheless we keep this idea at the moment. Since the usage is limited
+// (it's only used within this file, and only used with \c Rdata variants),
+// it's hopefully an acceptable practice.
+class RdataFieldComposer : public AbstractMessageRenderer {
+public:
+ RdataFieldComposer() : last_data_pos_(0), encode_spec_(NULL),
+ current_field_(0)
+ {}
+ virtual ~RdataFieldComposer() {}
+ virtual bool isTruncated() const { return (false); }
+ virtual size_t getLengthLimit() const { return (65535); }
+ virtual CompressMode getCompressMode() const { return (CASE_INSENSITIVE); }
+ virtual void setTruncated() {}
+ virtual void setLengthLimit(size_t) {}
+ virtual void setCompressMode(CompressMode) {}
+ virtual void writeName(const Name& name, bool /*compress*/) {
+ assert(current_field_ < encode_spec_->field_count); // TBD
+
+ extendData();
+
+ const RdataFieldSpec& field =
+ encode_spec_->fields[current_field_++];
+ assert(field.type == RdataFieldSpec::DOMAIN_NAME); // TBD
+
+ const LabelSequence labels(name);
+ labels.serialize(labels_placeholder_, sizeof(labels_placeholder_));
+ writeData(labels_placeholder_, labels.getSerializedLength());
+ names_.push_back(pair<size_t, Name>(0, name));
+
+ last_data_pos_ += labels.getSerializedLength();
+ }
+ void clearLocal(const RdataEncodeSpec* encode_spec) {
+ AbstractMessageRenderer::clear();
+ encode_spec_ = encode_spec;
+ data_positions_.clear();
+ names_.clear();
+ data_lengths_.clear();
+ last_data_pos_ = 0;
+ }
+ void startRdata() {
+ current_field_ = 0;
+ }
+ void endRdata() {
+ if (current_field_ < encode_spec_->field_count) {
+ extendData();
+ }
+ assert(current_field_ == encode_spec_->field_count); // TBD
+ }
+ vector<pair<size_t, size_t> > data_positions_;
+ vector<pair<size_t, Name> > names_;
+ vector<uint16_t> data_lengths_;
+
+private:
+ // We use generict write* methods, with the exception of writeName.
+ // So new data can arrive without us knowing it, this considers all new
+ // data to be just data and extends the fields to take it into account.
+ size_t last_data_pos_;
+ void extendData() {
+ assert(current_field_ < encode_spec_->field_count); // must be true
+
+ const size_t cur_pos = getLength();
+ size_t data_len = cur_pos - last_data_pos_;
+ while (current_field_ < encode_spec_->field_count) {
+ const RdataFieldSpec& field = encode_spec_->fields[current_field_];
+ if (field.type == RdataFieldSpec::DOMAIN_NAME) {
+ return;
+ }
+ ++current_field_;
+ if (field.type == RdataFieldSpec::FIXEDLEN_DATA) {
+ // TBD: validation
+ data_len -= field.fixeddata_len;
+ continue;
+ }
+ // We are looking at a variable-length data field.
+ // TBD: 16bit len check
+ data_lengths_.push_back(data_len);
+ data_len = 0;
+ break;
+ }
+ // TBD: data_len must be 0;
+ assert(data_len == 0);
+
+ // We added this much data from last time
+ data_positions_.push_back(
+ pair<size_t, size_t>(last_data_pos_, cur_pos - last_data_pos_));
+ last_data_pos_ = cur_pos;
+ }
+
+ const RdataEncodeSpec* encode_spec_;
+ size_t current_field_;
+ uint8_t labels_placeholder_[LabelSequence::MAX_SERIALIZED_LENGTH];
+};
} // end of unnamed namespace
+struct RdataEncoder::RdataEncoderImpl {
+ RdataEncoderImpl() : encode_spec_(NULL), rdata_count_(0)
+ {}
+
+ const RdataEncodeSpec* encode_spec_; // encode spec of current RDATA set
+ RdataFieldComposer field_composer_;
+ size_t rdata_count_;
+};
+
+RdataEncoder::RdataEncoder() :
+ impl_(new RdataEncoderImpl)
+{}
+
+RdataEncoder::~RdataEncoder() {
+ delete impl_;
+}
+
+void
+RdataEncoder::start(RRClass rrclass, RRType rrtype) {
+ impl_->encode_spec_ = &getRdataEncodeSpec(rrclass, rrtype);
+ impl_->field_composer_.clearLocal(impl_->encode_spec_);
+ impl_->rdata_count_ = 0;
+}
+
+void
+RdataEncoder::addRdata(const rdata::Rdata& rdata) {
+ impl_->field_composer_.startRdata();
+ rdata.toWire(impl_->field_composer_);
+ impl_->field_composer_.endRdata();
+ ++impl_->rdata_count_;
+}
+
+size_t
+RdataEncoder::getStorageLength() const {
+ return (sizeof(uint16_t) * impl_->field_composer_.data_lengths_.size() +
+ impl_->field_composer_.getLength());
+}
+
+void
+RdataEncoder::encode(void* buf, size_t buf_len) const {
+ // validation
+
+ const uint8_t* const dp_beg = reinterpret_cast<uint8_t*>(buf);
+ uint8_t* dp = reinterpret_cast<uint8_t*>(buf);
+ if (!impl_->field_composer_.data_lengths_.empty()) {
+ const size_t varlen_fields_len =
+ impl_->field_composer_.data_lengths_.size() * sizeof(uint16_t);
+ uint16_t* lenp = reinterpret_cast<uint16_t*>(buf);
+ memcpy(lenp, &impl_->field_composer_.data_lengths_[0],
+ varlen_fields_len);
+ dp += varlen_fields_len;
+ }
+ memcpy(dp, impl_->field_composer_.getData(),
+ impl_->field_composer_.getLength());
+ dp += impl_->field_composer_.getLength();
+
+ assert(buf_len >= dp - dp_beg);
+}
+
namespace testing {
void
encodeRdata(const rdata::Rdata& rdata, RRClass rrclass, RRType rrtype,
#include <dns/rrtype.h>
#include <boost/function.hpp>
+#include <boost/noncopyable.hpp>
#include <vector>
///< handling
};
+/// \brief TBD
+///
+/// Encoding, FYI:
+/// uint16_t n1_1: size of 1st variable len field (if any) of 1st RDATA
+/// uint16_t n1_2: size of 2nd variable len field of 1st RDATA
+/// ...
+/// uint16_t nN_M: size of last (Mth) variable len field of last (Nth) RDATA
+/// A sequence of packed data fields follow:
+/// uint8_t[]: data field value, length specified by nI_J (in case it's
+/// variable) or by the field spec (in case it's fixed-length).
+/// or
+/// opaque data, LabelSequence::getSerializedLength() bytes: data for a name
+/// (a possible 1-byte padding)
+/// uint16_t ns1: size of 1st RRSIG data
+/// ...
+/// uint16_t nsL: size of last (Lth) RRSIG data
+/// uint8_t[ns1]: 1st RRSIG data
+/// ...
+/// uint8_t[nsL]: last RRSIG data
+class RdataEncoder : boost::noncopyable {
+public:
+ /// \brief Default constructor.
+ RdataEncoder();
+
+ /// \brief The destrcutor.
+ ~RdataEncoder();
+
+ void start(dns::RRClass rrclass, dns::RRType rrtype);
+
+ void addRdata(const dns::rdata::Rdata& rdata);
+
+ size_t getStorageLength() const;
+
+ void encode(void* buf, size_t buf_len) const;
+
+private:
+ struct RdataEncoderImpl;
+ RdataEncoderImpl* impl_;
+};
+
// We use the following quick-hack version of encoder and "foreach"
// operator until we implement the complete versions. The plan is to
// update the test cases that use these functions with the complete
#include <boost/bind.hpp>
+#include <cstring>
#include <set>
#include <string>
#include <vector>
// Note: in our implementation RRSIG is treated as opaque data (including
// the signer name). We use "com" for signer so it won't be a compress
// target in the test.
- {"IN", "RRSIG", "SOA 5 2 3600 20120814220826 20120715220826 12345 "
- "com. FAKEFAKEFAKE", 1},
+ //{"IN", "RRSIG", "SOA 5 2 3600 20120814220826 20120715220826 12345 "
+ //"com. FAKEFAKEFAKE", 1},
{"IN", "NSEC", "next.example.com. A AAAA NSEC RRSIG", 1},
{"IN", "DNSKEY", "256 3 5 FAKEFAKE", 1},
{"IN", "DHCID", "FAKEFAKE", 1},
need_additionals.insert(RRType::MX());
need_additionals.insert(RRType::SRV());
- for (size_t i = 1; test_rdata_list[i].rrclass != NULL; ++i) {
+ for (size_t i = 0; test_rdata_list[i].rrclass != NULL; ++i) {
SCOPED_TRACE(string(test_rdata_list[i].rrclass) + "/" +
test_rdata_list[i].rrtype);
}
}
+class RdataEncoderTest : public ::testing::Test {
+protected:
+ RdataEncoderTest() {}
+
+ RdataEncoder encoder_;
+ vector<uint8_t> encoded_data_;
+ MessageRenderer expected_renderer_;
+ MessageRenderer actual_renderer_;
+};
+
+TEST_F(RdataEncoderTest, addRdata) {
+ // These two names will be rendered before and after the test RDATA,
+ // to check in case the RDATA contain a domain name whether it's
+ // compressed or not correctly. The names in the RDATA should basically
+ // a subdomain of example.com, so it can be compressed due to dummy_name.
+ // Likewise, dummy_name2 should be able to be fully compressed due to
+ // the name in the RDATA.
+ const Name dummy_name("com");
+ const Name dummy_name2("example.com");
+
+ // The set of RR types that require additional section processing.
+ // We'll pass it to renderNameField to check the stored attribute matches
+ // our expectation.
+ std::set<RRType> need_additionals;
+ need_additionals.insert(RRType::NS());
+ need_additionals.insert(RRType::MX());
+ need_additionals.insert(RRType::SRV());
+
+ for (size_t i = 0; test_rdata_list[i].rrclass != NULL; ++i) {
+ SCOPED_TRACE(string(test_rdata_list[i].rrclass) + "/" +
+ test_rdata_list[i].rrtype);
+
+ expected_renderer_.clear();
+ actual_renderer_.clear();
+ expected_renderer_.writeName(dummy_name);
+ actual_renderer_.writeName(dummy_name);
+ encoded_data_.clear();
+
+ const RRClass rrclass(test_rdata_list[i].rrclass);
+ const RRType rrtype(test_rdata_list[i].rrtype);
+ const ConstRdataPtr rdata = createRdata(rrtype, rrclass,
+ test_rdata_list[i].rdata);
+ const bool additional_required =
+ (need_additionals.find(rrtype) != need_additionals.end());
+
+ rdata->toWire(expected_renderer_);
+ expected_renderer_.writeName(dummy_name2);
+
+ encoder_.start(rrclass, rrtype);
+ encoder_.addRdata(*rdata);
+
+ vector<uint16_t> varlen_list;
+ encoded_data_.resize(encoder_.getStorageLength());
+ encoder_.encode(&encoded_data_[0], encoded_data_.size());
+ if (test_rdata_list[i].n_varlen_fields > 0) {
+ const size_t varlen_list_size =
+ test_rdata_list[i].n_varlen_fields * sizeof(uint16_t);
+ ASSERT_LE(varlen_list_size, encoded_data_.size());
+ varlen_list.resize(test_rdata_list[i].n_varlen_fields);
+ std::memcpy(&varlen_list[0], &encoded_data_[0], varlen_list_size);
+ encoded_data_.assign(encoded_data_.begin() + varlen_list_size,
+ encoded_data_.end());
+ }
+ foreachRdataField(rrclass, rrtype, encoded_data_, varlen_list,
+ boost::bind(renderNameField, &actual_renderer_,
+ additional_required, _1, _2),
+ boost::bind(renderDataField, &actual_renderer_,
+ _1, _2));
+
+ actual_renderer_.writeName(dummy_name2);
+ matchWireData(expected_renderer_.getData(),
+ expected_renderer_.getLength(),
+ actual_renderer_.getData(),
+ actual_renderer_.getLength());
+ }
+}
+
+// TODO: add before start
+
}