diff options
Diffstat (limited to 'src/lib/dns/tests/rdata_txt_like_unittest.cc')
-rw-r--r-- | src/lib/dns/tests/rdata_txt_like_unittest.cc | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc new file mode 100644 index 0000000..e2828c2 --- /dev/null +++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc @@ -0,0 +1,397 @@ +// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This is the common code for TXT and SPF tests. + +#include <config.h> + +#include <util/buffer.h> +#include <dns/exceptions.h> +#include <dns/rdataclass.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> + +#include <util/unittests/wiredata.h> + +#include <gtest/gtest.h> + +#include <string> +#include <sstream> +#include <vector> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { + +template<class T> +class RRTYPE : public RRType { +public: + RRTYPE(); +}; + +template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {} +template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {} + +const uint8_t wiredata_txt_like[] = { + sizeof("Test-String") - 1, + 'T', 'e', 's', 't', '-', 'S', 't', 'r', 'i', 'n', 'g' +}; + +const uint8_t wiredata_nulltxt[] = { 0 }; + +template<class TXT_LIKE> +class Rdata_TXT_LIKE_Test : public RdataTest { +protected: + Rdata_TXT_LIKE_Test() : + wiredata_longesttxt(256, 'a'), + rdata_txt_like("Test-String"), + rdata_txt_like_empty("\"\""), + rdata_txt_like_quoted("\"Test-String\"") + { + wiredata_longesttxt[0] = 255; // adjust length + } + +protected: + vector<uint8_t> wiredata_longesttxt; + const TXT_LIKE rdata_txt_like; + const TXT_LIKE rdata_txt_like_empty; + const TXT_LIKE rdata_txt_like_quoted; +}; + +// The list of types we want to test. +typedef testing::Types<generic::TXT, generic::SPF> Implementations; + +#ifdef TYPED_TEST_SUITE +TYPED_TEST_SUITE(Rdata_TXT_LIKE_Test, Implementations); +#else +TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations); +#endif + +TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) { + // Below we check the behavior for the "from text" constructors, both + // from std::string and with MasterLexer. The underlying implementation + // is the same, so both should work exactly same, but we confirm both + // cases. + + const std::string multi_line = "(\n \"Test-String\" )"; + const std::string escaped_txt = "Test\\045Strin\\g"; + + // test input for the lexer version + std::stringstream ss; + ss << "Test-String\n"; + ss << "\"Test-String\"\n"; // explicitly surrounded by '"'s + ss << multi_line << "\n"; // multi-line text with () + ss << escaped_txt << "\n"; // using the two types of escape with '\' + ss << "\"\"\n"; // empty string (note: still valid char-str) + ss << string(255, 'a') << "\n"; // Longest possible character-string. + ss << string(256, 'a') << "\n"; // char-string too long + ss << "\"Test-String\\\"\n"; // unbalanced quote + ss << "\"Test-String\\\"\"\n"; + this->lexer.pushSource(ss); + + // commonly used Rdata to compare below, created from wire + ConstRdataPtr const rdata = + this->rdataFactoryFromFile(RRTYPE<TypeParam>(), + RRClass("IN"), "rdata_txt_fromWire1"); + + // normal case is covered in toWireBuffer. First check the std::string + // case, then with MasterLexer. For the latter, we need to read and skip + // '\n'. These apply to most of the other cases below. + EXPECT_EQ(0, this->rdata_txt_like.compare(*rdata)); + EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // surrounding double-quotes shouldn't change the result. + EXPECT_EQ(0, this->rdata_txt_like_quoted.compare(*rdata)); + EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // multi-line input with () + EXPECT_EQ(0, TypeParam(multi_line).compare(*rdata)); + EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // for the same data using escape + EXPECT_EQ(0, TypeParam(escaped_txt).compare(*rdata)); + EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // Null character-string. + this->obuffer.clear(); + TypeParam(string("\"\"")).toWire(this->obuffer); + matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt), + this->obuffer.getData(), this->obuffer.getLength()); + + this->obuffer.clear(); + TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb). + toWire(this->obuffer); + matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt), + this->obuffer.getData(), this->obuffer.getLength()); + + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // Longest possible character-string. + this->obuffer.clear(); + TypeParam(string(255, 'a')).toWire(this->obuffer); + matchWireData(&this->wiredata_longesttxt[0], + this->wiredata_longesttxt.size(), + this->obuffer.getData(), this->obuffer.getLength()); + + this->obuffer.clear(); + TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb). + toWire(this->obuffer); + matchWireData(&this->wiredata_longesttxt[0], + this->wiredata_longesttxt.size(), + this->obuffer.getData(), this->obuffer.getLength()); + + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // Too long text for a valid character-string. + EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong); + EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, + this->loader_cb), CharStringTooLong); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // The escape character makes the double quote a part of character-string, + // so this is invalid input and should be rejected. + EXPECT_THROW(TypeParam("\"Test-String\\\""), InvalidRdataText); + EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, + this->loader_cb), MasterLexer::LexerError); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, createMultiStringsFromText) { + // Tests for "from text" variants construction with various forms of + // multi character-strings. + + std::vector<std::string > texts; + texts.push_back("\"Test-String\" \"Test-String\""); // most common form + texts.push_back("\"Test-String\"\"Test-String\""); // no space between'em + texts.push_back("\"Test-String\" Test-String"); // no '"' for one + texts.push_back("\"Test-String\"Test-String"); // and no space either + texts.push_back("Test-String \"Test-String\""); // no '"' for the other + texts.push_back("Test-String\"Test-String\""); // and no space either + + std::stringstream ss; + for (std::vector<std::string >::const_iterator it = texts.begin(); + it != texts.end(); ++it) { + ss << *it << "\n"; + } + this->lexer.pushSource(ss); + + // The corresponding Rdata built from wire to compare in the checks below. + ConstRdataPtr const rdata = + this->rdataFactoryFromFile(RRTYPE<TypeParam>(), + RRClass("IN"), "rdata_txt_fromWire3.wire"); + + // Confirm we can construct the Rdata from the test text, both from + // std::string and with lexer, and that matches the from-wire data. + for (std::vector<std::string >::const_iterator it = texts.begin(); + it != texts.end(); ++it) { + SCOPED_TRACE(*it); + EXPECT_EQ(0, TypeParam(*it).compare(*rdata)); + + EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, + this->lexer.getNextToken().getType()); + } +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, createFromTextExtra) { + // This is for the std::string version only: the input must end with EOF; + // an extra new-line will result in an exception. + EXPECT_THROW(TypeParam("\"Test-String\"\n"), InvalidRdataText); + // Same if there's a space before '\n' + EXPECT_THROW(TypeParam("\"Test-String\" \n"), InvalidRdataText); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, fromTextEmpty) { + // If the input text doesn't contain any character-string, it should be + // rejected + EXPECT_THROW(TypeParam(""), InvalidRdataText); + EXPECT_THROW(TypeParam(" "), InvalidRdataText); // even with a space + EXPECT_THROW(TypeParam("(\n)"), InvalidRdataText); // or multi-line with () +} + +void +makeLargest(vector<uint8_t>& data) { + uint8_t ch = 0; + + // create 255 sets of character-strings, each of which has the longest + // length (255bytes string + 1-byte length field) + for (int i = 0; i < 255; ++i, ++ch) { + data.push_back(255); + data.insert(data.end(), 255, ch); + } + // the last character-string should be 255 bytes (including the one-byte + // length field) in length so that the total length should be in the range + // of 16-bit integers. + data.push_back(254); + data.insert(data.end(), 254, ch); + + assert(data.size() == 65535); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) { + EXPECT_EQ(0, this->rdata_txt_like.compare( + *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), + RRClass("IN"), + "rdata_txt_fromWire1"))); + + // Empty character string + EXPECT_EQ(0, this->rdata_txt_like_empty.compare( + *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), + RRClass("IN"), + "rdata_txt_fromWire2.wire"))); + + // Multiple character strings + this->obuffer.clear(); + this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"), + "rdata_txt_fromWire3.wire")->toWire(this->obuffer); + // the result should be 'wiredata_txt' repeated twice + vector<uint8_t> expected_data(wiredata_txt_like, wiredata_txt_like + + sizeof(wiredata_txt_like)); + expected_data.insert(expected_data.end(), wiredata_txt_like, + wiredata_txt_like + sizeof(wiredata_txt_like)); + matchWireData(&expected_data[0], expected_data.size(), + this->obuffer.getData(), this->obuffer.getLength()); + + // Largest length of data. There's nothing special, but should be + // constructed safely, and the content should be identical to the original + // data. + vector<uint8_t> largest_txt_like_data; + makeLargest(largest_txt_like_data); + InputBuffer ibuffer(&largest_txt_like_data[0], + largest_txt_like_data.size()); + TypeParam largest_txt_like(ibuffer, largest_txt_like_data.size()); + this->obuffer.clear(); + largest_txt_like.toWire(this->obuffer); + matchWireData(&largest_txt_like_data[0], largest_txt_like_data.size(), + this->obuffer.getData(), this->obuffer.getLength()); + + // rdlen parameter is out of range. This is a rare event because we'd + // normally call the constructor via a polymorphic wrapper, where the + // length is validated. But this should be checked explicitly. + InputBuffer ibuffer2(&largest_txt_like_data[0], + largest_txt_like_data.size()); + EXPECT_THROW(TypeParam(ibuffer2, 65536), InvalidRdataLength); + + // RDATA is empty, which is invalid for TXT_LIKE. + EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"), + "rdata_txt_fromWire4.wire"), + DNSMessageFORMERR); + + // character-string length is too large, which could cause overrun. + EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"), + "rdata_txt_fromWire5.wire"), + DNSMessageFORMERR); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) { + EXPECT_EQ(0, this->rdata_txt_like.compare( + *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(), + "Test-String"))); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) { + this->rdata_txt_like.toWire(this->obuffer); + matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like), + this->obuffer.getData(), this->obuffer.getLength()); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) { + this->rdata_txt_like.toWire(this->renderer); + matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like), + this->renderer.getData(), this->renderer.getLength()); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, toText) { + EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText()); + EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText()); + EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText()); + + // Check escape behavior + const TypeParam double_quotes("Test-String\"Test-String\""); + EXPECT_EQ("\"Test-String\" \"Test-String\"", double_quotes.toText()); + const TypeParam semicolon("Test-String\\;Test-String"); + EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText()); + const TypeParam backslash("Test-String\\\\Test-String"); + EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText()); + const TypeParam before_x20("Test-String\\031Test-String"); + EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText()); + const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\""); + EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText()); + const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String"); + EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText()); + const TypeParam after_x7e("Test-String\\127Test-String"); + EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText()); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) { + TypeParam rdata1("assignment1"); + TypeParam rdata2("assignment2"); + rdata1 = rdata2; + EXPECT_EQ(0, rdata2.compare(rdata1)); + + // Check if the copied data is valid even after the original is deleted + TypeParam* rdata3 = new TypeParam(rdata1); + TypeParam rdata4("assignment3"); + rdata4 = *rdata3; + delete rdata3; + EXPECT_EQ(0, rdata4.compare(rdata1)); + + // Self assignment + rdata2 = *&rdata2; + EXPECT_EQ(0, rdata2.compare(rdata1)); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, compare) { + string const txt1("aaaaaaaa"); + string const txt2("aaaaaaaaaa"); + string const txt3("bbbbbbbb"); + string const txt4(129, 'a'); + string const txt5(128, 'b'); + + EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0); + + EXPECT_LT(TypeParam("\"\"").compare(TypeParam(txt1)), 0); + EXPECT_GT(TypeParam(txt1).compare(TypeParam("\"\"")), 0); + + EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0); + EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0); + + EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt3)), 0); + EXPECT_GT(TypeParam(txt3).compare(TypeParam(txt1)), 0); + + // we're comparing the data raw, starting at the length octet, so a shorter + // string sorts before a longer one no matter the lexicopraphical order + EXPECT_LT(TypeParam(txt3).compare(TypeParam(txt2)), 0); + EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt3)), 0); + + // to make sure the length octet compares unsigned + EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt4)), 0); + EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt1)), 0); + + EXPECT_LT(TypeParam(txt5).compare(TypeParam(txt4)), 0); + EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt5)), 0); + + // comparison attempt between incompatible RR types should be rejected + EXPECT_THROW(TypeParam(txt1).compare(*this->rdata_nomatch), + bad_cast); +} + +} |