]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3141] DNRv4 UTs edited
authorPiotrek Zadroga <piotrek@isc.org>
Mon, 19 Feb 2024 20:15:48 +0000 (21:15 +0100)
committerPiotrek Zadroga <piotrek@isc.org>
Fri, 23 Feb 2024 16:14:06 +0000 (17:14 +0100)
src/lib/dhcp/option4_dnr.cc
src/lib/dhcp/option4_dnr.h
src/lib/dhcp/tests/option4_dnr_unittest.cc
src/lib/dhcp/tests/option6_dnr_unittest.cc

index 097615248123e51a6fd1a8775fd998fdfa74860a..e2bc68424d8207c8c68cda544fe7a4f30886dec8 100644 (file)
@@ -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_) {
@@ -281,38 +254,6 @@ DnrInstance::getAdnAsText() const {
     return (adn_) ? (adn_->toText()) : ("");
 }
 
-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_);
@@ -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.");
             }
         }
 
index 682f68a710f5149aeff9cfeec12afbcb38d8403d..0db59e88083fa5a77a82e0006c80357ef9555041 100644 (file)
@@ -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);
index 60f8cf31c24c70cbad4fc06a8ad4bc7d39de0600..2fb616a4ffc81ccb4949ec68dc6d66b88560e676 100644 (file)
@@ -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;
index bf00fbeaa3636f98347b2cacc9d026ab8cf5382e..40854572d6a07417bf073be950cec9c61bad3a79 100644 (file)
@@ -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,"