From: Remi Gacogne Date: Thu, 5 May 2022 16:49:34 +0000 (+0200) Subject: Implement a zero-allocation, zero-copy visitor for DNSName raw labels X-Git-Tag: auth-4.8.0-alpha0~71^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fba509234488fbf1c35d3791d6f97f79f3e4096d;p=thirdparty%2Fpdns.git Implement a zero-allocation, zero-copy visitor for DNSName raw labels --- diff --git a/pdns/dnsname.cc b/pdns/dnsname.cc index 31dfa67ef1..1f302e4bb5 100644 --- a/pdns/dnsname.cc +++ b/pdns/dnsname.cc @@ -562,3 +562,61 @@ const unsigned char dns_tolower_table[256] = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; + +DNSName::RawLabelsVisitor::RawLabelsVisitor(const DNSName::string_t& storage): d_storage(storage) +{ + size_t position = 0; + while (position < storage.size()) { + auto labelLength = static_cast(storage.at(position)); + if (labelLength == 0) { + break; + } + d_labelPositions.at(d_position) = position; + d_position++; + position += labelLength + 1; + } +} + +DNSName::RawLabelsVisitor DNSName::getRawLabelsVisitor() const +{ + return DNSName::RawLabelsVisitor(getStorage()); +} + +std::string_view DNSName::RawLabelsVisitor::front() const +{ + if (d_position == 0) { + throw std::out_of_range("trying to access the front of an empty DNSName::RawLabelsVisitor"); + } + uint8_t length = d_storage.at(0); + if (length == 0) { + return std::string_view(); + } + return std::string_view(&d_storage.at(1), length); +} + +std::string_view DNSName::RawLabelsVisitor::back() const +{ + if (d_position == 0) { + throw std::out_of_range("trying to access the back of an empty DNSName::RawLabelsVisitor"); + } + size_t offset = d_labelPositions.at(d_position-1); + uint8_t length = d_storage.at(offset); + if (length == 0) { + return std::string_view(); + } + return std::string_view(&d_storage.at(offset + 1), length); +} + +bool DNSName::RawLabelsVisitor::pop_back() +{ + if (d_position > 0) { + d_position--; + return true; + } + return false; +} + +bool DNSName::RawLabelsVisitor::empty() const +{ + return d_position == 0; +} diff --git a/pdns/dnsname.hh b/pdns/dnsname.hh index 566b3bf7ff..3c3ed3330a 100644 --- a/pdns/dnsname.hh +++ b/pdns/dnsname.hh @@ -20,6 +20,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once +#include #include #include #include @@ -182,6 +183,33 @@ public: bool has8bitBytes() const; /* returns true if at least one byte of the labels forming the name is not included in [A-Za-z0-9_*./@ \\:-] */ + class RawLabelsVisitor + { + public: + /* Zero-copy, zero-allocation raw labels visitor. + The general idea is that we walk the labels in the constructor, + filling up our array of labels position and setting the initial + value of d_position at the number of labels. + We then can easily provide string_view into the first and last label. + pop_back() moves d_position one label closer to the start, so we + can also easily walk back the labels in reverse order. + There is no copy because we use a reference into the DNSName storage, + so it is absolutely forbidden to alter the DNSName for as long as we + exist, and no allocation because we use a static array (there cannot + be more than 128 labels in a DNSName). + */ + RawLabelsVisitor(const string_t& storage); + std::string_view front() const; + std::string_view back() const; + bool pop_back(); + bool empty() const; + private: + std::array d_labelPositions; + const string_t& d_storage; + size_t d_position{0}; + }; + RawLabelsVisitor getRawLabelsVisitor() const; + private: string_t d_storage; @@ -396,20 +424,20 @@ struct SuffixMatchTree return nullptr; } - auto labels = name.getRawLabels(); - return lookup(labels); + auto visitor = name.getRawLabelsVisitor(); + return lookup(visitor); } - T* lookup(std::vector& labels) const + T* lookup(DNSName::RawLabelsVisitor& visitor) const { - if (labels.empty()) { // optimization + if (visitor.empty()) { // optimization if (endNode) { return &d_value; } return nullptr; } - SuffixMatchTree smn(*labels.rbegin()); + SuffixMatchTree smn(std::string(visitor.back())); auto child = children.find(smn); if (child == children.end()) { if(endNode) { @@ -417,8 +445,8 @@ struct SuffixMatchTree } return nullptr; } - labels.pop_back(); - auto result = child->lookup(labels); + visitor.pop_back(); + auto result = child->lookup(visitor); if (result) { return result; } diff --git a/pdns/speedtest.cc b/pdns/speedtest.cc index e227aaa49b..d4c6a3c638 100644 --- a/pdns/speedtest.cc +++ b/pdns/speedtest.cc @@ -769,7 +769,33 @@ struct DNSNameRootTest }; +struct SuffixMatchNodeTest +{ + SuffixMatchNodeTest() + { + d_smn.add(d_exist); + } + + string getName() const + { + return "SuffixMatchNode"; + } + + void operator()() const + { + if (!d_smn.check(d_exist)) { + throw std::runtime_error("Entry not found in SuffixMatchNodeTest"); + } + if (d_smn.check(d_does_not_exist)) { + throw std::runtime_error("Non-existent entry found in SuffixMatchNodeTest"); + } + } +private: + const DNSName d_exist{"a.bb.ccc.ddd."}; + const DNSName d_does_not_exist{"e.bb.ccc.ddd"}; + SuffixMatchNode d_smn; +}; struct IEqualsTest { @@ -1258,6 +1284,8 @@ try doRun(DNSNameParseTest()); doRun(DNSNameRootTest()); + doRun(SuffixMatchNodeTest()); + doRun(NetmaskTreeTest()); doRun(UUIDGenTest()); diff --git a/pdns/test-dnsname_cc.cc b/pdns/test-dnsname_cc.cc index 41f882df53..a874649e2c 100644 --- a/pdns/test-dnsname_cc.cc +++ b/pdns/test-dnsname_cc.cc @@ -971,6 +971,27 @@ BOOST_AUTO_TEST_CASE(test_getrawlabel) { BOOST_CHECK_THROW(name.getRawLabel(name.countLabels()), std::out_of_range); } +BOOST_AUTO_TEST_CASE(test_getrawlabels_visitor) { + DNSName name("a.bb.ccc.dddd."); + auto visitor = name.getRawLabelsVisitor(); + BOOST_CHECK(!visitor.empty()); + BOOST_CHECK_EQUAL(visitor.front(), *name.getRawLabels().begin()); + BOOST_CHECK_EQUAL(visitor.back(), *name.getRawLabels().rbegin()); + + BOOST_CHECK_EQUAL(visitor.back(), "dddd"); + BOOST_CHECK(visitor.pop_back()); + BOOST_CHECK_EQUAL(visitor.back(), "ccc"); + BOOST_CHECK(visitor.pop_back()); + BOOST_CHECK_EQUAL(visitor.back(), "bb"); + BOOST_CHECK(visitor.pop_back()); + BOOST_CHECK_EQUAL(visitor.back(), "a"); + BOOST_CHECK(visitor.pop_back()); + BOOST_CHECK(visitor.empty()); + BOOST_CHECK(!visitor.pop_back()); + BOOST_CHECK_THROW(visitor.front(), std::out_of_range); + BOOST_CHECK_THROW(visitor.back(), std::out_of_range); +} + BOOST_AUTO_TEST_CASE(test_getlastlabel) { DNSName name("www.powerdns.com"); DNSName ans = name.getLastLabel();