diff options
author | Piotrek Zadroga <piotrek@isc.org> | 2024-02-19 21:15:48 +0100 |
---|---|---|
committer | Piotrek Zadroga <piotrek@isc.org> | 2024-02-23 17:14:06 +0100 |
commit | 119c973761eb71f25219cda0a0f3d79fca8cdf57 (patch) | |
tree | 0c3ead707b1e924fb8fe2eed7fd313363de8306b | |
parent | [#3141] option definition UTs fix (diff) | |
download | kea-119c973761eb71f25219cda0a0f3d79fca8cdf57.tar.xz kea-119c973761eb71f25219cda0a0f3d79fca8cdf57.zip |
[#3141] DNRv4 UTs edited
-rw-r--r-- | src/lib/dhcp/option4_dnr.cc | 190 | ||||
-rw-r--r-- | src/lib/dhcp/option4_dnr.h | 94 | ||||
-rw-r--r-- | src/lib/dhcp/tests/option4_dnr_unittest.cc | 712 | ||||
-rw-r--r-- | src/lib/dhcp/tests/option6_dnr_unittest.cc | 13 |
4 files changed, 581 insertions, 428 deletions
diff --git a/src/lib/dhcp/option4_dnr.cc b/src/lib/dhcp/option4_dnr.cc index 0976152481..e2bc68424d 100644 --- a/src/lib/dhcp/option4_dnr.cc +++ b/src/lib/dhcp/option4_dnr.cc @@ -8,7 +8,6 @@ #include <dhcp/option4_dnr.h> #include <dns/labelsequence.h> -#include <util/strutil.h> using namespace isc::asiolink; using namespace isc::util; @@ -216,32 +215,6 @@ DnrInstance::DnrInstance(Option::Universe universe) initMembers(); } -DnrInstance::DnrInstance(Option::Universe universe, - const uint16_t service_priority, - const std::string& adn, - const DnrInstance::AddressContainer& ip_addresses, - const std::string& svc_params) - : universe_(universe), dnr_instance_data_length_(0), service_priority_(service_priority), - adn_length_(0), addr_length_(0), ip_addresses_(ip_addresses), svc_params_length_(0), - adn_only_mode_(true), svc_params_(svc_params), alpn_http_(false), - dnr_instance_data_length_size_(0), adn_length_size_(0), addr_length_size_(0), - minimal_length_(0) { - initMembers(); - setAdn(adn); - checkFields(); -} - -DnrInstance::DnrInstance(Option::Universe universe, - const uint16_t service_priority, - const std::string& adn) - : universe_(universe), dnr_instance_data_length_(0), service_priority_(service_priority), - adn_length_(0), addr_length_(0), svc_params_length_(0), adn_only_mode_(true), - alpn_http_(false), dnr_instance_data_length_size_(0), adn_length_size_(0), - addr_length_size_(0), minimal_length_(0) { - initMembers(); - setAdn(adn); -} - void DnrInstance::packAdn(OutputBuffer& buf) const { if (!adn_) { @@ -282,38 +255,6 @@ DnrInstance::getAdnAsText() const { } void -DnrInstance::setAdn(const std::string& adn) { - std::string trimmed_adn = str::trim(adn); - if (trimmed_adn.empty()) { - isc_throw(InvalidOptionDnrDomainName, getLogPrefix() - << "Mandatory Authentication Domain Name fully " - "qualified domain-name must not be empty"); - } - - try { - adn_.reset(new isc::dns::Name(trimmed_adn, true)); - } catch (const Exception& ex) { - isc_throw(InvalidOptionDnrDomainName, getLogPrefix() - << "Failed to parse " - "fully qualified domain-name from string - " - << ex.what()); - } - - size_t adn_len = 0; - isc::dns::LabelSequence label_sequence(*adn_); - label_sequence.getData(&adn_len); - if (adn_len > std::numeric_limits<uint16_t>::max()) { - isc_throw(InvalidOptionDnrDomainName, getLogPrefix() << "Given ADN FQDN length " << adn_len - << " is bigger than uint_16 MAX"); - } - - adn_length_ = adn_len; - if (universe_ == Option::V4) { - setDnrInstanceDataLength(); - } -} - -void DnrInstance::unpackAdn(OptionBufferConstIter& begin, OptionBufferConstIter end) { OpaqueDataTuple::LengthFieldType lft = OptionDataTypeUtil::getTupleLenFieldType(universe_); OpaqueDataTuple adn_tuple(lft); @@ -346,119 +287,6 @@ DnrInstance::unpackAdn(OptionBufferConstIter& begin, OptionBufferConstIter end) begin += adn_length_ + getAdnLengthSize(); } -void -DnrInstance::checkSvcParams(bool from_wire_data) { - std::string svc_params = str::trim(svc_params_); - if (svc_params.empty()) { - isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() - << "Provided Svc Params field is empty"); - } - - if (!from_wire_data) { - // If Service Params field was not parsed from on-wire data, - // but actually was provided with ctor, let's calculate svc_params_length_. - auto svc_params_len = svc_params.length(); - if (svc_params_len > std::numeric_limits<uint16_t>::max()) { - isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() - << "Given Svc Params length " << svc_params_len - << " is bigger than uint_16 MAX"); - } - - svc_params_length_ = svc_params_len; - // If Service Params field was not parsed from on-wire data, - // but actually was provided with ctor, let's replace it with trimmed value. - svc_params_ = svc_params; - } - - // SvcParams are a whitespace-separated list, with each SvcParam - // consisting of a SvcParamKey=SvcParamValue pair or a standalone SvcParamKey. - // SvcParams in presentation format MAY appear in any order, but keys MUST NOT be repeated. - - // Let's put all elements of a whitespace-separated list into a vector. - std::vector<std::string> tokens = str::tokens(svc_params, " "); - - // Set of keys used to check if a key is not repeated. - std::unordered_set<std::string> keys; - // String sanitizer is used to check keys syntax. - str::StringSanitizerPtr sanitizer; - // SvcParamKeys are lower-case alphanumeric strings. Key names - // contain 1-63 characters from the ranges "a"-"z", "0"-"9", and "-". - std::string regex = "[^a-z0-9-]"; - sanitizer.reset(new str::StringSanitizer(regex, "")); - - // Now let's check each SvcParamKey=SvcParamValue pair. - for (const std::string& token : tokens) { - std::vector<std::string> key_val = str::tokens(token, "="); - if (key_val.size() > 2) { - isc_throw(InvalidOptionDnrSvcParams, - getLogPrefix() << "Wrong Svc Params syntax - more than one " - "equals sign found in SvcParamKey=SvcParamValue pair"); - } - - // SvcParam Key related checks come below. - std::string key = key_val[0]; - if (key.length() > 63) { - isc_throw(InvalidOptionDnrSvcParams, - getLogPrefix() << "Wrong Svc Params syntax - key had more " - << "than 63 characters - " << key); - } - - if (FORBIDDEN_SVC_PARAMS.find(key) != FORBIDDEN_SVC_PARAMS.end()) { - isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() << "Wrong Svc Params syntax - key " - << key << " must not be used"); - } - - auto insert_res = keys.insert(key); - if (!insert_res.second) { - isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() << "Wrong Svc Params syntax - key " - << key << " was duplicated"); - } - - std::string sanitized_key = sanitizer->scrub(key); - if (sanitized_key.size() < key.size()) { - isc_throw(InvalidOptionDnrSvcParams, - getLogPrefix() - << "Wrong Svc Params syntax - invalid character used in key - " << key); - } - } -} - -void -DnrInstance::checkFields() { - if (svc_params_.empty() && ip_addresses_.empty()) { - // ADN only mode, nothing more to do. - return; - } - - if (!svc_params_.empty() && ip_addresses_.empty()) { - // As per draft-ietf-add-dnr 3.1.8: - // If additional data is supplied (i.e. not ADN only mode), - // the option includes at least one valid IP address. - isc_throw(OutOfRange, getLogPrefix() << "No IP address given. Since this is not ADN only " - "mode, at least one valid IP address must " - "be included"); - } - - if (!svc_params_.empty()) { - checkSvcParams(false); - } - - adn_only_mode_ = false; - const uint8_t addr_field_len = (universe_ == Option::V4) ? V4ADDRESS_LEN : V6ADDRESS_LEN; - const uint16_t max_addr_len = (universe_ == Option::V4) ? std::numeric_limits<uint8_t>::max() : - std::numeric_limits<uint16_t>::max(); - auto addr_len = ip_addresses_.size() * addr_field_len; - if (addr_len > max_addr_len) { - isc_throw(OutOfRange, getLogPrefix() << "Given IP addresses length " << addr_len - << " is bigger than MAX " << max_addr_len); - } - - addr_length_ = addr_len; - if (universe_ == Option::V4) { - setDnrInstanceDataLength(); - } -} - std::string DnrInstance::svcParamValAsText(const std::pair<uint16_t, OpaqueDataTuple>& svc_param) const { OptionBufferConstIter alpn_begin; @@ -503,7 +331,7 @@ DnrInstance::svcParamValAsText(const std::pair<uint16_t, OpaqueDataTuple>& svc_p break; case 7: // dohpath - // convertion not needed, let's return data as string + // conversion not needed, let's return data as string ret = svc_param.second.getText(); break; } @@ -661,7 +489,7 @@ DnrInstance::unpackSvcParams(OptionBufferConstIter& begin, OptionBufferConstIter } // Check if SvcParamKey usage is supported by DNR DHCP option. - // Note that SUPPORTED_SVC_PARAMS set may expand in future. + // Note that SUPPORTED_SVC_PARAMS set may expand in the future. if (SUPPORTED_SVC_PARAMS.find(num_svc_param_key) == SUPPORTED_SVC_PARAMS.end()) { isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() << "Wrong Svc Params syntax - key " << svc_param_key @@ -702,8 +530,6 @@ DnrInstance::unpackSvcParams(OptionBufferConstIter& begin, OptionBufferConstIter svc_params_map_.insert(std::make_pair(num_svc_param_key, svc_param_tuple)); begin += svc_param_tuple.getTotalLength(); } - - begin += svc_params_length_; } } @@ -819,6 +645,7 @@ DnrInstance::parseDnrInstanceConfigData(const std::string& config_txt) { std::vector<std::string> alpn_ids_tokens; OpaqueDataTuple svc_param_val_tuple(OpaqueDataTuple::LENGTH_2_BYTES); OutputBuffer out_buf(2); + std::vector<uint8_t> utf8_encoded; for (auto const& svc_param_pair : svc_params_pairs) { std::vector<std::string> key_val_tokens = str::tokens(str::trim(svc_param_pair), "="); if (key_val_tokens.size() != 2) { @@ -848,7 +675,7 @@ DnrInstance::parseDnrInstanceConfigData(const std::string& config_txt) { } // Check if SvcParamKey usage is supported by DNR DHCP option. - // Note that SUPPORTED_SVC_PARAMS set may expand in future. + // Note that SUPPORTED_SVC_PARAMS set may expand in the future. uint16_t num_svc_param_key = svc_params_iterator->second; if (SUPPORTED_SVC_PARAMS.find(num_svc_param_key) == SUPPORTED_SVC_PARAMS.end()) { isc_throw(InvalidOptionDnrSvcParams, @@ -947,10 +774,17 @@ DnrInstance::parseDnrInstanceConfigData(const std::string& config_txt) { // We hope to have URI containing < 0x80 ASCII chars, however to be sure // and to be inline with RFC9461 Section 5, let's encode the dohpath with utf8. - auto const utf8_encoded = encode::encodeUtf8(svc_param_val); + utf8_encoded = encode::encodeUtf8(svc_param_val); svc_param_val_tuple.append(utf8_encoded.begin(), utf8_encoded.size()); svc_params_map_.insert(std::make_pair(num_svc_param_key, svc_param_val_tuple)); break; + default: + // This should not happen because we check if num_svc_param_key is + // in SUPPORTED_SVC_PARAMS before. But in case new SvcParam appears in Supported, + // and is not handled here... + isc_throw(InvalidOptionDnrSvcParams, + getLogPrefix() << "Wrong Svc Params syntax - key " << num_svc_param_key + << " not supported yet."); } } diff --git a/src/lib/dhcp/option4_dnr.h b/src/lib/dhcp/option4_dnr.h index 682f68a710..0db59e8808 100644 --- a/src/lib/dhcp/option4_dnr.h +++ b/src/lib/dhcp/option4_dnr.h @@ -103,41 +103,6 @@ public: /// @param universe either V4 or V6 Option universe explicit DnrInstance(Option::Universe universe); - /// @brief Constructor of the DNR Instance with all fields from params. - /// - /// Constructor of the DNR Instance where all fields - /// i.e. Service priority, ADN, IP address(es) and Service params - /// are provided as ctor parameters. - /// - /// @param universe either V4 or V6 Option universe - /// @param service_priority Service priority - /// @param adn ADN FQDN - /// @param ip_addresses Container of IP addresses - /// @param svc_params Service Parameters - /// - /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN - /// @throw InvalidOptionDnrSvcParams Thrown when @c checkSvcParams(from_wire_data) throws - /// @throw OutOfRange Thrown in case of no IP addresses found or when IP addresses length - /// is too big - DnrInstance(Option::Universe universe, - uint16_t service_priority, - const std::string& adn, - const AddressContainer& ip_addresses, - const std::string& svc_params); - - /// @brief Constructor of the DNR Instance in ADN only mode. - /// - /// Constructor of the DNR Instance in ADN only mode - /// i.e. only Service priority and ADN FQDN - /// are provided as ctor parameters. - /// - /// @param universe either V4 or V6 Option universe - /// @param service_priority Service priority - /// @param adn ADN FQDN - /// - /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN - DnrInstance(Option::Universe universe, uint16_t service_priority, const std::string& adn); - /// @brief Default destructor. virtual ~DnrInstance() = default; @@ -241,19 +206,6 @@ public: return (adn_only_mode_); } - /// @brief Sets Authentication domain name from given string. - /// - /// Sets FQDN of the encrypted DNS resolver from given string. - /// It may throw an exception if parsing of the FQDN fails or if - /// provided FQDN length is bigger than uint16_t Max. - /// It also calculates and sets value of Addr length field. - /// - /// @param adn string representation of ADN FQDN - /// - /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN - /// from given string. - void setAdn(const std::string& adn); - /// @brief Setter of the @c adn_only_mode_ field. /// /// @param adn_only_mode enabled/disabled setting @@ -342,34 +294,6 @@ public: /// @param end end of the buffer from which the field will be read void unpackSvcParams(OptionBufferConstIter& begin, OptionBufferConstIter end); - /// @brief Checks SvcParams field if encoded correctly and throws in case of issue found. - /// - /// The field should be encoded following the rules in - /// Section 2.1 of [I-D.ietf-dnsop-svcb-https]. SvcParams are - /// a whitespace-separated list, with each SvcParam consisting of - /// a SvcParamKey=SvcParamValue pair or a standalone SvcParamKey. - /// - /// @note It is user's responsibility to provide correct configuration - /// of @c SvcParams as described in Section 2.1 of [I-D.ietf-dnsop-svcb-https]. - /// Currently, SvcParamValue is not verified. Proper syntax of SvcParamValue - /// is described in Appendix A of [I-D.ietf-dnsop-svcb-https]. - /// - /// @param from_wire_data used to determine whether SvcParams data comes - /// from unpacked wire data or from ctor param - /// - /// @throw InvalidOptionDnrSvcParams Thrown in case of any issue found when checking - /// @c ServiceParams field syntax - void checkSvcParams(bool from_wire_data = true); - - /// @brief Checks IP address(es) field if data is correct and throws in case of issue found. - /// - /// Fields lengths are also calculated and saved to member variables. - /// - /// @throw OutOfRange Thrown in case of no IP addresses found or when IP addresses length - /// is too big - /// @throw InvalidOptionDnrSvcParams Thrown when @c checkSvcParams(from_wire_data) throws - void checkFields(); - /// @brief Adds IP address to @c ip_addresses_ container. /// /// @param ip_address IP address to be added @@ -392,7 +316,7 @@ public: /// /// Note that this function parses single DnrInstance. For DNRv4 it is possible to have more /// than one DnrInstance per one Option. In that case this function must be called for each - /// DnrInstance. + /// DnrInstance config. /// /// @param config_txt convenient notation of the option data received as string /// @@ -445,12 +369,6 @@ protected: /// fields are not present if the ADN-only mode is used. bool adn_only_mode_; - /// @brief Service Parameters (SvcParams) (variable length) as string. - /// - /// Specifies a set of service parameters that are encoded - /// following the rules in Section 2.1 of [I-D.ietf-dnsop-svcb-https]. - std::string svc_params_; - /// @brief Service Parameters (SvcParams) (variable length) as on-wire data buffer. /// /// Specifies a set of service parameters that are encoded @@ -465,7 +383,7 @@ protected: /// (Defined values are in Section 14.3.2 of RFC9460 - listed in @c SVC_PARAMS). /// The value is an OpaqueDataTuple containing: /// - the length of the SvcParamValue as an uint_16 integer in network byte order - /// - data buffer the SvcParamValue in a format determined by the SvcParamKey. + /// - data buffer with the SvcParamValue in a format determined by the SvcParamKey. std::map<uint16_t, OpaqueDataTuple> svc_params_map_; /// @brief Calculates and returns length of DNR Instance data in octets. @@ -557,14 +475,6 @@ public: OptionBufferConstIter end, bool convenient_notation = false); - /// @brief Constructor of the empty %Option. - /// - /// No DNR instances are included in the %Option when using this ctor. - /// They must be added afterwards. - /// No fields data included in the %Option when using this ctor. - /// They must be added afterwards. - Option4Dnr() : Option(V4, DHO_V4_DNR) {} - /// @brief Adds given DNR instance to Option's DNR Instance container. /// @param dnr_instance DNR instance to be added void addDnrInstance(DnrInstance& dnr_instance); diff --git a/src/lib/dhcp/tests/option4_dnr_unittest.cc b/src/lib/dhcp/tests/option4_dnr_unittest.cc index 60f8cf31c2..2fb616a4ff 100644 --- a/src/lib/dhcp/tests/option4_dnr_unittest.cc +++ b/src/lib/dhcp/tests/option4_dnr_unittest.cc @@ -16,32 +16,20 @@ using namespace isc::asiolink; namespace { -// This test verifies constructor of the empty Option4Dnr class. -TEST(Option4DnrTest, emptyCtor) { - // Create option instance. Check that constructor doesn't throw. - Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr())); - ASSERT_TRUE(option); +// This test verifies constructor from convenient string notation +// with one ADN-only-mode DNR instance. +TEST(Option4DnrTest, fromConfigCtorOneAdnOnlyModeInstance) { + // Prepare example config. + const std::string config = "1, myhost1.example.com."; - // Check if member variables were correctly set by ctor. - EXPECT_EQ(Option::V4, option->getUniverse()); - EXPECT_EQ(DHO_V4_DNR, option->getType()); -} + OptionBuffer in_buf; + in_buf.assign(config.begin(), config.end()); -// This test verifies constructor of the empty Option4Dnr class together -// with adding ADN-only-mode DNR instance to option's DNR instances. -TEST(Option4DnrTest, oneAdnOnlyModeInstance) { // Create option instance. Check that constructor doesn't throw. Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr())); + EXPECT_NO_THROW(option.reset(new Option4Dnr(in_buf.begin(), in_buf.end(), true))); ASSERT_TRUE(option); - // Prepare example DNR instance to add. - DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); - - // Add DNR instance. - option->addDnrInstance(dnr_1); - // Check if member variables were correctly set inside DNR instances. EXPECT_EQ(1, option->getDnrInstances().size()); EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority()); @@ -67,28 +55,26 @@ TEST(Option4DnrTest, oneAdnOnlyModeInstance) { option->toText()); } -// This test verifies constructor of the empty Option4Dnr class together -// with adding multiple ADN-only-mode DNR instances to option's DNR instances. -TEST(Option4DnrTest, multipleAdnOnlyModeInstances) { +// This test verifies constructor from convenient string notation +// with multiple ADN-only-mode DNR instances. +TEST(Option4DnrTest, fromConfigCtorMultipleAdnOnlyModeInstances) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com. | " + "3, myhost3.example.com."; + + OptionBuffer in_buf; + in_buf.assign(config.begin(), config.end()); + // Create option instance. Check that constructor doesn't throw. Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr())); + EXPECT_NO_THROW(option.reset(new Option4Dnr(in_buf.begin(), in_buf.end(), true))); ASSERT_TRUE(option); // Check if member variables were correctly set by ctor. EXPECT_EQ(Option::V4, option->getUniverse()); EXPECT_EQ(DHO_V4_DNR, option->getType()); - // Prepare example DNR instances to add. - DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); - DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com."); - DnrInstance dnr_3 = DnrInstance(Option::V4, 3, "myhost3.example.com."); - - // Add DNR instances. - option->addDnrInstance(dnr_1); - option->addDnrInstance(dnr_2); - option->addDnrInstance(dnr_3); - // Check if member variables were correctly set inside DNR instances. EXPECT_EQ(3, option->getDnrInstances().size()); EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority()); @@ -128,29 +114,24 @@ TEST(Option4DnrTest, multipleAdnOnlyModeInstances) { option->toText()); } -// This test verifies constructor of the empty Option4Dnr class together -// with adding to option's DNR instances: +// This test verifies constructor from convenient string notation +// with two DNR instances: // 1. ADN-only-mode DNR instance // 2. All fields included (IP addresses and service params also) DNR instance. -TEST(Option4DnrTest, mixedDnrInstances) { +TEST(Option4DnrTest, fromConfigCtorMixedDnrInstances) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, " + "alpn=h3 port=1234 dohpath=/q{?dns}"; + + OptionBuffer in_buf; + in_buf.assign(config.begin(), config.end()); + // Create option instance. Check that constructor doesn't throw. Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr())); + EXPECT_NO_THROW(option.reset(new Option4Dnr(in_buf.begin(), in_buf.end(), true))); ASSERT_TRUE(option); - // Prepare example DNR instance to add. - DnrInstance::AddressContainer addresses; - addresses.push_back(IOAddress("192.168.0.1")); - addresses.push_back(IOAddress("192.168.0.2")); - std::string svc_params = "key123=val key234=val2 key345"; - - DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); - DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com.", addresses, svc_params); - - // Add DNR instance. - option->addDnrInstance(dnr_1); - option->addDnrInstance(dnr_2); - // Check if member variables were correctly set inside DNR instances. EXPECT_EQ(2, option->getDnrInstances().size()); EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority()); @@ -164,46 +145,60 @@ TEST(Option4DnrTest, mixedDnrInstances) { EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength()); EXPECT_EQ(2, option->getDnrInstances()[1].getAddresses().size()); EXPECT_EQ(8, option->getDnrInstances()[1].getAddrLength()); - EXPECT_EQ(29, option->getDnrInstances()[1].getSvcParamsLength()); EXPECT_EQ("192.168.0.1", option->getDnrInstances()[1].getAddresses()[0].toText()); EXPECT_EQ("192.168.0.2", option->getDnrInstances()[1].getAddresses()[1].toText()); + + // Reference SvcParams on-wire data buffer. + const OptionBuffer svc_params = { + 0, 1, // SvcParamKey alpn + 0, 3, // SvcParamVal Len + 2, 'h', '3', // 2 octets long alpn-id http/3 + 0, 3, // SvcParamKey port + 0, 2, // SvcParamVal Len + 0x4, 0xD2, // 1234 in hex + 0, 7, // SvcParamKey dohpath + 0, 8, // SvcParamVal Len + '/', 'q', '{', '?', 'd', 'n', 's', '}' // dohpath value + }; + EXPECT_EQ(svc_params, option->getDnrInstances()[1].getSvcParams()); + EXPECT_EQ(svc_params.size(), option->getDnrInstances()[1].getSvcParamsLength()); // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len // is set to ADN Len (21) + 3 = 24. // expected len: 1x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers) // + 24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len) + 1 (Addr Len) - // + 8 (IP addresses) + 29 (svc params) - // = 92 - EXPECT_EQ(92, option->len()); + // + 8 (IP addresses) + 25 (svc params) + // = 88 + EXPECT_EQ(88, option->len()); // BTW let's check if toText() works ok. // toText() len does not count in headers len. - EXPECT_EQ("type=162(V4_DNR), len=90, " + EXPECT_EQ("type=162(V4_DNR), len=86, " "DNR Instance 1(Instance len=24, service_priority=1, " "adn_length=21, adn='myhost1.example.com.'), " - "DNR Instance 2(Instance len=62, service_priority=2, " + "DNR Instance 2(Instance len=58, service_priority=2, " "adn_length=21, adn='myhost2.example.com.', " "addr_length=8, address(es): 192.168.0.1 192.168.0.2, " - "svc_params='key123=val key234=val2 key345')", + "svc_params='alpn=h3 port=1234 dohpath=/q{?dns}')", option->toText()); } // This test verifies option packing into wire data. // Provided data to pack contains 1 DNR instance: // 1. ADN only mode -TEST(Option4DnrTest, packOneAdnOnlyModeInstance) { +TEST(Option4DnrTest, fromConfigCtorPackOneAdnOnlyModeInstance) { + // Prepare example config. + const std::string config = "1, myhost1.example.com."; + + OptionBuffer in_buf; + in_buf.assign(config.begin(), config.end()); + // Create option instance. Check that constructor doesn't throw. Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr())); + EXPECT_NO_THROW(option.reset(new Option4Dnr(in_buf.begin(), in_buf.end(), true))); ASSERT_TRUE(option); - // Prepare example DNR instance to add. - DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); - - // Add DNR instance. - option->addDnrInstance(dnr_1); - // Prepare on-wire format of the option. isc::util::OutputBuffer buf(10); ASSERT_NO_THROW(option->pack(buf)); @@ -233,26 +228,24 @@ TEST(Option4DnrTest, packOneAdnOnlyModeInstance) { // 1. ADN only mode // 2. ADN only mode // 3. ADN only mode -TEST(Option4DnrTest, packMultipleAdnOnlyModeInstances) { +TEST(Option4DnrTest, fromConfigCtorPackMultipleAdnOnlyModeInstances) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com. | " + "3, myhost3.example.com."; + + OptionBuffer in_buf; + in_buf.assign(config.begin(), config.end()); + // Create option instance. Check that constructor doesn't throw. Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr())); + EXPECT_NO_THROW(option.reset(new Option4Dnr(in_buf.begin(), in_buf.end(), true))); ASSERT_TRUE(option); // Check if member variables were correctly set by ctor. EXPECT_EQ(Option::V4, option->getUniverse()); EXPECT_EQ(DHO_V4_DNR, option->getType()); - // Prepare example DNR instances to add. - DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); - DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com."); - DnrInstance dnr_3 = DnrInstance(Option::V4, 3, "myhost3.example.com."); - - // Add DNR instances. - option->addDnrInstance(dnr_1); - option->addDnrInstance(dnr_2); - option->addDnrInstance(dnr_3); - // Prepare on-wire format of the option. isc::util::OutputBuffer buf(10); ASSERT_NO_THROW(option->pack(buf)); @@ -293,79 +286,459 @@ TEST(Option4DnrTest, packMultipleAdnOnlyModeInstances) { // Provided data to pack contains 2 DNR instances: // 1. ADN only mode // 2. All fields included (IP addresses and service params also). -TEST(Option4DnrTest, packMixedDnrInstances) { +TEST(Option4DnrTest, fromConfigCtorPackMixedDnrInstances) { // Create option instance. Check that constructor doesn't throw. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,doq"; + OptionBuffer opt_buf; + opt_buf.assign(config.begin(), config.end()); Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr())); + EXPECT_NO_THROW(option.reset(new Option4Dnr(opt_buf.begin(), opt_buf.end(), true))); ASSERT_TRUE(option); - // Prepare example DNR instance to add. - DnrInstance::AddressContainer addresses; - addresses.push_back(IOAddress("192.168.0.1")); - addresses.push_back(IOAddress("192.168.0.2")); - std::string svc_params = "key123=val key234=val2 key345"; - - DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); - DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com.", addresses, svc_params); - - // Add DNR instance. - option->addDnrInstance(dnr_1); - option->addDnrInstance(dnr_2); - // Prepare on-wire format of the option. - isc::util::OutputBuffer buf(10); - ASSERT_NO_THROW(option->pack(buf)); + isc::util::OutputBuffer out_buf(10); + ASSERT_NO_THROW(option->pack(out_buf)); // Prepare reference data. const uint8_t ref_data[] = { - DHO_V4_DNR, // Option code - 90, // Option len=90 dec - 0x00, 24, // DNR Instance Data Len - 0x00, 0x01, // Service priority is 1 dec - 21, // ADN Length is 21 dec - 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. - 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. - 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. - 0x00, 62, // DNR Instance Data Len - 0x00, 0x02, // Service priority is 2 dec - 21, // ADN Length is 21 dec - 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. - 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. - 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. - 8, // Addr Len - 192, 168, 0, 1, // IP address 1 - 192, 168, 0, 2, // IP address 2 - 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params - 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params - '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params - 'e', 'y', '3', '4', '5' // Svc Params + DHO_V4_DNR, // Option code + 73, // Option len + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 45, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 8, // Addr Len + 192, 168, 0, 1, // IP address 1 + 192, 168, 0, 2, // IP address 2 + 0, 1, // SvcParamKey alpn + 0, 8, // SvcParamVal Len + 3, 'd', 'o', 't', // 3 octets long alpn-id dot + 3, 'd', 'o', 'q' // 3 octets long alpn-id doq + }; size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); // Check if the buffer has the same length as the reference data, // so as they can be compared directly. - ASSERT_EQ(ref_data_size, buf.getLength()); - EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); + ASSERT_EQ(ref_data_size, out_buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, out_buf.getData(), out_buf.getLength())); } -// This test verifies option constructor from wire data. -TEST(Option4DnrTest, onWireDataCtor) { - // Prepare data to decode - ADN only mode 1 DNR instance. - const uint8_t buf_data[] = { +// This test verifies option packing into wire data. +// Provided data to pack contains 2 DNR instances: +// 1. ADN only mode +// 2. Almost all fields included (IP addresses but no service params). +TEST(Option4DnrTest, fromConfigCtorPackMixedDnrInstancesTwo) { + // Create option instance. Check that constructor doesn't throw. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2"; + OptionBuffer opt_buf; + opt_buf.assign(config.begin(), config.end()); + Option4DnrPtr option; + EXPECT_NO_THROW(option.reset(new Option4Dnr(opt_buf.begin(), opt_buf.end(), true))); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer out_buf(10); + ASSERT_NO_THROW(option->pack(out_buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + DHO_V4_DNR, // Option code + 61, // Option len 0x00, 24, // DNR Instance Data Len 0x00, 0x01, // Service priority is 1 dec 21, // ADN Length is 21 dec 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. - 0x03, 0x63, 0x6F, 0x6D, 0x00 // com. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 33, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 8, // Addr Len + 192, 168, 0, 1, // IP address 1 + 192, 168, 0, 2 // IP address 2 }; - OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, out_buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, out_buf.getData(), out_buf.getLength())); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has only Svc Priority. +TEST(Option4DnrTest, fromConfigCtorNotEnoughFields) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some\\|path{?dns} | " // "pipe char" escape is tested here + "3"; // 3rd dnr instance config has only Svc Priority + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + BadValue); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has too many comma separated fields. +TEST(Option4DnrTest, fromConfigCtorTooManyFields) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "3, test.com., 10.2.3.4, port=1234, extra"; // too many fields + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + BadValue); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed Svc priority. +TEST(Option4DnrTest, fromConfigCtorWrongSvcPriority) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "what?, test.com., 10.2.3.4, port=1234"; // non-parsable Svc Priority + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + BadValue); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed ADN. +TEST(Option4DnrTest, fromConfigCtorWrongAdn) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, , 10.2.3.4, port=1234"; // wrong ADN - empty + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrDomainName); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed IP address. +TEST(Option4DnrTest, fromConfigCtorWrongIpAddr) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , what?, port=1234"; // wrong IP address + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + BadValue); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has no IP address. +TEST(Option4DnrTest, fromConfigCtorNoIpAddr) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , , port=1234"; // no IP address + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + BadValue); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParams) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4, ="; // wrong SvcParams + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams - forbidden SvcParam. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParamsForbiddenSvcParam) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4, ipv4hint=1"; // forbidden SvcParam + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams - unknown SvcParam. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParamsUnknownSvcParam) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4, unknown=1"; // unknown SvcParam + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams - not supported SvcParam. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParamsNotSupportedSvcParam) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4, ech=1"; // ech is not supported in DNR + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams - duplicated SvcParam. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParamsDuplicatedSvcParam) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4," + " port=1234 port=2345"; // duplicated SvcParam + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams - unknown AlpnId. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParamsUnknownAlpnId) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4, alpn=dot\\,unknown"; // unknown alpn-id + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams - dohpath is missing. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParamsSkippedDohpath) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4, alpn=h3"; // dohpath must be there + // when any of http alpn-ids + // are included + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams - port can't be parsed. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParamsWrongPort) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4, port=what?"; // port must be uint_16 + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies that option constructor throws +// an exception when config provided via ctor is malformed +// - DnrInstance config has malformed SvcParams - dns var missing in dohpath. +TEST(Option4DnrTest, fromConfigCtorWrongSvcParamsMissingVarInDohpath) { + // Prepare example config. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, alpn=dot\\,h3 " + "dohpath=/some-path{?dns} | " + "100, test.net. , 10.2.3.4, alpn=dot\\,h3 " + "dohpath=/some-path"; // dns var is missing in the uri of dohpath + + OptionBuffer buf; + buf.assign(config.begin(), config.end()); + + // Create option instance. Check that constructor throws. + Option4DnrPtr option; + EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end(), true)), + InvalidOptionDnrSvcParams); + ASSERT_FALSE(option); +} + +// This test verifies option packing into wire data. +// Provided data to pack contains 2 DNR instances: +// 1. ADN only mode +// 2. All fields included and SvcParams are given in wrong order. +TEST(Option4DnrTest, fromConfigCtorPackUnorderedSvcParams) { // Create option instance. Check that constructor doesn't throw. + const std::string config = "1, myhost1.example.com. | " + "2, myhost2.example.com., 192.168.0.1 192.168.0.2, port=85" + " alpn=dot\\,doq"; // SvcParams are NOT in increasing order + OptionBuffer opt_buf; + opt_buf.assign(config.begin(), config.end()); Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end()))); + EXPECT_NO_THROW(option.reset(new Option4Dnr(opt_buf.begin(), opt_buf.end(), true))); ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer out_buf(10); + ASSERT_NO_THROW(option->pack(out_buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + DHO_V4_DNR, // Option code + 79, // Option len + 0x00, 24, // DNR Instance Data Len + 0x00, 0x01, // Service priority is 1 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 0x00, 51, // DNR Instance Data Len + 0x00, 0x02, // Service priority is 2 dec + 21, // ADN Length is 21 dec + 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. + 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. + 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. + 8, // Addr Len + 192, 168, 0, 1, // IP address 1 + 192, 168, 0, 2, // IP address 2 + 0, 1, // SvcParamKey alpn + 0, 8, // SvcParamVal Len + 3, 'd', 'o', 't', // 3 octets long alpn-id dot + 3, 'd', 'o', 'q', // 3 octets long alpn-id doq + 0, 3, // SvcParamKey port is in correct order + // after alpn + 0, 2, // SvcParamVal Len + 0, 85 // port nr + }; + + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, out_buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, out_buf.getData(), out_buf.getLength())); } // This test verifies option constructor from wire data in terms @@ -425,7 +798,7 @@ TEST(Option4DnrTest, unpackOneAdnOnly) { TEST(Option4DnrTest, unpackOneDnrInstance) { // Prepare data to decode - 1 DNR instance. const uint8_t buf_data[] = { - 0x00, 62, // DNR Instance Data Len + 0x00, 45, // DNR Instance Data Len 0x00, 0x01, // Service priority is 1 dec 21, // ADN Length is 21 dec 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. @@ -434,10 +807,10 @@ TEST(Option4DnrTest, unpackOneDnrInstance) { 8, // Addr Len 192, 168, 0, 1, // IP address 1 192, 168, 0, 2, // IP address 2 - 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params - 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params - '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params - 'e', 'y', '3', '4', '5' // Svc Params + 0, 1, // SvcParamKey alpn + 0, 8, // SvcParamVal Len + 3, 'd', 'o', 't', // 3 octets long alpn-id dot + 3, 'd', 'o', 'q' // 3 octets long alpn-id doq }; OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); @@ -452,17 +825,26 @@ TEST(Option4DnrTest, unpackOneDnrInstance) { // Check if data was unpacked correctly from wire data. const DnrInstance& dnr_i = option->getDnrInstances()[0]; - EXPECT_EQ(62, dnr_i.getDnrInstanceDataLength()); + EXPECT_EQ(45, dnr_i.getDnrInstanceDataLength()); EXPECT_EQ(1, dnr_i.getServicePriority()); EXPECT_EQ(21, dnr_i.getAdnLength()); EXPECT_EQ("myhost1.example.com.", dnr_i.getAdnAsText()); EXPECT_EQ(8, dnr_i.getAddrLength()); - EXPECT_EQ(29, dnr_i.getSvcParamsLength()); EXPECT_EQ(2, dnr_i.getAddresses().size()); EXPECT_EQ("192.168.0.1", dnr_i.getAddresses()[0].toText()); EXPECT_EQ("192.168.0.2", dnr_i.getAddresses()[1].toText()); - EXPECT_EQ("key123=val key234=val2 key345", dnr_i.getSvcParams()); - EXPECT_EQ(66, option->len()); + + // Reference SvcParams on-wire data buffer. + const OptionBuffer svc_params = { + 0, 1, // SvcParamKey alpn + 0, 8, // SvcParamVal Len + 3, 'd', 'o', 't', // 3 octets long alpn-id dot + 3, 'd', 'o', 'q' // 3 octets long alpn-id doq + }; + + EXPECT_EQ(svc_params, dnr_i.getSvcParams()); + EXPECT_EQ(svc_params.size(), dnr_i.getSvcParamsLength()); + EXPECT_EQ(49, option->len()); } // This test verifies option constructor from wire data in terms @@ -479,7 +861,7 @@ TEST(Option4DnrTest, unpackMixedDnrInstances) { 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1. 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example. 0x03, 0x63, 0x6F, 0x6D, 0x00, // com. - 0x00, 62, // DNR Instance Data Len + 0x00, 58, // DNR Instance Data Len 0x00, 0x02, // Service priority is 2 dec 21, // ADN Length is 21 dec 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2. @@ -488,10 +870,15 @@ TEST(Option4DnrTest, unpackMixedDnrInstances) { 8, // Addr Len 192, 168, 0, 1, // IP address 1 192, 168, 0, 2, // IP address 2 - 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params - 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params - '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params - 'e', 'y', '3', '4', '5' // Svc Params + 0, 1, // SvcParamKey alpn + 0, 3, // SvcParamVal Len + 2, 'h', '3', // 2 octets long alpn-id http/3 + 0, 3, // SvcParamKey port + 0, 2, // SvcParamVal Len + 0x4, 0xD2, // 1234 in hex + 0, 7, // SvcParamKey dohpath + 0, 8, // SvcParamVal Len + '/', 'q', '{', '?', 'd', 'n', 's', '}' // dohpath value }; OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); @@ -514,18 +901,31 @@ TEST(Option4DnrTest, unpackMixedDnrInstances) { EXPECT_EQ(0, dnr_1.getSvcParamsLength()); const DnrInstance& dnr_2 = option->getDnrInstances()[1]; - EXPECT_EQ(62, dnr_2.getDnrInstanceDataLength()); + EXPECT_EQ(58, dnr_2.getDnrInstanceDataLength()); EXPECT_EQ(2, dnr_2.getServicePriority()); EXPECT_EQ(21, dnr_2.getAdnLength()); EXPECT_EQ("myhost2.example.com.", dnr_2.getAdnAsText()); EXPECT_EQ(8, dnr_2.getAddrLength()); - EXPECT_EQ(29, dnr_2.getSvcParamsLength()); EXPECT_EQ(2, dnr_2.getAddresses().size()); EXPECT_EQ("192.168.0.1", dnr_2.getAddresses()[0].toText()); EXPECT_EQ("192.168.0.2", dnr_2.getAddresses()[1].toText()); - EXPECT_EQ("key123=val key234=val2 key345", dnr_2.getSvcParams()); - EXPECT_EQ(92, option->len()); + // Reference SvcParams on-wire data buffer. + const OptionBuffer svc_params = { + 0, 1, // SvcParamKey alpn + 0, 3, // SvcParamVal Len + 2, 'h', '3', // 2 octets long alpn-id http/3 + 0, 3, // SvcParamKey port + 0, 2, // SvcParamVal Len + 0x4, 0xD2, // 1234 in hex + 0, 7, // SvcParamKey dohpath + 0, 8, // SvcParamVal Len + '/', 'q', '{', '?', 'd', 'n', 's', '}' // dohpath value + }; + EXPECT_EQ(svc_params, dnr_2.getSvcParams()); + EXPECT_EQ(svc_params.size(), dnr_2.getSvcParamsLength()); + + EXPECT_EQ(88, option->len()); } // Test checks that exception is thrown when trying to unpack malformed wire data @@ -601,7 +1001,7 @@ TEST(Option4DnrTest, unpackTruncatedAdn) { } // Test checks that exception is thrown when trying to unpack malformed wire data -// - ADN FQDN contains only whitespace - non valid FQDN. +// - ADN FQDN contains only whitespace - non-valid FQDN. TEST(Option4DnrTest, unpackInvalidFqdnAdn) { // Prepare malformed data to decode. const uint8_t buf_data[] = { @@ -737,7 +1137,7 @@ TEST(Option4DnrTest, unpackIpAddressNon4Modulo) { } // Test checks that exception is thrown when trying to unpack malformed wire data -// - SvcParams Key contains char that is not allowed. +// - SvcParams contain unknown SvcParamKey. TEST(Option4DnrTest, unpackvcParamsInvalidCharKey) { // Prepare malformed data to decode. const uint8_t buf_data[] = { @@ -756,7 +1156,13 @@ TEST(Option4DnrTest, unpackvcParamsInvalidCharKey) { 8, // Addr Len 192, 168, 0, 1, // IP address 1 192, 168, 0, 2, // IP address 2 truncated - 'k', 'e', 'y', '+', '2', '3' // Svc Params key has forbidden char + + 0, 1, // SvcParamKey alpn + 0, 8, // SvcParamVal Len + 3, 'd', 'o', 't', // 3 octets long alpn-id dot + 3, 'd', 'o', 'q', // 3 octets long alpn-id doq + 0, 99, // unknown SvcParamKey + 0, 2, // SvcParamVal Len + 1, 2 // random data }; OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); @@ -770,17 +1176,17 @@ TEST(Option4DnrTest, unpackvcParamsInvalidCharKey) { // This test verifies that string representation of the option returned by // toText method is correctly formatted. TEST(Option4DnrTest, toText) { + // Prepare example config. + const std::string config = "1, myhost1.example.com."; + + OptionBuffer in_buf; + in_buf.assign(config.begin(), config.end()); + // Create option instance. Check that constructor doesn't throw. Option4DnrPtr option; - EXPECT_NO_THROW(option.reset(new Option4Dnr())); + EXPECT_NO_THROW(option.reset(new Option4Dnr(in_buf.begin(), in_buf.end(), true))); ASSERT_TRUE(option); - // Prepare example DNR instance to add. - DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com."); - - // Add DNR instance. - option->addDnrInstance(dnr_1); - // Let's check if toText() works ok. // toText() len does not count in headers len. const int indent = 4; diff --git a/src/lib/dhcp/tests/option6_dnr_unittest.cc b/src/lib/dhcp/tests/option6_dnr_unittest.cc index bf00fbeaa3..40854572d6 100644 --- a/src/lib/dhcp/tests/option6_dnr_unittest.cc +++ b/src/lib/dhcp/tests/option6_dnr_unittest.cc @@ -77,7 +77,7 @@ TEST(Option6DnrTest, onWireCtorDataTruncated) { } // Test checks that exception is thrown when trying to unpack malformed wire data -// - ADN FQDN contains only whitespace - non valid FQDN. +// - ADN FQDN contains only whitespace - non-valid FQDN. TEST(Option6DnrTest, onWireCtorOnlyWhitespaceFqdn) { // Prepare data to decode - ADN only mode. const uint8_t buf_data[] = { @@ -308,7 +308,8 @@ TEST(Option6DnrTest, onWireCtorSvcParamsIncluded) { const Option6Dnr::AddressContainer& addresses = option->getAddresses(); EXPECT_EQ(1, addresses.size()); EXPECT_EQ("2001:db8:1::dead:beef", addresses[0].toText()); - EXPECT_EQ(12, option->getSvcParamsLength()); + + // Reference SvcParams on-wire data buffer. const OptionBuffer svc_params = { 0, 1, // SvcParamKey alpn 0, 8, // SvcParamVal Len @@ -316,6 +317,7 @@ TEST(Option6DnrTest, onWireCtorSvcParamsIncluded) { 3, 'd', 'o', 'q' // 3 octets long alpn-id doq }; EXPECT_EQ(svc_params, option->getSvcParams()); + EXPECT_EQ(svc_params.size(), option->getSvcParamsLength()); // BTW let's check if len() works ok. // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28 @@ -612,7 +614,8 @@ TEST(Option6DnrTest, toText) { EXPECT_EQ(expected, option->toText(indent)); } -// This test verifies on-wire format of the option is correctly created in ADN only mode. +// This test verifies that the option is correctly created in ADN only mode, +// when constructor from convenient string notation is used. TEST(Option6DnrTest, fromConfigCtorPackAdnOnlyMode) { // Prepare example config. const std::string config = "9, myhost.example.com."; @@ -648,8 +651,8 @@ TEST(Option6DnrTest, fromConfigCtorPackAdnOnlyMode) { EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); } -// This test verifies on-wire format of the option is correctly created when -// IP addresses and Svc Params are also included. +// This test verifies on-wire format of the option is correctly created with pack +// method when IP addresses and Svc Params are also included. TEST(Option6DnrTest, pack) { // Prepare example config. const std::string config = "9, myhost.example.com., 2001:db8:1::dead:beef ff02::face:b00c," |