From: JINMEI Tatuya Date: Wed, 25 Jul 2012 01:32:08 +0000 (-0700) Subject: [2095] added initial version of rdata encoder. X-Git-Tag: trac2351_base~137^2~4^2~20 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=deca831069987f95dba8773ca4771b7f26b200a3;p=thirdparty%2Fkea.git [2095] added initial version of rdata encoder. generally working, but missing some validity, corner case tests, etc. --- diff --git a/src/lib/datasrc/memory/rdata_encoder.cc b/src/lib/datasrc/memory/rdata_encoder.cc index 2311554ecb..9b9ab5b992 100644 --- a/src/lib/datasrc/memory/rdata_encoder.cc +++ b/src/lib/datasrc/memory/rdata_encoder.cc @@ -14,6 +14,7 @@ #include #include +#include #include #include // for a test function #include @@ -26,11 +27,13 @@ #include #include +#include #include #include using namespace isc::dns; +using std::pair; using std::vector; namespace isc { @@ -266,8 +269,172 @@ getNAPTRDataLen(const rdata::Rdata& rdata) { 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(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 > data_positions_; + vector > names_; + vector 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(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(buf); + uint8_t* dp = reinterpret_cast(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(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, diff --git a/src/lib/datasrc/memory/rdata_encoder.h b/src/lib/datasrc/memory/rdata_encoder.h index 39fe873009..10e0ae72ff 100644 --- a/src/lib/datasrc/memory/rdata_encoder.h +++ b/src/lib/datasrc/memory/rdata_encoder.h @@ -21,6 +21,7 @@ #include #include +#include #include @@ -39,6 +40,46 @@ enum RdataNameAttributes { ///< 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 diff --git a/src/lib/datasrc/memory/tests/rdata_encoder_unittest.cc b/src/lib/datasrc/memory/tests/rdata_encoder_unittest.cc index 33c09afc0f..949de774e8 100644 --- a/src/lib/datasrc/memory/tests/rdata_encoder_unittest.cc +++ b/src/lib/datasrc/memory/tests/rdata_encoder_unittest.cc @@ -27,6 +27,7 @@ #include +#include #include #include #include @@ -74,8 +75,8 @@ const TestRdata test_rdata_list[] = { // 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}, @@ -132,7 +133,7 @@ TEST(RdataFieldSpec, checkData) { 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); @@ -169,4 +170,83 @@ TEST(RdataFieldSpec, checkData) { } } +class RdataEncoderTest : public ::testing::Test { +protected: + RdataEncoderTest() {} + + RdataEncoder encoder_; + vector 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 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 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 + }