Version in base suite: 2.6.3-1 Base version: isc-kea_2.6.3-1 Target version: isc-kea_2.6.3-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/i/isc-kea/isc-kea_2.6.3-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/i/isc-kea/isc-kea_2.6.3-1+deb13u1.dsc changelog | 6 patches/CVE-2026-3608.patch | 2645 ++++++++++++++++++++++++++++++++++++++++++++ patches/series | 1 3 files changed, 2652 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpdhlsdjsv/isc-kea_2.6.3-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpdhlsdjsv/isc-kea_2.6.3-1+deb13u1.dsc: no acceptable signature found diff -Nru isc-kea-2.6.3/debian/changelog isc-kea-2.6.3/debian/changelog --- isc-kea-2.6.3/debian/changelog 2025-06-02 17:00:06.000000000 +0000 +++ isc-kea-2.6.3/debian/changelog 2026-06-12 15:58:34.000000000 +0000 @@ -1,3 +1,9 @@ +isc-kea (2.6.3-1+deb13u1) trixie; urgency=medium + + * CVE-2026-3608 + + -- Moritz Mühlenhoff Fri, 12 Jun 2026 17:58:34 +0200 + isc-kea (2.6.3-1) unstable; urgency=medium * New upstream version 2.6.3. diff -Nru isc-kea-2.6.3/debian/patches/CVE-2026-3608.patch isc-kea-2.6.3/debian/patches/CVE-2026-3608.patch --- isc-kea-2.6.3/debian/patches/CVE-2026-3608.patch 1970-01-01 00:00:00.000000000 +0000 +++ isc-kea-2.6.3/debian/patches/CVE-2026-3608.patch 2026-06-12 15:58:27.000000000 +0000 @@ -0,0 +1,2645 @@ +From 5dc0f6612aa5f6003eb7b8e5299f7774f46e5849 Mon Sep 17 00:00:00 2001 +From: Razvan Becheriu +Date: Thu, 12 Mar 2026 14:25:03 +0200 +Subject: [PATCH] [#4391] backport #4275, #4288 to v2_6 + +--- isc-kea-2.6.3.orig/src/lib/cc/data.cc ++++ isc-kea-2.6.3/src/lib/cc/data.cc +@@ -1,4 +1,4 @@ +-// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") ++// Copyright (C) 2010-2026 Internet Systems Consortium, Inc. ("ISC") + // + // This Source Code Form is subject to the terms of the Mozilla Public + // License, v. 2.0. If a copy of the MPL was not distributed with this +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -37,6 +38,8 @@ const char* const WHITESPACE = " \b\f\n\ + namespace isc { + namespace data { + ++constexpr unsigned Element::MAX_NESTING_LEVEL; ++ + std::string + Element::Position::str() const { + std::ostringstream ss; +@@ -50,6 +53,53 @@ operator<<(std::ostream& out, const Elem + return (out); + } + ++void ++Element::removeEmptyContainersRecursively(unsigned level) { ++ if (level <= 0) { ++ // Cycles are by definition not empty so no need to throw. ++ return; ++ } ++ if (type_ == list || type_ == map) { ++ size_t s(size()); ++ for (size_t i = 0; i < s; ++i) { ++ // Get child. ++ ElementPtr child; ++ if (type_ == list) { ++ child = getNonConst(i); ++ } else if (type_ == map) { ++ std::string const key(get(i)->stringValue()); ++ // The ElementPtr - ConstElementPtr disparity between ++ // ListElement and MapElement is forcing a const cast here. ++ // It's undefined behavior to modify it after const casting. ++ // The options are limited. I've tried templating, moving ++ // this function from a member function to free-standing and ++ // taking the Element template as argument. I've tried ++ // making it a virtual function with overridden ++ // implementations in ListElement and MapElement. Nothing ++ // works. ++ child = boost::const_pointer_cast(get(key)); ++ } ++ ++ // Makes no sense to continue for non-container children. ++ if (child->getType() != list && child->getType() != map) { ++ continue; ++ } ++ ++ // Recurse if not empty. ++ if (!child->empty()){ ++ child->removeEmptyContainersRecursively(level - 1); ++ } ++ ++ // When returning from recursion, remove if empty. ++ if (child->empty()) { ++ remove(i); ++ --i; ++ --s; ++ } ++ } ++ } ++} ++ + std::string + Element::str() const { + std::stringstream ss; +@@ -600,7 +650,10 @@ fromStringstreamString(std::istream& in, + + ElementPtr + fromStringstreamList(std::istream& in, const std::string& file, int& line, +- int& pos) { ++ int& pos, unsigned level) { ++ if (level == 0) { ++ isc_throw(JSONError, "fromJSON elements nested too deeply"); ++ } + int c = 0; + ElementPtr list = Element::createList(Element::Position(file, line, pos)); + ElementPtr cur_list_element; +@@ -608,7 +661,8 @@ fromStringstreamList(std::istream& in, c + skipChars(in, WHITESPACE, line, pos); + while (c != EOF && c != ']') { + if (in.peek() != ']') { +- cur_list_element = Element::fromJSON(in, file, line, pos); ++ cur_list_element = ++ Element::fromJSON(in, file, line, pos, level - 1); + list->add(cur_list_element); + c = skipTo(in, file, line, pos, ",]", WHITESPACE); + } else { +@@ -621,7 +675,10 @@ fromStringstreamList(std::istream& in, c + + ElementPtr + fromStringstreamMap(std::istream& in, const std::string& file, int& line, +- int& pos) { ++ int& pos, unsigned level) { ++ if (level == 0) { ++ isc_throw(JSONError, "fromJSON elements nested too deeply"); ++ } + ElementPtr map = Element::createMap(Element::Position(file, line, pos)); + skipChars(in, WHITESPACE, line, pos); + int c = in.peek(); +@@ -637,7 +694,8 @@ fromStringstreamMap(std::istream& in, co + skipTo(in, file, line, pos, ":", WHITESPACE); + // skip the : + +- ConstElementPtr value = Element::fromJSON(in, file, line, pos); ++ ConstElementPtr value = ++ Element::fromJSON(in, file, line, pos, level - 1); + map->set(key, value); + + c = skipTo(in, file, line, pos, ",}", WHITESPACE); +@@ -726,7 +784,10 @@ Element::fromJSON(std::istream& in, cons + + ElementPtr + Element::fromJSON(std::istream& in, const std::string& file, int& line, +- int& pos) { ++ int& pos, unsigned level) { ++ if (level == 0) { ++ isc_throw(JSONError, "fromJSON elements nested too deeply"); ++ } + int c = 0; + ElementPtr element; + bool el_read = false; +@@ -773,11 +834,11 @@ Element::fromJSON(std::istream& in, cons + el_read = true; + break; + case '[': +- element = fromStringstreamList(in, file, line, pos); ++ element = fromStringstreamList(in, file, line, pos, level); + el_read = true; + break; + case '{': +- element = fromStringstreamMap(in, file, line, pos); ++ element = fromStringstreamMap(in, file, line, pos, level); + el_read = true; + break; + case EOF: +@@ -831,17 +892,17 @@ Element::fromJSONFile(const std::string& + // to JSON format + + void +-IntElement::toJSON(std::ostream& ss) const { ++IntElement::toJSON(std::ostream& ss, unsigned) const { + ss << intValue(); + } + + void +-BigIntElement::toJSON(std::ostream& ss) const { ++BigIntElement::toJSON(std::ostream& ss, unsigned) const { + ss << bigIntValue(); + } + + void +-DoubleElement::toJSON(std::ostream& ss) const { ++DoubleElement::toJSON(std::ostream& ss, unsigned) const { + // The default output for doubles nicely drops off trailing + // zeros, however this produces strings without decimal points + // for whole number values. When reparsed this will create +@@ -857,7 +918,7 @@ DoubleElement::toJSON(std::ostream& ss) + } + + void +-BoolElement::toJSON(std::ostream& ss) const { ++BoolElement::toJSON(std::ostream& ss, unsigned) const { + if (boolValue()) { + ss << "true"; + } else { +@@ -866,12 +927,12 @@ BoolElement::toJSON(std::ostream& ss) co + } + + void +-NullElement::toJSON(std::ostream& ss) const { ++NullElement::toJSON(std::ostream& ss, unsigned) const { + ss << "null"; + } + + void +-StringElement::toJSON(std::ostream& ss) const { ++StringElement::toJSON(std::ostream& ss, unsigned) const { + ss << "\""; + const std::string& str = stringValue(); + for (size_t i = 0; i < str.size(); ++i) { +@@ -919,7 +980,11 @@ StringElement::toJSON(std::ostream& ss) + } + + void +-ListElement::toJSON(std::ostream& ss) const { ++ListElement::toJSON(std::ostream& ss, unsigned level) const { ++ if (level == 0) { ++ isc_throw(BadValue, "toJSON got infinite recursion: " ++ "arguments include cycles"); ++ } + ss << "[ "; + + const std::vector& v = listValue(); +@@ -930,13 +995,17 @@ ListElement::toJSON(std::ostream& ss) co + } else { + first = false; + } +- it->toJSON(ss); ++ it->toJSON(ss, level - 1); + } + ss << " ]"; + } + + void +-MapElement::toJSON(std::ostream& ss) const { ++MapElement::toJSON(std::ostream& ss, unsigned level) const { ++ if (level == 0) { ++ isc_throw(BadValue, "toJSON got infinite recursion: " ++ "arguments include cycles"); ++ } + ss << "{ "; + + bool first = true; +@@ -948,7 +1017,7 @@ MapElement::toJSON(std::ostream& ss) con + } + ss << "\"" << it.first << "\": "; + if (it.second) { +- it.second->toJSON(ss); ++ it.second->toJSON(ss, level - 1); + } else { + ss << "None"; + } +@@ -1024,9 +1093,9 @@ MapElement::find(const std::string& id, + } + + bool +-IntElement::equals(const Element& other) const { ++IntElement::equals(const Element& other, unsigned) const { + // Let's not be very picky with constraining the integer types to be the +- // same. Equality is sometimes checked from high-up in the Element hierarcy. ++ // same. Equality is sometimes checked from high-up in the Element hierarchy. + // That is a context which, most of the time, does not have information on + // the type of integers stored on Elements lower in the hierarchy. So it + // would be difficult to differentiate between the integer types. +@@ -1035,9 +1104,9 @@ IntElement::equals(const Element& other) + } + + bool +-BigIntElement::equals(const Element& other) const { ++BigIntElement::equals(const Element& other, unsigned) const { + // Let's not be very picky with constraining the integer types to be the +- // same. Equality is sometimes checked from high-up in the Element hierarcy. ++ // same. Equality is sometimes checked from high-up in the Element hierarchy. + // That is a context which, most of the time, does not have information on + // the type of integers stored on Elements lower in the hierarchy. So it + // would be difficult to differentiate between the integer types. +@@ -1046,37 +1115,41 @@ BigIntElement::equals(const Element& oth + } + + bool +-DoubleElement::equals(const Element& other) const { ++DoubleElement::equals(const Element& other, unsigned) const { + return (other.getType() == Element::real) && + (fabs(d - other.doubleValue()) < 1e-14); + } + + bool +-BoolElement::equals(const Element& other) const { ++BoolElement::equals(const Element& other, unsigned) const { + return (other.getType() == Element::boolean) && + (b == other.boolValue()); + } + + bool +-NullElement::equals(const Element& other) const { ++NullElement::equals(const Element& other, unsigned) const { + return (other.getType() == Element::null); + } + + bool +-StringElement::equals(const Element& other) const { ++StringElement::equals(const Element& other, unsigned) const { + return (other.getType() == Element::string) && + (s == other.stringValue()); + } + + bool +-ListElement::equals(const Element& other) const { ++ListElement::equals(const Element& other, unsigned level) const { ++ if (level == 0) { ++ isc_throw(BadValue, "equals got infinite recursion: " ++ "arguments include cycles"); ++ } + if (other.getType() == Element::list) { + const size_t s = size(); + if (s != other.size()) { + return (false); + } + for (size_t i = 0; i < s; ++i) { +- if (!get(i)->equals(*other.get(i))) { ++ if (!get(i)->equals(*other.get(i), level - 1)) { + return (false); + } + } +@@ -1123,7 +1196,11 @@ ListElement::sort(std::string const& ind + } + + bool +-MapElement::equals(const Element& other) const { ++MapElement::equals(const Element& other, unsigned level) const { ++ if (level == 0) { ++ isc_throw(BadValue, "equals got infinite recursion: " ++ "arguments include cycles"); ++ } + if (other.getType() == Element::map) { + if (size() != other.size()) { + return (false); +@@ -1131,7 +1208,7 @@ MapElement::equals(const Element& other) + for (auto const& kv : mapValue()) { + auto key = kv.first; + if (other.contains(key)) { +- if (!get(key)->equals(*other.get(key))) { ++ if (!get(key)->equals(*other.get(key), level - 1)) { + return (false); + } + } else { +@@ -1215,7 +1292,12 @@ merge(ElementPtr element, ConstElementPt + + void + mergeDiffAdd(ElementPtr& element, ElementPtr& other, +- HierarchyDescriptor& hierarchy, std::string key, size_t idx) { ++ HierarchyDescriptor& hierarchy, std::string key, size_t idx, ++ unsigned level) { ++ if (level == 0) { ++ isc_throw(BadValue, "mergeDiffAdd got infinite recursion: " ++ "arguments include cycles"); ++ } + if (element->getType() != other->getType()) { + isc_throw(TypeError, "mergeDiffAdd arguments not same type"); + } +@@ -1237,7 +1319,8 @@ mergeDiffAdd(ElementPtr& element, Elemen + // entity. + if (f->second.match_(mutable_left, mutable_right)) { + found = true; +- mergeDiffAdd(mutable_left, mutable_right, hierarchy, key, idx); ++ mergeDiffAdd(mutable_left, mutable_right, hierarchy, ++ key, idx, level - 1); + } + } + if (!found) { +@@ -1263,7 +1346,8 @@ mergeDiffAdd(ElementPtr& element, Elemen + (value->getType() == Element::map || + value->getType() == Element::list)) { + ElementPtr mutable_element = boost::const_pointer_cast(element->get(current_key)); +- mergeDiffAdd(mutable_element, value, hierarchy, current_key, idx + 1); ++ mergeDiffAdd(mutable_element, value, hierarchy, ++ current_key, idx + 1, level - 1); + } else { + element->set(current_key, value); + } +@@ -1276,7 +1360,12 @@ mergeDiffAdd(ElementPtr& element, Elemen + + void + mergeDiffDel(ElementPtr& element, ElementPtr& other, +- HierarchyDescriptor& hierarchy, std::string key, size_t idx) { ++ HierarchyDescriptor& hierarchy, std::string key, size_t idx, ++ unsigned level) { ++ if (level == 0) { ++ isc_throw(BadValue, "mergeDiffDel got infinite recursion: " ++ "arguments include cycles"); ++ } + if (element->getType() != other->getType()) { + isc_throw(TypeError, "mergeDiffDel arguments not same type"); + } +@@ -1301,7 +1390,8 @@ mergeDiffDel(ElementPtr& element, Elemen + element->remove(iter); + removed = true; + } else { +- mergeDiffDel(mutable_left, mutable_right, hierarchy, key, idx); ++ mergeDiffDel(mutable_left, mutable_right, ++ hierarchy, key, idx, level - 1); + if (mutable_left->empty()) { + element->remove(iter); + removed = true; +@@ -1332,7 +1422,8 @@ mergeDiffDel(ElementPtr& element, Elemen + ElementPtr mutable_element = boost::const_pointer_cast(element->get(current_key)); + if (mutable_element->getType() == Element::map || + mutable_element->getType() == Element::list) { +- mergeDiffDel(mutable_element, value, hierarchy, current_key, idx + 1); ++ mergeDiffDel(mutable_element, value, hierarchy, ++ current_key, idx + 1, level - 1); + if (mutable_element->empty()) { + element->remove(current_key); + } +@@ -1367,7 +1458,12 @@ mergeDiffDel(ElementPtr& element, Elemen + void + extend(const std::string& container, const std::string& extension, + ElementPtr& element, ElementPtr& other, HierarchyDescriptor& hierarchy, +- std::string key, size_t idx, bool alter) { ++ std::string key, size_t idx, bool alter, unsigned level) { ++ ++ if (level == 0) { ++ isc_throw(BadValue, "extend got infinite recursion: " ++ "arguments include cycles"); ++ } + if (element->getType() != other->getType()) { + isc_throw(TypeError, "extend arguments not same type"); + } +@@ -1386,7 +1482,7 @@ extend(const std::string& container, con + } + if (f->second.match_(mutable_left, mutable_right)) { + extend(container, extension, mutable_left, mutable_right, +- hierarchy, key, idx, alter); ++ hierarchy, key, idx, alter, level - 1); + } + } + } +@@ -1406,7 +1502,8 @@ extend(const std::string& container, con + if (container == key) { + alter = true; + } +- extend(container, extension, mutable_element, value, hierarchy, current_key, idx + 1, alter); ++ extend(container, extension, mutable_element, value, ++ hierarchy, current_key, idx + 1, alter, level - 1); + } else if (alter && current_key == extension) { + element->set(current_key, value); + } +@@ -1417,7 +1514,7 @@ extend(const std::string& container, con + } + + ElementPtr +-copy(ConstElementPtr from, int level) { ++copy(ConstElementPtr from, unsigned level) { + if (!from) { + isc_throw(BadValue, "copy got a null pointer"); + } +@@ -1543,9 +1640,15 @@ isEquivalent(ConstElementPtr a, ConstEle + return (isEquivalent0(a, b, 100)); + } + ++namespace { ++ + void +-prettyPrint(ConstElementPtr element, std::ostream& out, +- unsigned indent, unsigned step) { ++prettyPrint0(ConstElementPtr element, std::ostream& out, ++ unsigned indent, unsigned step, unsigned level) { ++ if (level == 0) { ++ isc_throw(BadValue, "prettyPrint got infinite recursion: " ++ "arguments include cycles"); ++ } + if (!element) { + isc_throw(BadValue, "prettyPrint got a null pointer"); + } +@@ -1585,7 +1688,7 @@ prettyPrint(ConstElementPtr element, std + out << std::string(indent + step, ' '); + } + // recursive call +- prettyPrint(it, out, indent + step, step); ++ prettyPrint0(it, out, indent + step, step, level - 1); + } + + // close the list +@@ -1620,7 +1723,7 @@ prettyPrint(ConstElementPtr element, std + // add keyword: + out << "\"" << it.first << "\": "; + // recursive call +- prettyPrint(it.second, out, indent + step, step); ++ prettyPrint0(it.second, out, indent + step, step, level - 1); + } + + // close the map +@@ -1631,6 +1734,14 @@ prettyPrint(ConstElementPtr element, std + } + } + ++} // end anonymous namespace ++ ++void ++prettyPrint(ConstElementPtr element, std::ostream& out, ++ unsigned indent, unsigned step) { ++ prettyPrint0(element, out, indent, step, Element::MAX_NESTING_LEVEL); ++} ++ + std::string + prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) { + std::stringstream ss; +@@ -1657,5 +1768,90 @@ void Element::preprocess(std::istream& i + } + } + ++namespace { ++ ++// Type of arcs. ++typedef std::set Arc; ++ ++// Helper function walking on the supposed tree. ++bool ++IsCircular0(ConstElementPtr element, Arc arc) { ++ // Sanity check. ++ if (!element) { ++ return (false); ++ } ++ auto type = element->getType(); ++ // Container? ++ if ((type != Element::list) && (type != Element::map)) { ++ return (false); ++ } ++ // Empty? A cycle requires at least one element. ++ if (element->empty()) { ++ return (false); ++ } ++ // In the arc? ++ if (arc.count(element) > 0) { ++ return (true); ++ } ++ // This requires to work on a copy of the arc but it should be small. ++ arc.insert(element); ++ if (type == Element::list) { ++ for (auto const& it : element->listValue()) { ++ if (IsCircular0(it, arc)) { ++ return (true); ++ } ++ } ++ return (false); ++ } ++ // The argument is a map. ++ for (auto const& it : element->mapValue()) { ++ if (IsCircular0(it.second, arc)) { ++ return (true); ++ } ++ } ++ return (false); ++} ++ ++} // end anonymous namespace ++ ++bool ++IsCircular(ConstElementPtr element) { ++ return (IsCircular0(element, Arc())); ++} ++ ++unsigned ++getNestDepth(ConstElementPtr element, unsigned max_depth) { ++ if (max_depth == 0U) { ++ return (0U); ++ } ++ if (!element) { ++ return (0U); ++ } ++ unsigned ret = 1U; ++ if (element->getType() == Element::list) { ++ for (auto const& i : element->listValue()) { ++ unsigned sub = getNestDepth(i, max_depth - 1); ++ if (sub == max_depth - 1) { ++ return (max_depth); ++ } ++ if (sub + 1 > ret) { ++ ret = sub + 1; ++ } ++ } ++ } else if (element->getType() == Element::map) { ++ for (auto const& i : element->mapValue()) { ++ unsigned sub = getNestDepth(i.second, max_depth - 1); ++ if (sub == max_depth - 1) { ++ return (max_depth); ++ } ++ if (sub + 1 > ret) { ++ ret = sub + 1; ++ } ++ } ++ ++ } ++ return (ret); ++} ++ + } // end of isc::data namespace + } // end of isc namespace +--- isc-kea-2.6.3.orig/src/lib/cc/data.h ++++ isc-kea-2.6.3/src/lib/cc/data.h +@@ -21,7 +21,8 @@ + + #include + +-namespace isc { namespace data { ++namespace isc { ++namespace data { + + class Element; + // todo: describe the rationale behind ElementPtr? +@@ -70,8 +71,20 @@ public: + /// the type in question. + /// + class Element { +- + public: ++ /// @brief Maximum nesting level of Element objects. ++ /// ++ /// Many methods and functions perform a recursive walk on an element ++ /// containing lists or/and maps. This recursion is limited to using ++ /// an allowed level of nesting argument which is decremented at ++ /// each recursive call until it reaches 0. This was extended to ++ /// recursive parsing of a JSON text as stack overflows were reported ++ /// with excessive recursion on specially crafted input. ++ /// This constant is the default allowed level of nesting, its value ++ /// is arbitrary (but enough for all realistic cases) and used before ++ /// limiting recursion in *all* recursive methods/functions. ++ static constexpr unsigned MAX_NESTING_LEVEL = 100U; ++ + /// @brief Represents the position of the data element within a + /// configuration string. + /// +@@ -121,7 +134,7 @@ public: + /// + /// The object containing two zeros is a default for most of the + /// methods creating @c Element objects. The returned value is static +- /// so as it is not created everytime the function with the default ++ /// so as it is not created every time the function with the default + /// position argument is called. + static const Position& ZERO_POSITION() { + static Position position("", 0, 0); +@@ -136,7 +149,7 @@ public: + /// + /// any is a special type used in list specifications, specifying that the + /// elements can be of any type. +- enum types { ++ enum types : int { + integer = 0, + real = 1, + boolean = 2, +@@ -171,37 +184,53 @@ protected: + + + public: +- // base class; make dtor virtual +- virtual ~Element() {}; ++ // Base class; make destructor virtual. ++ virtual ~Element() {} + +- /// @return the type of this element +- types getType() const { return (type_); } ++ /// @return the type of this element. ++ types getType() const { ++ return (type_); ++ } + + /// @brief Returns position where the data element's value starts in a + /// configuration string. + /// + /// @warning The returned reference is valid as long as the object which + /// created it lives. +- const Position& getPosition() const { return (position_); } ++ /// @return The position. ++ const Position& getPosition() const { ++ return (position_); ++ } + +- /// Returns a string representing the Element and all its +- /// child elements; note that this is different from stringValue(), ++ /// @brief Returns a string representing the Element and all its ++ /// child elements ++ /// ++ /// @note: that this is different from stringValue(), + /// which only returns the single value of a StringElement + /// + /// The resulting string will contain the Element in JSON format. ++ /// Based on @ref toJSON. + /// +- /// @return std::string containing the string representation ++ /// @return std::string containing the string representation. + std::string str() const; + +- /// Returns the wireformat for the Element and all its child ++ /// @brief Returns the wireformat for the Element and all its child + /// elements. + /// ++ /// Based on @ref toJSON. ++ /// + /// @return std::string containing the element in wire format + std::string toWire() const; ++ ++ /// @brief Appends the wireformat for the Element to the stream. ++ /// ++ /// @param out The output stream where to append the wireformat. + void toWire(std::ostream& out) const; + + /// @brief Add the position to a TypeError message + /// should be used in place of isc_throw(TypeError, error) ++ /// ++ /// @param error The error message. + #define throwTypeError(error) \ + { \ + std::string msg_ = error; \ +@@ -213,15 +242,26 @@ public: + isc_throw(TypeError, msg_); \ + } + +- /// @name pure virtuals, every derived class must implement these ++ /// @name pure virtuals, every derived class must implement these. + ++ /// @brief Test equality. ++ /// ++ /// @param other The other element to compare with. ++ /// @param level The maximum level of recursion. + /// @return true if the other ElementPtr has the same value and the same + /// type (or a different and compatible type), false otherwise. +- virtual bool equals(const Element& other) const = 0; +- +- /// Converts the Element to JSON format and appends it to +- /// the given stringstream. +- virtual void toJSON(std::ostream& ss) const = 0; ++ /// @throw BadValue when nesting depth is more than level. ++ virtual bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const = 0; ++ ++ /// @brief Converts the Element to JSON format and appends it to ++ /// the given output stream. ++ /// ++ /// @param ss The output stream where to append the JSON format. ++ /// @param level The maximum level of recursion. ++ /// @throw BadValue when nesting depth is more than level. ++ virtual void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const = 0; + + /// @name Type-specific getters + /// +@@ -231,25 +271,42 @@ public: + /// If you want an exception-safe getter method, use + /// getValue() below + //@{ +- virtual int64_t intValue() const +- { throwTypeError("intValue() called on non-integer Element"); }; ++ /// @brief Return the integer value. ++ virtual int64_t intValue() const { ++ throwTypeError("intValue() called on non-integer Element"); ++ } ++ ++ /// @brief Return the big integer value. + virtual isc::util::int128_t bigIntValue() const { + throwTypeError("bigIntValue() called on non-big-integer Element"); + } +- virtual double doubleValue() const +- { throwTypeError("doubleValue() called on non-double Element"); }; +- virtual bool boolValue() const +- { throwTypeError("boolValue() called on non-Bool Element"); }; +- virtual std::string stringValue() const +- { throwTypeError("stringValue() called on non-string Element"); }; ++ ++ /// @brief Return the double value. ++ virtual double doubleValue() const { ++ throwTypeError("doubleValue() called on non-double Element"); ++ } ++ ++ /// @brief Return the boolean value. ++ virtual bool boolValue() const { ++ throwTypeError("boolValue() called on non-Bool Element"); ++ } ++ ++ /// @brief Return the string value. ++ virtual std::string stringValue() const { ++ throwTypeError("stringValue() called on non-string Element"); ++ } ++ ++ /// @brief Return the list value. + virtual const std::vector& listValue() const { + // replace with real exception or empty vector? + throwTypeError("listValue() called on non-list Element"); +- }; ++ } ++ ++ /// @brief Return the map value. + virtual const std::map& mapValue() const { + // replace with real exception or empty map? + throwTypeError("mapValue() called on non-map Element"); +- }; ++ } + //@} + + /// @name Exception-safe getters +@@ -261,11 +318,40 @@ public: + /// data to the given reference and returning true + /// + //@{ ++ /// @brief Get the integer value. ++ /// ++ /// @param t The reference to the integer. ++ /// @return false. + virtual bool getValue(int64_t& t) const; ++ ++ /// @brief Get the double value. ++ /// ++ /// @param t The reference to the double. ++ /// @return false. + virtual bool getValue(double& t) const; ++ ++ /// @brief Get the boolean value. ++ /// ++ /// @param t The reference to the boolean. ++ /// @return false. + virtual bool getValue(bool& t) const; ++ ++ /// @brief Get the string value. ++ /// ++ /// @param t The reference to the string. ++ /// @return false. + virtual bool getValue(std::string& t) const; ++ ++ /// @brief Get the list value. ++ /// ++ /// @param t The reference to the list. ++ /// @return false. + virtual bool getValue(std::vector& t) const; ++ ++ /// @brief Get the map value. ++ /// ++ /// @param t The reference to the map. ++ /// @return false. + virtual bool getValue(std::map& t) const; + //@} + +@@ -279,20 +365,70 @@ public: + /// Notes: Read notes of IntElement definition about the use of + /// long long int, long int and int. + //@{ ++ /// @brief Set the integer value. ++ /// ++ /// @param v The new integer value. ++ /// @return False. + virtual bool setValue(const long long int v); ++ ++ /// @brief Set the big integer value. ++ /// ++ /// @param v The new big integer value. ++ /// @return False. + virtual bool setValue(const isc::util::int128_t& v); +- bool setValue(const long int i) { return (setValue(static_cast(i))); }; +- bool setValue(const int i) { return (setValue(static_cast(i))); }; ++ ++ /// @brief Set the double value. ++ /// ++ /// @param v The new double value. ++ /// @return False. + virtual bool setValue(const double v); ++ ++ /// @brief Set the boolean value. ++ /// ++ /// @param t The new boolean value. ++ /// @return False. + virtual bool setValue(const bool t); ++ ++ /// @brief Set the string value. ++ /// ++ /// @param v The new string value. ++ /// @return False. + virtual bool setValue(const std::string& v); ++ ++ /// @brief Set the list value. ++ /// ++ /// @param v The new list value. ++ /// @return False. + virtual bool setValue(const std::vector& v); ++ ++ /// @brief Set the map value. ++ /// ++ /// @param v The new map value. ++ /// @return False. + virtual bool setValue(const std::map& v); ++ ++ /// @brief Set the integer value (long int overload). ++ /// ++ /// @param i The new integer value. ++ /// @return True (and set the value) when the Element type is integer, ++ /// false otherwise. ++ bool setValue(const long int i) { ++ return (setValue(static_cast(i))); ++ } ++ ++ /// @brief Set the integer value (int overload). ++ /// ++ /// @param i The new integer value. ++ /// @return True (and set the value) when the Element type is integer, ++ /// false otherwise. ++ bool setValue(const int i) { ++ return (setValue(static_cast(i))); ++ } + //@} + +- // Other functions for specific subtypes ++ // Other functions for specific subtypes. + +- /// @name ListElement functions ++ /// @name ListElement functions. + /// + /// @brief If the Element on which these functions are called are not + /// an instance of ListElement, a TypeError exception is thrown. +@@ -300,33 +436,34 @@ public: + /// Returns the ElementPtr at the given index. If the index is out + /// of bounds, this function throws an std::out_of_range exception. + /// @param i The position of the ElementPtr to return ++ /// @return specified element pointer. + virtual ConstElementPtr get(const int i) const; + +- /// @brief returns element as non-const pointer ++ /// @brief returns element as non-const pointer. + /// +- /// @param i The position of the ElementPtr to retrieve +- /// @return specified element pointer ++ /// @param i The position of the ElementPtr to retrieve. ++ /// @return specified element pointer. + virtual ElementPtr getNonConst(const int i) const; + +- /// Sets the ElementPtr at the given index. If the index is out ++ /// @brief Sets the ElementPtr at the given index. If the index is out + /// of bounds, this function throws an std::out_of_range exception. + /// @param i The position of the ElementPtr to set + /// @param element The ElementPtr to set at the position + virtual void set(const size_t i, ElementPtr element); + +- /// Adds an ElementPtr to the list ++ /// @brief Adds an ElementPtr to the list + /// @param element The ElementPtr to add + virtual void add(ElementPtr element); + +- /// Removes the element at the given position. If the index is out ++ /// @brief Removes the element at the given position. If the index is out + /// of nothing happens. + /// @param i The index of the element to remove. + virtual void remove(const int i); + +- /// Returns the number of elements in the list. ++ /// @brief Returns the number of elements in the list. + virtual size_t size() const; + +- /// Return true if there are no elements in the list. ++ /// @brief Return true if there are no elements in the list. + virtual bool empty() const; + //@} + +@@ -336,26 +473,26 @@ public: + /// @brief If the Element on which these functions are called are not + /// an instance of MapElement, a TypeError exception is thrown. + //@{ +- /// Returns the ElementPtr at the given key ++ /// @brief Returns the ElementPtr at the given key + /// @param name The key of the Element to return + /// @return The ElementPtr at the given key, or null if not present + virtual ConstElementPtr get(const std::string& name) const; + +- /// Sets the ElementPtr at the given key ++ /// @brief Sets the ElementPtr at the given key + /// @param name The key of the Element to set + /// @param element The ElementPtr to set at the given key. + virtual void set(const std::string& name, ConstElementPtr element); + +- /// Remove the ElementPtr at the given key ++ /// @brief Remove the ElementPtr at the given key + /// @param name The key of the Element to remove + virtual void remove(const std::string& name); + +- /// Checks if there is data at the given key ++ /// @brief Checks if there is data at the given key + /// @param name The key of the Element checked for existence + /// @return true if there is data at the key, false if not. + virtual bool contains(const std::string& name) const; + +- /// Recursively finds any data at the given identifier. The ++ /// @brief Recursively finds any data at the given identifier. The + /// identifier is a /-separated list of names of nested maps, with + /// the last name being the leaf that is returned. + /// +@@ -370,7 +507,7 @@ public: + /// Element::is_null(ElementPtr e). + virtual ConstElementPtr find(const std::string& identifier) const; + +- /// See @c Element::find() ++ /// @brief See @c Element::find() + /// @param identifier The identifier of the element to find + /// @param t Reference to store the resulting ElementPtr, if found. + /// @return true if the element was found, false if not. +@@ -396,25 +533,84 @@ public: + /// Notes: Read notes of IntElement definition about the use of + /// long long int, long int and int. + //@{ ++ /// @brief Create a NullElement. ++ /// ++ /// @param pos The position. ++ /// @return The NullElement at the position. + static ElementPtr create(const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create an IntElement. ++ /// ++ /// @param i The integer. ++ /// @param pos The position. ++ /// @return The IntElement with the argument at the position. + static ElementPtr create(const long long int i, + const Position& pos = ZERO_POSITION()); +- static ElementPtr create(const isc::util::int128_t& i, +- const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create an IntElement (int overload). ++ /// ++ /// @param i The integer. ++ /// @param pos The position. ++ /// @return The IntElement with the argument at the position. + static ElementPtr create(const int i, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create an IntElement (long int overload). ++ /// ++ /// @param i The integer. ++ /// @param pos The position. ++ /// @return The IntElement with the argument at the position. + static ElementPtr create(const long int i, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create an IntElement (int32_t overload). ++ /// ++ /// @param i The integer. ++ /// @param pos The position. ++ /// @return The IntElement with the argument at the position. + static ElementPtr create(const uint32_t i, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create a BigIntElement. ++ /// ++ /// @param i The big integer. ++ /// @param pos The position. ++ /// @return The BigIntElement with the argument at the position. ++ static ElementPtr create(const isc::util::int128_t& i, ++ const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create a DoubleElement. ++ /// ++ /// @param d The double. ++ /// @param pos The position. ++ /// @return The DoubleElement with the argument at the position. + static ElementPtr create(const double d, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create a BoolElement. ++ /// ++ /// @param b The boolean. ++ /// @param pos The position. ++ /// @return The BoolElement with the argument at the position. + static ElementPtr create(const bool b, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create a StringElement. ++ /// ++ /// @param s The string. ++ /// @param pos The position. ++ /// @return The StringElement with the argument at the position. + static ElementPtr create(const std::string& s, + const Position& pos = ZERO_POSITION()); ++ + // need both std:string and char *, since c++ will match + // bool before std::string when you pass it a char * ++ ++ /// @brief Create a StringElement (char* overload). ++ /// ++ /// @param s The string. ++ /// @param pos The position. ++ /// @return The StringElement with the argument at the position. + static ElementPtr create(const char *s, + const Position& pos = ZERO_POSITION()); + +@@ -438,7 +634,7 @@ public: + /// error, an exception of the type isc::data::JSONError is thrown. + + //@{ +- /// Creates an Element from the given JSON string ++ /// @brief Creates an Element from the given JSON string + /// @param in The string to parse the element from + /// @param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed +@@ -446,7 +642,7 @@ public: + /// in the given string. + static ElementPtr fromJSON(const std::string& in, bool preproc = false); + +- /// Creates an Element from the given input stream containing JSON ++ /// @brief Creates an Element from the given input stream containing JSON + /// formatted data. + /// + /// @param in The string to parse the element from +@@ -457,7 +653,7 @@ public: + /// in the given input stream. + static ElementPtr fromJSON(std::istream& in, bool preproc = false); + +- /// Creates an Element from the given input stream containing JSON ++ /// @brief Creates an Element from the given input stream containing JSON + /// formatted data. + /// + /// @param in The string to parse the element from +@@ -471,7 +667,7 @@ public: + static ElementPtr fromJSON(std::istream& in, const std::string& file_name, + bool preproc = false); + +- /// Creates an Element from the given input stream, where we keep ++ /// @brief Creates an Element from the given input stream, where we keep + /// track of the location in the stream for error reporting. + /// + /// @param in The string to parse the element from. +@@ -480,13 +676,14 @@ public: + /// track of the current line. + /// @param pos A reference to the int where the function keeps + /// track of the current position within the current line. +- /// @throw JSONError ++ /// @param level The maximum level of recursion. + /// @return An ElementPtr that contains the element(s) specified + /// in the given input stream. + // make this one private? + /// @throw JSONError + static ElementPtr fromJSON(std::istream& in, const std::string& file, +- int& line, int &pos); ++ int& line, int &pos, ++ unsigned level = MAX_NESTING_LEVEL); + + /// Reads contents of specified file and interprets it as JSON. + /// +@@ -499,23 +696,23 @@ public: + bool preproc = false); + //@} + +- /// @name Type name conversion functions ++ /// @name Type name conversion functions. + +- /// Returns the name of the given type as a string ++ /// @brief Returns the name of the given type as a string + /// +- /// @param type The type to return the name of ++ /// @param type The type to return the name of. + /// @return The name of the type, or "unknown" if the type + /// is not known. + static std::string typeToName(Element::types type); + +- /// Converts the string to the corresponding type ++ /// @brief Converts the string to the corresponding type + /// Throws a TypeError if the name is unknown. + /// +- /// @param type_name The name to get the type of ++ /// @param type_name The name to get the type of. + /// @return the corresponding type value + static Element::types nameToType(const std::string& type_name); + +- /// @brief input text preprocessor ++ /// @brief input text preprocessor. + /// + /// This method performs preprocessing of the input stream (which is + /// expected to contain a text version of to be parsed JSON). For now the +@@ -527,79 +724,43 @@ public: + /// the input stream, filters the content and returns the result in a + /// different stream. + /// +- /// @param in input stream to be preprocessed +- /// @param out output stream (filtered content will be written here) ++ /// @param in input stream to be preprocessed. ++ /// @param out output stream (filtered content will be written here). + static void preprocess(std::istream& in, std::stringstream& out); + + /// @name Wire format factory functions + +- /// These function pparse the wireformat at the given stringstream ++ /// These function parse the wireformat at the given stringstream + /// (of the given length). If there is a parse error an exception + /// of the type isc::cc::DecodeError is raised. + + //@{ +- /// Creates an Element from the wire format in the given ++ /// @brief Creates an Element from the wire format in the given + /// stringstream of the given length. +- /// Since the wire format is JSON, this is the same as ++ /// ++ /// @note: Since the wire format is JSON, this is the same as + /// fromJSON, and could be removed. + /// + /// @param in The input stringstream. +- /// @param length The length of the wireformat data in the stream ++ /// @param length The length of the wireformat data in the stream. + /// @return ElementPtr with the data that is parsed. + static ElementPtr fromWire(std::stringstream& in, int length); + +- /// Creates an Element from the wire format in the given string +- /// Since the wire format is JSON, this is the same as ++ /// @brief Creates an Element from the wire format in the given string. ++ /// ++ /// @note: Since the wire format is JSON, this is the same as + /// fromJSON, and could be removed. + /// +- /// @param s The input string ++ /// @param s The input string. + /// @return ElementPtr with the data that is parsed. + static ElementPtr fromWire(const std::string& s); + //@} + + /// @brief Remove all empty maps and lists from this Element and its + /// descendants. +- void removeEmptyContainersRecursively() { +- if (type_ == list || type_ == map) { +- size_t s(size()); +- for (size_t i = 0; i < s; ++i) { +- // Get child. +- ElementPtr child; +- if (type_ == list) { +- child = getNonConst(i); +- } else if (type_ == map) { +- std::string const key(get(i)->stringValue()); +- // The ElementPtr - ConstElementPtr disparity between +- // ListElement and MapElement is forcing a const cast here. +- // It's undefined behavior to modify it after const casting. +- // The options are limited. I've tried templating, moving +- // this function from a member function to free-standing and +- // taking the Element template as argument. I've tried +- // making it a virtual function with overridden +- // implementations in ListElement and MapElement. Nothing +- // works. +- child = boost::const_pointer_cast(get(key)); +- } +- +- // Makes no sense to continue for non-container children. +- if (child->getType() != list && child->getType() != map) { +- continue; +- } +- +- // Recurse if not empty. +- if (!child->empty()){ +- child->removeEmptyContainersRecursively(); +- } +- +- // When returning from recursion, remove if empty. +- if (child->empty()) { +- remove(i); +- --i; +- --s; +- } +- } +- } +- } ++ /// ++ /// @param level nesting level. ++ void removeEmptyContainersRecursively(unsigned level = MAX_NESTING_LEVEL); + }; + + /// Notes: IntElement type is changed to int64_t. +@@ -622,8 +783,10 @@ public: + bool getValue(int64_t& t) const { t = i; return (true); } + using Element::setValue; + bool setValue(long long int v) { i = v; return (true); } +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + /// @brief Wrapper over int128_t +@@ -655,13 +818,20 @@ public: + + /// @brief Converts the Element to JSON format and appends it to the given + /// stringstream. +- void toJSON(std::ostream& ss) const override; ++ /// ++ /// @param ss The output stream where to append the JSON format. ++ /// @param level The maximum level of recursion. Ignored. ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const override; + + /// @brief Checks whether the other Element is equal. + /// ++ /// @param other The other element to compare with. ++ /// @param level The maximum level of recursion. Ignored. + /// @return true if the other ElementPtr has the same value and the same + /// type (or a different and compatible type), false otherwise. +- bool equals(const Element& other) const override; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const override; + + private: + /// @brief the underlying stored value +@@ -673,14 +843,16 @@ class DoubleElement : public Element { + + public: + DoubleElement(double v, const Position& pos = ZERO_POSITION()) +- : Element(real, pos), d(v) {}; ++ : Element(real, pos), d(v) {} + double doubleValue() const { return (d); } + using Element::getValue; + bool getValue(double& t) const { t = d; return (true); } + using Element::setValue; + bool setValue(const double v) { d = v; return (true); } +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + class BoolElement : public Element { +@@ -688,22 +860,26 @@ class BoolElement : public Element { + + public: + BoolElement(const bool v, const Position& pos = ZERO_POSITION()) +- : Element(boolean, pos), b(v) {}; ++ : Element(boolean, pos), b(v) {} + bool boolValue() const { return (b); } + using Element::getValue; + bool getValue(bool& t) const { t = b; return (true); } + using Element::setValue; + bool setValue(const bool v) { b = v; return (true); } +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + class NullElement : public Element { + public: + NullElement(const Position& pos = ZERO_POSITION()) +- : Element(null, pos) {}; +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ : Element(null, pos) {} ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + class StringElement : public Element { +@@ -711,14 +887,16 @@ class StringElement : public Element { + + public: + StringElement(std::string v, const Position& pos = ZERO_POSITION()) +- : Element(string, pos), s(v) {}; ++ : Element(string, pos), s(v) {} + std::string stringValue() const { return (s); } + using Element::getValue; + bool getValue(std::string& t) const { t = s; return (true); } + using Element::setValue; + bool setValue(const std::string& v) { s = v; return (true); } +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + class ListElement : public Element { +@@ -745,13 +923,15 @@ public: + void set(size_t i, ElementPtr e) { + l.at(i) = e; + } +- void add(ElementPtr e) { l.push_back(e); }; ++ void add(ElementPtr e) { l.push_back(e); } + using Element::remove; +- void remove(int i) { l.erase(l.begin() + i); }; +- void toJSON(std::ostream& ss) const; ++ void remove(int i) { l.erase(l.begin() + i); } ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; + size_t size() const { return (l.size()); } + bool empty() const { return (l.empty()); } +- bool equals(const Element& other) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + + /// @brief Sorts the elements inside the list. + /// +@@ -820,7 +1000,8 @@ public: + bool contains(const std::string& s) const override { + return (m.find(s) != m.end()); + } +- void toJSON(std::ostream& ss) const override; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const override; + + // we should name the two finds better... + // find the element at id; raises TypeError if one of the +@@ -842,46 +1023,60 @@ public: + return (m.size()); + } + +- bool equals(const Element& other) const override; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const override; + + bool empty() const override { return (m.empty()); } + }; + +-/// Checks whether the given ElementPtr is a NULL pointer ++/// Checks whether the given ElementPtr is a null pointer + /// @param p The ElementPtr to check +-/// @return true if it is NULL, false if not. ++/// @return true if it is null, false if not. + bool isNull(ConstElementPtr p); + + /// + /// @brief Remove all values from the first ElementPtr that are + /// equal in the second. Both ElementPtrs MUST be MapElements ++/// + /// The use for this function is to end up with a MapElement that + /// only contains new and changed values (for ModuleCCSession and + /// configuration update handlers) +-/// Raises a TypeError if a or b are not MapElements ++/// ++/// @param a Pointer to the first element. ++/// @param b Pointer to the second element. ++/// @throw TypeError if a or b are not MapElements + void removeIdentical(ElementPtr a, ConstElementPtr b); + + /// @brief Create a new ElementPtr from the first ElementPtr, removing all + /// values that are equal in the second. Both ElementPtrs MUST be MapElements. +-/// The returned ElementPtr will be a MapElement that only contains new and +-/// changed values (for ModuleCCSession and configuration update handlers). +-/// Raises a TypeError if a or b are not MapElements ++/// ++/// @param a Pointer to the first element. ++/// @param b Pointer to the second element. ++/// @throw TypeError if a or b are not MapElements ++/// @return ElementPtr will be a MapElement that only contains new and changed ++/// values (for ModuleCCSession and configuration update handlers). + ConstElementPtr removeIdentical(ConstElementPtr a, ConstElementPtr b); + +-/// @brief Merges the data from other into element. (on the first level). Both +-/// elements must be MapElements. Every string, value pair in other is copied +-/// into element (the ElementPtr of value is copied, this is not a new object) +-/// Unless the value is a NullElement, in which case the key is removed from +-/// element, rather than setting the value to the given NullElement. ++/// @brief Merges the data from other into element. (on the first level). ++ ++/// Both elements must be MapElements. Every string, value pair in ++/// other is copied into element (the ElementPtr of value is copied, ++/// this is not a new object) Unless the value is a NullElement, in ++/// which case the key is removed from element, rather than setting ++/// the value to the given NullElement. + /// This way, we can remove values from for instance maps with configuration + /// data (which would then result in reverting back to the default). +-/// Raises a TypeError if either ElementPtr is not a MapElement ++/// ++/// @param element Pointer to the Element holding data. ++/// @param other Pointer to the other / from Element. ++/// @throw TypeError if either ElementPtr is not a MapElement + void merge(ElementPtr element, ConstElementPtr other); + + /// @brief Function used to check if two MapElements refer to the same +-/// configuration data. It can check if the two MapElements have the same or +-/// have equivalent value for some members. +-/// e.g. ++/// configuration data. ++/// ++/// It can check if the two MapElements have the same or have ++/// equivalent value for some members. e.g. + /// ( + /// left->get("prefix")->stringValue() == right->get("prefix")->stringValue() && + /// left->get("prefix-len")->intValue() == right->get("prefix-len")->intValue() && +@@ -925,7 +1120,7 @@ typedef std::vector Hierarc + + /// @brief Merges the diff data by adding the missing elements from 'other' + /// to 'element' (recursively). Both elements must be the same Element type. +-/// Raises a TypeError if elements are not the same Element type. ++/// + /// @note + /// for non map and list elements the values are updated with the new values + /// for maps: +@@ -941,13 +1136,15 @@ typedef std::vector Hierarc + /// identification keys. + /// @param key The container holding the current element. + /// @param idx The level inside the hierarchy the current element is located. ++/// @param level The maximum level of recursion. ++/// @throw TypeError if elements are not the same Element type. + void mergeDiffAdd(ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, +- size_t idx = 0); ++ size_t idx = 0, unsigned level = Element::MAX_NESTING_LEVEL); + + /// @brief Merges the diff data by removing the data present in 'other' from + /// 'element' (recursively). Both elements must be the same Element type. +-/// Raises a TypeError if elements are not the same Element type. ++//// + /// for non map and list elements the values are set to NullElement + /// for maps: + /// - non map and list elements are removed from the map +@@ -962,14 +1159,15 @@ void mergeDiffAdd(ElementPtr& element, E + /// identification keys. + /// @param key The container holding the current element. + /// @param idx The level inside the hierarchy the current element is located. ++/// @param level The maximum level of recursion. ++/// @throw TypeError if elements are not the same Element type. + void mergeDiffDel(ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, +- size_t idx = 0); ++ size_t idx = 0, unsigned level = Element::MAX_NESTING_LEVEL); + + /// @brief Extends data by adding the specified 'extension' elements from + /// 'other' inside the 'container' element (recursively). Both elements must be + /// the same Element type. +-/// Raises a TypeError if elements are not the same Element type. + /// + /// @param container The container holding the data that must be extended. + /// @param extension The name of the element that contains the data that must be +@@ -982,30 +1180,53 @@ void mergeDiffDel(ElementPtr& element, E + /// @param idx The level inside the hierarchy the current element is located. + /// @param alter The flag which indicates if the current element should be + /// updated. ++/// @param level The maximum level of recursion. ++/// @throw TypeError if elements are not the same Element type. + void extend(const std::string& container, const std::string& extension, + ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, size_t idx = 0, +- bool alter = false); ++ bool alter = false, unsigned level = Element::MAX_NESTING_LEVEL); + + /// @brief Copy the data up to a nesting level. + /// + /// The copy is a deep copy so nothing is shared if it is not + /// under the given nesting level. + /// +-/// @param from the pointer to the element to copy +-/// @param level nesting level (default is 100, 0 means shallow copy, +-/// negative means outbound and perhaps looping forever). +-/// @return a pointer to a fresh copy ++/// @note: copy is the ONLY method taking a level argument which make ++/// sense outside unit tests, and also which accepts the 0 value. ++/// ++/// @param from the pointer to the element to copy. ++/// @param level nesting level (default is 100, 0 means shallow copy). ++/// @return Pointer to a fresh copy + /// @throw raises a BadValue is a null pointer occurs. +-ElementPtr copy(ConstElementPtr from, int level = 100); ++ElementPtr copy(ConstElementPtr from, unsigned level = Element::MAX_NESTING_LEVEL); + +-/// @brief Compares the data with other using unordered lists ++/// @brief Compares the data with other using unordered lists. + /// + /// This comparison function handles lists (JSON arrays) as + /// unordered multi sets (multi means an item can occurs more + /// than once as soon as it occurs the same number of times). ++/// ++/// @param a Pointer to the first element. ++/// @param b Pointer to the second element. ++/// @return Result of loose comparison. + bool isEquivalent(ConstElementPtr a, ConstElementPtr b); + ++/// @brief Check if the data is circular. ++/// ++/// @param element The @c ConstElementPtr object to check. ++/// @return True if the argument is circular, false otherwise. ++bool IsCircular(ConstElementPtr element); ++ ++/// @brief Compute the nesting depth. ++/// ++/// @param element The @c ConstElementPtr object. ++/// @param max_depth Maximal nesting depth. ++/// @return The nesting depth or max_depth if the object has deeper nesting ++/// including being circular. ++unsigned getNestDepth(ConstElementPtr element, ++ unsigned max_depth = Element::MAX_NESTING_LEVEL); ++ + /// @brief Pretty prints the data into stream. + /// + /// This operator converts the @c ConstElementPtr into a string and +@@ -1013,23 +1234,23 @@ bool isEquivalent(ConstElementPtr a, Con + /// indentation @c indent and add at each level @c step spaces. + /// For maps if there is a comment property it is printed first. + /// +-/// @param element A @c ConstElementPtr to pretty print +-/// @param out A @c std::ostream on which the print operation is performed +-/// @param indent An initial number of spaces to add each new line +-/// @param step A number of spaces to add to indentation at a new level ++/// @param element A @c ConstElementPtr to pretty print. ++/// @param out A @c std::ostream on which the print operation is performed. ++/// @param indent An initial number of spaces to add each new line. ++/// @param step A number of spaces to add to indentation at a new level. + void prettyPrint(ConstElementPtr element, std::ostream& out, + unsigned indent = 0, unsigned step = 2); + +-/// @brief Pretty prints the data into string ++/// @brief Pretty prints the data into string. + /// + /// This operator converts the @c ConstElementPtr into a string with + /// an initial indentation @c indent and add at each level @c step spaces. + /// For maps if there is a comment property it is printed first. + /// +-/// @param element A @c ConstElementPtr to pretty print +-/// @param indent An initial number of spaces to add each new line +-/// @param step A number of spaces to add to indentation at a new level +-/// @return a string where element was pretty printed ++/// @param element A @c ConstElementPtr to pretty print. ++/// @param indent An initial number of spaces to add each new line. ++/// @param step A number of spaces to add to indentation at a new level. ++/// @return a string where element was pretty printed. + std::string prettyPrint(ConstElementPtr element, + unsigned indent = 0, unsigned step = 2); + +@@ -1061,8 +1282,31 @@ std::ostream& operator<<(std::ostream& o + /// parameter @c out after the insertion operation. + std::ostream& operator<<(std::ostream& out, const Element& e); + ++/// @brief Test equality. ++/// ++/// @param a First element. ++/// @param b Second Element. ++/// @return True when the two elements are equal, false otherwise. + bool operator==(const Element& a, const Element& b); ++ ++/// @brief Test inequality. ++/// ++/// @param a First element. ++/// @param b Second Element. ++/// @return True when the two elements are not equal, false otherwise. + bool operator!=(const Element& a, const Element& b); ++ ++/// @brief Test less than. ++/// ++/// @note: both arguments must have the same supported type i.e. integer, ++/// double, boolean or string. ++/// ++/// @param a First element. ++/// @param b Second Element. ++/// @return True when the value of the first element is less than the value ++/// of the second element. ++/// @throw BadValue when arguments have different type or the type is not ++/// supported. + bool operator<(const Element& a, const Element& b); + + } // namespace data +--- isc-kea-2.6.3.orig/src/lib/cc/tests/data_unittests.cc ++++ isc-kea-2.6.3/src/lib/cc/tests/data_unittests.cc +@@ -1077,7 +1077,7 @@ TEST(Element, copy) { + ElementPtr elem; + EXPECT_THROW(copy(elem, 0), isc::BadValue); + EXPECT_THROW(copy(elem), isc::BadValue); +- EXPECT_THROW(copy(elem, -1), isc::BadValue); ++ EXPECT_THROW(copy(elem, static_cast(-1)), isc::BadValue); + + // Basic types + elem.reset(new IntElement(1)); +@@ -1653,6 +1653,108 @@ TEST(Element, mergeDiffAddBadParams) { + right->add(right_right); + ASSERT_THROW(mergeDiffAdd(left, right, hierarchy, "root"), TypeError); + } ++ { ++ SCOPED_TRACE("nested map"); ++ // From 'scalar and list and map in map'. ++ isc::data::HierarchyDescriptor hierarchy; ++ hierarchy = createHierarchy(); ++ ElementPtr left = Element::createMap(); ++ ElementPtr right = Element::createMap(); ++ ElementPtr left_left = Element::createMap(); ++ ElementPtr right_right = Element::createMap(); ++ left_left->set("id", Element::create(0)); ++ left_left->set("elements", Element::create("left")); ++ left_left->set("other-elements", Element::create("other")); ++ // scalar element used as key ++ right_right->set("id", Element::create(0)); ++ // scalar element which is updated ++ right_right->set("elements", Element::create("right")); ++ // scalar element which is added ++ right_right->set("new-elements", Element::create("new")); ++ ElementPtr left_other_left = Element::createMap(); ++ ElementPtr right_other_right = Element::createMap(); ++ left_other_left->set("id", Element::create(1)); ++ left_other_left->set("elements", Element::create("other-left")); ++ // scalar element used as key ++ right_other_right->set("id", Element::create(2)); ++ // scalar element which is added ++ right_other_right->set("elements", Element::create("other-right")); ++ left->set("elements", left_left); ++ left->set("left-other-elements", left_other_left); ++ // map element which is added ++ right->set("right-other-elements", right_other_right); ++ // map element which is updated ++ right->set("elements", right_right); ++ left_other_left = Element::createList(); ++ right_other_right = Element::createList(); ++ left_other_left->add(Element::create("left-other-left")); ++ left_other_left->add(Element::create("left-other-left-other")); ++ left_other_left->add(Element::create("other-other")); ++ // scalar element which is added ++ right_other_right->add(Element::create("right-other-right")); ++ // scalar element which is added ++ right_other_right->add(Element::create("right-other-right-other")); ++ // scalar element which already exists but is still added ++ right_other_right->add(Element::create("other-other")); ++ left->set("other", left_other_left); ++ // list element which is updated ++ right->set("other", right_other_right); ++ ASSERT_FALSE(isc::data::isEquivalent(left, right)); ++ // Uses 2 levels of nesting so throw with 1 (and pass with 2 or more). ++ EXPECT_THROW(mergeDiffAdd(left, right, hierarchy, "root", 0, 1), ++ isc::BadValue); ++ } ++ { ++ SCOPED_TRACE("nested list"); ++ // From 'scalar and list and map in list'. ++ isc::data::HierarchyDescriptor hierarchy; ++ hierarchy = createHierarchy(); ++ ElementPtr left = Element::createList(); ++ ElementPtr right = Element::createList(); ++ ElementPtr left_left = Element::createMap(); ++ ElementPtr right_right = Element::createMap(); ++ left_left->set("id", Element::create(0)); ++ left_left->set("elements", Element::create("left")); ++ left_left->set("other-elements", Element::create("other")); ++ // scalar element used as key ++ right_right->set("id", Element::create(0)); ++ // scalar element which is updated ++ right_right->set("elements", Element::create("right")); ++ // scalar element which is added ++ right_right->set("new-elements", Element::create("new")); ++ ElementPtr left_other_left = Element::createMap(); ++ ElementPtr right_other_right = Element::createMap(); ++ left_other_left->set("id", Element::create(1)); ++ left_other_left->set("elements", Element::create("other-left")); ++ // scalar element used as key ++ right_other_right->set("id", Element::create(2)); ++ // scalar element which is added ++ right_other_right->set("elements", Element::create("other-right")); ++ left->add(left_left); ++ left->add(left_other_left); ++ // map element which is added ++ right->add(right_other_right); ++ // map element which is updated ++ right->add(right_right); ++ left_other_left = Element::createList(); ++ right_other_right = Element::createList(); ++ left_other_left->add(Element::create("left-other-left")); ++ left_other_left->add(Element::create("left-other-left-other")); ++ left_other_left->add(Element::create("other-other")); ++ // scalar element which is added ++ right_other_right->add(Element::create("right-other-right")); ++ // scalar element which is added ++ right_other_right->add(Element::create("right-other-right-other")); ++ // scalar element which already exists but is still added ++ right_other_right->add(Element::create("other-other")); ++ left_left->set("other", left_other_left); ++ // list element which is updated ++ right_right->set("other", right_other_right); ++ ASSERT_FALSE(isc::data::isEquivalent(left, right)); ++ // Uses 3 levels of nesting so throw with 2 (and pass with 3 or more). ++ EXPECT_THROW(mergeDiffAdd(left, right, hierarchy, "root", 0, 2), ++ isc::BadValue); ++ } + } + + /// @brief Test which checks that mergeDiffAdd works as expected. +@@ -1802,7 +1904,7 @@ TEST(Element, mergeDiffAdd) { + // list element which is updated + right->set("other", right_other_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); +- mergeDiffAdd(left, right, hierarchy, "root"); ++ ASSERT_NO_THROW(mergeDiffAdd(left, right, hierarchy, "root", 0, 2)); + std::string expected_str("{ \"elements\": { \"elements\": \"right\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" }, " + "\"left-other-elements\": { \"elements\": \"other-left\", \"id\": 1 }, " + "\"other\": [ \"left-other-left\", \"left-other-left-other\", \"other-other\", \"right-other-right\", \"right-other-right-other\", \"other-other\" ], " +@@ -1858,7 +1960,7 @@ TEST(Element, mergeDiffAdd) { + // list element which is updated + right_right->set("other", right_other_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); +- mergeDiffAdd(left, right, hierarchy, "root"); ++ ASSERT_NO_THROW(mergeDiffAdd(left, right, hierarchy, "root", 0, 3)); + std::string expected_str("[ { \"elements\": \"right\", \"id\": 0, \"new-elements\": \"new\", " + "\"other\": [ \"left-other-left\", \"left-other-left-other\", \"other-other\", \"right-other-right\", \"right-other-right-other\", \"other-other\" ], " + "\"other-elements\": \"other\" }, " +@@ -1907,6 +2009,136 @@ TEST(Element, mergeDiffDelBadParams) { + right->add(right_right); + ASSERT_THROW(mergeDiffDel(left, right, hierarchy, "root"), TypeError); + } ++ { ++ SCOPED_TRACE("nested map"); ++ // From 'scalar and list and map in map'. ++ isc::data::HierarchyDescriptor hierarchy; ++ hierarchy = createHierarchy(); ++ ElementPtr left = Element::createMap(); ++ ElementPtr right = Element::createMap(); ++ ElementPtr left_left = Element::createMap(); ++ ElementPtr right_right = Element::createMap(); ++ left_left->set("id", Element::create(0)); ++ left_left->set("elements", Element::create("left")); ++ left_left->set("other-elements", Element::create("other")); ++ // scalar element used as key ++ right_right->set("id", Element::create(0)); ++ // scalar element which is removed ++ right_right->set("elements", Element::create("right")); ++ // scalar element which does not exist and does nothing ++ right_right->set("new-elements", Element::create("new")); ++ ElementPtr left_other_left = Element::createMap(); ++ ElementPtr right_other_right = Element::createMap(); ++ left_other_left->set("id", Element::create(1)); ++ left_other_left->set("elements", Element::create("other-left")); ++ // scalar element used as key ++ right_other_right->set("id", Element::create(2)); ++ // scalar element which does not exist and does nothing ++ right_other_right->set("elements", Element::create("other-right")); ++ left->set("elements", left_left); ++ left->set("left-other-elements", left_other_left); ++ // map element which does not exist and does nothing ++ right->set("right-other-elements", right_other_right); ++ // map element which is updated ++ right->set("elements", right_right); ++ left_other_left = Element::createList(); ++ right_other_right = Element::createList(); ++ left_other_left->add(Element::create("left-other-left")); ++ left_other_left->add(Element::create("other")); ++ left_other_left->add(Element::create("left-other-left-other")); ++ left_other_left->add(Element::create("new")); ++ // scalar element which does not exist and does nothing ++ right_other_right->add(Element::create("right-other-right")); ++ // scalar element which is removed ++ right_other_right->add(Element::create("other")); ++ // scalar element which does not exist and does nothing ++ right_other_right->add(Element::create("right-other-right-other")); ++ // scalar element which is removed ++ right_other_right->add(Element::create("new")); ++ left->set("other", left_other_left); ++ // list element which is updated ++ right->set("other", right_other_right); ++ left_left = Element::createMap(); ++ right_right = Element::createMap(); ++ left_left->set("id", Element::create(3)); ++ left_left->set("elements", Element::create("new-left")); ++ left_left->set("other-elements", Element::create("new-other")); ++ left->set("elements-other", left_left); ++ // scalar element used as key ++ right_right->set("id", Element::create(3)); ++ // map element which is not removed because it is contained in a map and ++ // the key can not be removed ++ right->set("elements-other", right_right); ++ ASSERT_FALSE(isc::data::isEquivalent(left, right)); ++ // Uses 2 levels of nesting so throw with 1 (and pass with 2 or more). ++ EXPECT_THROW(mergeDiffDel(left, right, hierarchy, "root", 0, 1), ++ isc::BadValue); ++ } ++ { ++ SCOPED_TRACE("nested list"); ++ // From 'scalar and list and map in list'. ++ isc::data::HierarchyDescriptor hierarchy; ++ hierarchy = createHierarchy(); ++ ElementPtr left = Element::createList(); ++ ElementPtr right = Element::createList(); ++ ElementPtr left_left = Element::createMap(); ++ ElementPtr right_right = Element::createMap(); ++ left_left->set("id", Element::create(0)); ++ left_left->set("elements", Element::create("left")); ++ left_left->set("other-elements", Element::create("other")); ++ // scalar element used as key ++ right_right->set("id", Element::create(0)); ++ // scalar element which is removed ++ right_right->set("elements", Element::create("right")); ++ // scalar element which does not exist and does nothing ++ right_right->set("new-elements", Element::create("new")); ++ ElementPtr left_other_left = Element::createMap(); ++ ElementPtr right_other_right = Element::createMap(); ++ left_other_left->set("id", Element::create(1)); ++ left_other_left->set("elements", Element::create("other-left")); ++ // scalar element used as key ++ right_other_right->set("id", Element::create(2)); ++ // scalar element which does not exist and does nothing ++ right_other_right->set("elements", Element::create("other-right")); ++ left->add(left_left); ++ left->add(left_other_left); ++ // map element which does not exist and does nothing ++ right->add(right_other_right); ++ // map element which is updated ++ right->add(right_right); ++ left_other_left = Element::createList(); ++ right_other_right = Element::createList(); ++ left_other_left->add(Element::create("left-other-left")); ++ left_other_left->add(Element::create("other")); ++ left_other_left->add(Element::create("left-other-left-other")); ++ left_other_left->add(Element::create("new")); ++ // scalar element which does not exist and does nothing ++ right_other_right->add(Element::create("right-other-right")); ++ // scalar element which is removed ++ right_other_right->add(Element::create("other")); ++ // scalar element which does not exist and does nothing ++ right_other_right->add(Element::create("right-other-right-other")); ++ // scalar element which is removed ++ right_other_right->add(Element::create("new")); ++ left_left->set("other", left_other_left); ++ // list element which is updated ++ right_right->set("other", right_other_right); ++ left_left = Element::createMap(); ++ right_right = Element::createMap(); ++ left_left->set("id", Element::create(3)); ++ left_left->set("elements", Element::create("new-left")); ++ left_left->set("other-elements", Element::create("new-other")); ++ left->add(left_left); ++ // scalar element used as key ++ right_right->set("id", Element::create(3)); ++ // map element which is removed by key ++ // the key can not be removed ++ right->add(right_right); ++ ASSERT_FALSE(isc::data::isEquivalent(left, right)); ++ // Uses 3 levels of nesting so throw with 2 (and pass with 3 or more). ++ EXPECT_THROW(mergeDiffDel(left, right, hierarchy, "root", 0, 2), ++ isc::BadValue); ++ } + } + + /// @brief Test which checks that mergeDiffDel works as expected. +@@ -2053,7 +2285,7 @@ TEST(Element, mergeDiffDel) { + // the key can not be removed + right->set("elements-other", right_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); +- mergeDiffDel(left, right, hierarchy, "root"); ++ ASSERT_NO_THROW(mergeDiffDel(left, right, hierarchy, "root", 0, 2)); + std::string expected_str("{ \"elements\": { \"id\": 0, \"other-elements\": \"other\" }, " + "\"elements-other\": { \"elements\": \"new-left\", \"id\": 3, \"other-elements\": \"new-other\" }, " + "\"left-other-elements\": { \"elements\": \"other-left\", \"id\": 1 }, " +@@ -2123,7 +2355,7 @@ TEST(Element, mergeDiffDel) { + // the key can not be removed + right->add(right_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); +- mergeDiffDel(left, right, hierarchy, "root"); ++ ASSERT_NO_THROW(mergeDiffDel(left, right, hierarchy, "root", 0, 3)); + std::string expected_str("[ { \"id\": 0, \"other\": [ \"left-other-left\", \"left-other-left-other\" ], \"other-elements\": \"other\" }, " + "{ \"elements\": \"other-left\", \"id\": 1 } ]"); + ElementPtr expected = Element::fromJSON(expected_str); +@@ -2169,6 +2401,58 @@ TEST(Element, extendBadParam) { + right->add(right_right); + ASSERT_THROW(extend("elements", "", left, right, hierarchy, "root"), TypeError); + } ++ { ++ SCOPED_TRACE("nested map"); ++ /// From 'scalar in map in map'. ++ isc::data::HierarchyDescriptor hierarchy; ++ hierarchy = createHierarchy(true); ++ ElementPtr left = Element::createMap(); ++ ElementPtr right = Element::createMap(); ++ ElementPtr left_left = Element::createMap(); ++ ElementPtr right_right = Element::createMap(); ++ left_left->set("id", Element::create(0)); ++ left_left->set("elements", Element::create("left")); ++ left_left->set("other-elements", Element::create("other")); ++ // scalar element used as key ++ right_right->set("id", Element::create(1)); ++ // scalar element which is not updated ++ right_right->set("elements", Element::create("right")); ++ // scalar element which is extended ++ right_right->set("new-elements", Element::create("new")); ++ left->set("elements", left_left); ++ // map element which is used for extension ++ right->set("elements", right_right); ++ ASSERT_FALSE(isc::data::isEquivalent(left, right)); ++ // Uses 2 levels of nesting so throw with 1 (and pass with 2 or more). ++ EXPECT_THROW(extend("root", "new-elements", left, right, hierarchy, ++ "root", 0, false, 1), isc::BadValue); ++ } ++ { ++ SCOPED_TRACE("nested list"); ++ // From 'scalar in map in list'. ++ isc::data::HierarchyDescriptor hierarchy; ++ hierarchy = createHierarchy(true); ++ ElementPtr left = Element::createList(); ++ ElementPtr right = Element::createList(); ++ ElementPtr left_left = Element::createMap(); ++ ElementPtr right_right = Element::createMap(); ++ left_left->set("id", Element::create(0)); ++ left_left->set("elements", Element::create("left")); ++ left_left->set("other-elements", Element::create("other")); ++ // scalar element used as key ++ right_right->set("id", Element::create(1)); ++ // scalar element which is not updated ++ right_right->set("elements", Element::create("right")); ++ // scalar element which is extended ++ right_right->set("new-elements", Element::create("new")); ++ left->add(left_left); ++ // map element which is used for extension ++ right->add(right_right); ++ ASSERT_FALSE(isc::data::isEquivalent(left, right)); ++ // Uses 2 levels of nesting so throw with 1 (and pass with 2 or more). ++ EXPECT_THROW(extend("root", "new-elements", left, right, hierarchy, ++ "root", 0, false, 1), isc::BadValue); ++ } + } + + /// @brief Test which checks that extend works as expected. +@@ -2234,7 +2518,8 @@ TEST(Element, extend) { + // map element which is used for extension + right->set("elements", right_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); +- extend("root", "new-elements", left, right, hierarchy, "root"); ++ ASSERT_NO_THROW(extend("root", "new-elements", left, right, hierarchy, ++ "root", 0, false, 2)); + std::string expected_str("{ \"elements\": { \"elements\": \"left\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" } }"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) +@@ -2262,7 +2547,8 @@ TEST(Element, extend) { + // map element which is used for extension + right->add(right_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); +- extend("root", "new-elements", left, right, hierarchy, "root"); ++ ASSERT_NO_THROW(extend("root", "new-elements", left, right, hierarchy, ++ "root", 0, false, 2)); + std::string expected_str("[ { \"elements\": \"left\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" } ]"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) +@@ -2271,4 +2557,490 @@ TEST(Element, extend) { + } + } + ++/// @brief Create nested lists. ++/// ++/// @param leaf The leaf. ++/// @param level The nested level. ++ElementPtr ++mkNestedList(ElementPtr leaf, size_t level) { ++ if (!leaf && !level) { ++ isc_throw(isc::BadValue, "need level > 0 for null leaf"); ++ } ++ ElementPtr child = leaf; ++ ElementPtr list; ++ for (size_t i = 0; i < level; ++i) { ++ list = Element::createList(); ++ if (child) { ++ list->add(child); ++ } ++ child = list; ++ } ++ return (child); ++} ++ ++/// @brief Create nested maps. ++/// ++/// @param leaf The leaf. ++/// @param level The nested level. ++ElementPtr ++mkNestedMap(ElementPtr leaf, size_t level) { ++ if (!leaf && !level) { ++ isc_throw(isc::BadValue, "need level > 0 for null leaf"); ++ } ++ ElementPtr child = leaf; ++ ElementPtr map; ++ for (size_t i = 0; i < level; ++i) { ++ map = Element::createMap(); ++ if (child) { ++ std::ostringstream key; ++ key << (level - (i + 1)); ++ map->set(key.str(), child); ++ } ++ child = map; ++ } ++ return (child); ++} ++ ++/// @brief Create list cycle. ++/// ++/// @param length Cycle length (default 0). ++ElementPtr ++mkCycleList(size_t length = 0) { ++ ElementPtr head = Element::createList(); ++ ElementPtr child = head; ++ for (size_t i = 0; i < length; ++i) { ++ ElementPtr list = Element::createList(); ++ child->add(list); ++ child = list; ++ } ++ child->add(head); ++ return (head); ++} ++ ++/// @brief Create map cycle. ++/// ++/// @param length Cycle length (default 0). ++ElementPtr ++mkCycleMap(size_t length = 0) { ++ ElementPtr head = Element::createMap(); ++ ElementPtr child = head; ++ for (size_t i = 0; i < length; ++i) { ++ ElementPtr map = Element::createMap(); ++ std::ostringstream key; ++ key << i; ++ child->set(key.str(), map); ++ child = map; ++ } ++ child->set("rec", head); ++ return (head); ++} ++ ++/// @brief Equals on nested list. ++TEST(Element, nestedListEquals) { ++ ElementPtr leaf = Element::create(1); ++ for (size_t level = 0; level < 111; ++level) { ++ ElementPtr list = mkNestedList(leaf, level); ++ try { ++ EXPECT_TRUE(list->equals(*list)); ++ } catch (const isc::BadValue&) { ++ EXPECT_EQ(Element::MAX_NESTING_LEVEL + 1, level); ++ break; ++ } catch (const std::exception& e) { ++ FAIL() << "Got exception " << e.what(); ++ } ++ } ++} ++ ++/// @brief Equals on nested map. ++TEST(Element, nestedMapEquals) { ++ ElementPtr leaf = Element::create(true); ++ for (size_t level = 0; level < 111; ++level) { ++ ElementPtr map = mkNestedMap(leaf, level); ++ try { ++ EXPECT_TRUE(map->equals(*map)); ++ } catch (const isc::BadValue&) { ++ EXPECT_EQ(Element::MAX_NESTING_LEVEL + 1, level); ++ break; ++ } catch (const std::exception& e) { ++ FAIL() << "Got exception " << e.what(); ++ } ++ } ++} ++ ++/// @brief Equals on cycle. ++TEST(Element, cycleEquals) { ++ ElementPtr cycle = mkCycleList(); ++ EXPECT_THROW(cycle->equals(*cycle), isc::BadValue); ++ cycle->remove(0); ++ cycle = mkCycleList(10); ++ EXPECT_THROW(cycle->equals(*cycle), isc::BadValue); ++ cycle->remove(0); ++ cycle = mkCycleMap(); ++ EXPECT_THROW(cycle->equals(*cycle), isc::BadValue); ++ cycle->set("rec", Element::createMap()); ++ cycle = mkCycleMap(10); ++ EXPECT_THROW(cycle->equals(*cycle), isc::BadValue); ++ cycle->set("0", Element::createMap()); ++} ++ ++/// @brief str/toJSON on nested list. ++TEST(Element, nestedListStr) { ++ ElementPtr leaf = Element::create(1); ++ for (size_t level = 0; level < 111; ++level) { ++ ElementPtr list = mkNestedList(leaf, level); ++ try { ++ std::string out = list->str(); ++ EXPECT_EQ(4 * level + 1, out.size()); ++ std::ostringstream expected; ++ for (size_t i = 0; i < level; i++) { ++ expected << "[ "; ++ } ++ expected << "1"; ++ for (size_t i = 0; i < level; i++) { ++ expected << " ]"; ++ } ++ EXPECT_EQ(expected.str(), out); ++ } catch (const isc::BadValue&) { ++ EXPECT_EQ(Element::MAX_NESTING_LEVEL + 1, level); ++ break; ++ } catch (const std::exception& e) { ++ FAIL() << "Got exception " << e.what(); ++ } ++ } ++} ++ ++/// @brief str/toJSON on nested map. ++TEST(Element, nestedMapStr) { ++ ElementPtr leaf = Element::create(true); ++ for (size_t level = 0; level < 111; ++level) { ++ ElementPtr map = mkNestedMap(leaf, level); ++ try { ++ std::string out = map->str(); ++ std::ostringstream expected; ++ for (size_t i = 0; i < level; i++) { ++ expected << "{ \"" << i << "\": "; ++ } ++ expected << "true"; ++ for (size_t i = 0; i < level; i++) { ++ expected << " }"; ++ } ++ EXPECT_EQ(expected.str(), out); ++ } catch (const isc::BadValue&) { ++ EXPECT_EQ(Element::MAX_NESTING_LEVEL + 1, level); ++ break; ++ } catch (const std::exception& e) { ++ FAIL() << "Got exception " << e.what(); ++ } ++ } ++} ++ ++/// @brief str/toJSON on cycle. ++TEST(Element, cycleStr) { ++ ElementPtr cycle = mkCycleList(); ++ EXPECT_THROW(cycle->str(), isc::BadValue); ++ cycle->remove(0); ++ cycle = mkCycleList(10); ++ EXPECT_THROW(cycle->str(), isc::BadValue); ++ cycle->remove(0); ++ cycle = mkCycleMap(); ++ EXPECT_THROW(cycle->str(), isc::BadValue); ++ cycle->set("rec", Element::createMap()); ++ cycle = mkCycleMap(10); ++ EXPECT_THROW(cycle->str(), isc::BadValue); ++ cycle->set("0", Element::createMap()); ++} ++ ++/// @brief prettyPrint on nested object. ++TEST(Element, nestedPrettyPrint) { ++ ElementPtr leaf = Element::create(string("foo")); ++ size_t level = Element::MAX_NESTING_LEVEL - 1; ++ EXPECT_NO_THROW(prettyPrint(mkNestedList(leaf, level))); ++ EXPECT_NO_THROW(prettyPrint(mkNestedMap(leaf, level))); ++ ++level; ++ EXPECT_THROW(prettyPrint(mkNestedList(leaf, level)), isc::BadValue); ++ EXPECT_THROW(prettyPrint(mkNestedMap(leaf, level)), isc::BadValue); ++} ++ ++/// @brief removeEmptyContainersRecursively on nested list. ++TEST(Element, nestedListRemoveEmptyContainersRecursively) { ++ for (size_t level = 1; level < 111; ++level) { ++ ElementPtr list = mkNestedList(ElementPtr(), level); ++ list->removeEmptyContainersRecursively(); ++ ASSERT_TRUE(list); ++ if (level <= Element::MAX_NESTING_LEVEL + 1) { ++ EXPECT_TRUE(list->empty()); ++ } else { ++ EXPECT_FALSE(list->empty()); ++ } ++ } ++} ++ ++/// @brief removeEmptyContainersRecursively on nested map. ++TEST(Element, nestedMapRemoveEmptyContainersRecursively) { ++ for (size_t level = 1; level < 111; ++level) { ++ ElementPtr map = mkNestedMap(ElementPtr(), level); ++ map->removeEmptyContainersRecursively(); ++ ASSERT_TRUE(map); ++ if (level <= Element::MAX_NESTING_LEVEL + 1) { ++ EXPECT_TRUE(map->empty()); ++ } else { ++ EXPECT_FALSE(map->empty()); ++ } ++ } ++} ++ ++/// @brief fromJSON on nested list. ++TEST(Element, nestedListFromJSON) { ++ ElementPtr leaf = Element::create(1); ++ size_t max_level = 50; ++ ASSERT_LE(max_level, Element::MAX_NESTING_LEVEL); ++ for (size_t level = 1; level < max_level + 10; ++level) { ++ std::ostringstream iss; ++ for (size_t i = 0; i < level; i++) { ++ iss << "["; ++ } ++ iss << 1; ++ for (size_t i = 0; i < level; i++) { ++ iss << "]"; ++ } ++ std::stringstream ss; ++ ss << iss.str(); ++ ElementPtr expected = mkNestedList(leaf, level); ++ try { ++ int l = 1; ++ int p = 1; ++ ElementPtr list = Element::fromJSON(ss, "sss", l, p, max_level); ++ EXPECT_TRUE(isEquivalent(list, expected)); ++ } catch (const JSONError&) { ++ EXPECT_EQ(max_level, level); ++ break; ++ } catch (const std::exception& e) { ++ FAIL() << "Got exception " << e.what(); ++ } ++ } ++} ++ ++/// @brief fromJSON on nested map. ++TEST(Element, nestedMapFromJSON) { ++ ElementPtr leaf = Element::create(true); ++ size_t max_level = 50; ++ ASSERT_LE(max_level, Element::MAX_NESTING_LEVEL); ++ for (size_t level = 1; level < max_level + 10; ++level) { ++ std::ostringstream iss; ++ for (size_t i = 0; i < level; i++) { ++ iss << "{ \"" << i << "\":"; ++ } ++ iss << "true"; ++ for (size_t i = 0; i < level; i++) { ++ iss << "}"; ++ } ++ std::stringstream ss; ++ ss << iss.str(); ++ ElementPtr expected = mkNestedMap(leaf, level); ++ try { ++ int l = 1; ++ int p = 1; ++ ElementPtr map = Element::fromJSON(ss, "sss", l, p, max_level); ++ EXPECT_TRUE(isEquivalent(map, expected)); ++ } catch (const JSONError&) { ++ EXPECT_EQ(max_level, level); ++ break; ++ } catch (const std::exception& e) { ++ FAIL() << "Got exception " << e.what(); ++ } ++ } ++} ++ ++/// @brief IsCircular on nested list. ++TEST(Element, nestedListHasCycle) { ++ ElementPtr leaf = Element::create(1); ++ for (size_t level = 0; level < 111; ++level) { ++ ElementPtr list = mkNestedList(leaf, level); ++ bool ret = false; ++ ASSERT_NO_THROW(ret = IsCircular(list)); ++ EXPECT_FALSE(ret); ++ } ++} ++ ++/// @brief IsCircular on nested map. ++TEST(Element, nestedMapHasCycle) { ++ ElementPtr leaf = Element::create(true); ++ for (size_t level = 0; level < 111; ++level) { ++ ElementPtr map = mkNestedMap(leaf, level); ++ bool ret = false; ++ ASSERT_NO_THROW(ret = IsCircular(map)); ++ EXPECT_FALSE(ret); ++ } ++} ++ ++/// @brief IsCircular on cycle. ++TEST(Element, cycleasCycle) { ++ ElementPtr cycle = mkCycleList(); ++ bool ret = false; ++ ASSERT_NO_THROW(ret = IsCircular(cycle)); ++ EXPECT_TRUE(ret); ++ cycle->remove(0); ++ cycle = mkCycleList(10); ++ ASSERT_NO_THROW(ret = IsCircular(cycle)); ++ EXPECT_TRUE(ret); ++ cycle->remove(0); ++ cycle = mkCycleMap(); ++ ASSERT_NO_THROW(ret = IsCircular(cycle)); ++ EXPECT_TRUE(ret); ++ cycle->set("rec", Element::createMap()); ++ cycle = mkCycleMap(10); ++ ASSERT_NO_THROW(ret = IsCircular(cycle)); ++ EXPECT_TRUE(ret); ++ cycle->set("0", Element::createMap()); ++} ++ ++/// @brief Create shared tree using lists. ++ElementPtr ++mkSharedLists(unsigned level) { ++ ElementPtr ret; ++ for (unsigned i = 0; i < level; ++i) { ++ ElementPtr list = Element::createList(); ++ if (i != 0) { ++ list->add(ret); ++ list->add(ret); ++ } ++ ret = list; ++ } ++ return (ret); ++} ++ ++/// @brief Create shared tree using maps. ++ElementPtr ++mkSharedMaps(unsigned level) { ++ ElementPtr ret; ++ for (unsigned i = 0; i < level; ++i) { ++ ElementPtr map = Element::createMap(); ++ if (i != 0) { ++ map->set("left", ret); ++ map->set("right", ret); ++ } ++ ret = map; ++ } ++ return (ret); ++} ++ ++/// @brief Count total number of elements ++size_t ++countTotal(ConstElementPtr x) { ++ if (!x) { ++ return (0); ++ } ++ size_t cnt = 1; ++ if (x->getType() == Element::list) { ++ for (auto const& i : x->listValue()) { ++ cnt += countTotal(i); ++ } ++ } else if (x->getType() == Element::map) { ++ for (auto const& i : x->mapValue()) { ++ cnt += countTotal(i.second); ++ } ++ } ++ return (cnt); ++} ++ ++/// @brief Count number of distinct elements. ++void ++countShared0(ConstElementPtr x, std::set& seen) { ++ if (!x) { ++ return; ++ } ++ if (seen.count(x) > 0) { ++ return; ++ } ++ seen.insert(x); ++ if (x->getType() == Element::list) { ++ for (auto const& i : x->listValue()) { ++ countShared0(i, seen); ++ } ++ } else if (x->getType() == Element::map) { ++ for (auto const& i : x->mapValue()) { ++ countShared0(i.second, seen); ++ } ++ } ++} ++ ++size_t ++countShared(ConstElementPtr x) { ++ std::set seen; ++ countShared0(x, seen); ++ return (seen.size()); ++} ++ ++/// @brief Test copy of shared tree using lists. ++TEST(Element, sharedTreeList) { ++ ConstElementPtr t10 = mkSharedLists(10); ++ for (unsigned i = 0; i < 20; ++i) { ++ ConstElementPtr t = copy(t10, i); ++ EXPECT_EQ(1023, countTotal(t)); ++ if (i == 0) { ++ EXPECT_EQ(10U, countShared(t)); ++ } else if (i >= 10) { ++ EXPECT_EQ(1023, countShared(t)); ++ } else { ++ EXPECT_EQ((1U << (i + 1)) + (9U - (i + 1)), countShared(t)); ++ } ++ } ++} ++ ++/// @brief Test copy of shared tree using maps. ++TEST(Element, sharedTreeMap) { ++ ConstElementPtr t10 = mkSharedMaps(10); ++ for (unsigned i = 0; i < 20; ++i) { ++ ConstElementPtr t = copy(t10, i); ++ EXPECT_EQ(1023, countTotal(t)); ++ if (i == 0) { ++ EXPECT_EQ(10U, countShared(t)); ++ } else if (i >= 10) { ++ EXPECT_EQ(1023, countShared(t)); ++ } else { ++ EXPECT_EQ((1U << (i + 1)) + (9U - (i + 1)), countShared(t)); ++ } ++ } ++} ++ ++/// @brief getNestDeph on lists. ++TEST(Element, getNestDepthList) { ++ ASSERT_EQ(0U, getNestDepth(ConstElementPtr())); ++ ElementPtr leaf = Element::create(1); ++ for (size_t level = 0; level < 111; ++level) { ++ unsigned depth = getNestDepth(mkNestedList(leaf, level)); ++ if (level >= Element::MAX_NESTING_LEVEL) { ++ EXPECT_EQ(depth, Element::MAX_NESTING_LEVEL); ++ } else { ++ EXPECT_EQ(depth, level + 1); ++ } ++ } ++ auto cycle = mkCycleList(); ++ EXPECT_EQ(Element::MAX_NESTING_LEVEL, getNestDepth(cycle)); ++ cycle->remove(0); ++ cycle = mkCycleList(10); ++ EXPECT_EQ(Element::MAX_NESTING_LEVEL, getNestDepth(cycle)); ++ cycle->remove(0); ++} ++ ++/// @brief getNestDeph on maps. ++TEST(Element, getNestDepthMap) { ++ ASSERT_EQ(0U, getNestDepth(ConstElementPtr())); ++ ElementPtr leaf = Element::create(true); ++ for (size_t level = 0; level < 111; ++level) { ++ unsigned depth = getNestDepth(mkNestedMap(leaf, level)); ++ if (level >= Element::MAX_NESTING_LEVEL) { ++ EXPECT_EQ(depth, Element::MAX_NESTING_LEVEL); ++ } else { ++ EXPECT_EQ(depth, level + 1); ++ } ++ } ++ auto cycle = mkCycleMap(); ++ EXPECT_EQ(Element::MAX_NESTING_LEVEL, getNestDepth(cycle)); ++ cycle->set("rec", Element::createMap()); ++ cycle = mkCycleMap(10); ++ EXPECT_EQ(Element::MAX_NESTING_LEVEL, getNestDepth(cycle)); ++ cycle->set("0", Element::createMap()); ++} ++ + } // namespace +--- isc-kea-2.6.3.orig/src/lib/process/redact_config.cc ++++ isc-kea-2.6.3/src/lib/process/redact_config.cc +@@ -1,4 +1,4 @@ +-// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC") ++// Copyright (C) 2021-2025 Internet Systems Consortium, Inc. ("ISC") + // + // This Source Code Form is subject to the terms of the Mozilla Public + // License, v. 2.0. If a copy of the MPL was not distributed with this +@@ -18,10 +18,14 @@ namespace { + + template + ElementPtrType +-redact(ElementPtrType const& element, list json_path) { ++ redact(ElementPtrType const& element, list json_path, ++ string obscure, unsigned level) { + if (!element) { + isc_throw(BadValue, "redact() got a null pointer"); + } ++ if (level == 0) { ++ isc_throw(BadValue, "redact() elements nested too deeply"); ++ } + + string const next_key(json_path.empty() ? string() : json_path.front()); + ElementPtr result; +@@ -36,9 +40,9 @@ redact(ElementPtrType const& element, li + // Then redact all children. + result = Element::createList(); + for (ElementPtr const& child : element->listValue()) { +- result->add(redact(child, json_path)); ++ result->add(redact(child, json_path, obscure, level - 1)); + } +- return result; ++ return (result); + } + } else if (element->getType() == Element::map) { + // If we are looking for anything or if we have reached the end of a +@@ -53,7 +57,7 @@ redact(ElementPtrType const& element, li + if (boost::algorithm::ends_with(key, "password") || + boost::algorithm::ends_with(key, "secret")) { + // Sensitive data +- result->set(key, Element::create(string("*****"))); ++ result->set(key, Element::create(obscure)); + } else if (key == "user-context") { + // Skip user contexts. + result->set(key, value); +@@ -64,23 +68,25 @@ redact(ElementPtrType const& element, li + result->set(key, value); + } else { + // We are looking for anything '*' so redact further. +- result->set(key, redact(value, json_path)); ++ result->set(key, redact(value, json_path, obscure, ++ level - 1)); + } + } + } +- return result; ++ return (result); + } else { + ConstElementPtr child(element->get(next_key)); + if (child) { +- result = isc::data::copy(element, 1); ++ result = isc::data::copy(element, 1U); + json_path.pop_front(); +- result->set(next_key, redact(child, json_path)); +- return result; ++ result->set(next_key, ++ redact(child, json_path, obscure, level - 1)); ++ return (result); + } + } + } + +- return element; ++ return (element); + } + + } // namespace +@@ -89,8 +95,9 @@ namespace isc { + namespace process { + + ConstElementPtr +-redactConfig(ConstElementPtr const& element, list const& json_path) { +- return redact(element, json_path); ++redactConfig(ConstElementPtr const& element, list const& json_path, ++ string obscure, unsigned max_nesting_depth) { ++ return (redact(element, json_path, obscure, max_nesting_depth)); + } + + } // namespace process +--- isc-kea-2.6.3.orig/src/lib/process/redact_config.h ++++ isc-kea-2.6.3/src/lib/process/redact_config.h +@@ -1,4 +1,4 @@ +-// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC") ++// Copyright (C) 2021-2025 Internet Systems Consortium, Inc. ("ISC") + // + // This Source Code Form is subject to the terms of the Mozilla Public + // License, v. 2.0. If a copy of the MPL was not distributed with this +@@ -17,19 +17,24 @@ namespace process { + /// + /// This method walks on the configuration tree: + /// - it copies only subtrees where a change was done. +-/// - it replaces passwords and secrets by asterisks. ++/// - it replaces passwords and secrets by obscure argument ++/// (default 5 asterisks). + /// - it skips user context. + /// - if a not empty list of keywords is given it follows only them. + /// + /// @param element initially the Element tree structure that describe the + /// configuration and smaller subtrees in recursive calls. + /// @param json_path JSON path to redact ++/// @param obscure new value of secrets / passwords ++/// @param max_nesting_depth maximum nesting depth + /// + /// @return a copy of the config where passwords and secrets were replaced by + /// asterisks so it can be safely logged to an unprivileged place. + isc::data::ConstElementPtr + redactConfig(isc::data::ConstElementPtr const& element, +- std::list const& json_path = {"*"}); ++ std::list const& json_path = {"*"}, ++ std::string obscure = "*****", ++ unsigned max_nesting_depth = isc::data::Element::MAX_NESTING_LEVEL); + + } // namespace process + } // namespace isc +--- isc-kea-2.6.3.orig/src/lib/process/tests/d_cfg_mgr_unittests.cc ++++ isc-kea-2.6.3/src/lib/process/tests/d_cfg_mgr_unittests.cc +@@ -313,6 +313,16 @@ TEST_F(DStubCfgMgrTest, redactConfig) { + expected = "{ \"foo\": { \"password\": \"*****\" }, "; + expected += "\"next\": { \"secret\": \"bar\" } }"; + EXPECT_EQ(expected, ret->str()); ++ ++ // Verify that it throws on cycles. ++ ElementPtr cycle = Element::createList(); ++ cycle->add(cycle); ++ EXPECT_THROW(redactConfig(cycle), BadValue); ++ cycle->remove(0); ++ cycle = Element::createMap(); ++ cycle->set("loop", cycle); ++ EXPECT_THROW(redactConfig(cycle), BadValue); ++ cycle->set("loop", Element::createMap()); + } + + // Test that user context is not touched when configuration is redacted. diff -Nru isc-kea-2.6.3/debian/patches/series isc-kea-2.6.3/debian/patches/series --- isc-kea-2.6.3/debian/patches/series 2025-06-02 16:58:00.000000000 +0000 +++ isc-kea-2.6.3/debian/patches/series 2026-06-12 15:58:08.000000000 +0000 @@ -1 +1,2 @@ 0009-disable-database-tests.patch +CVE-2026-3608.patch