This update fixes a low impact DoS issue in Kea. All tests in debusine
were fine, debdiff below.
Cheers,
Moritz
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 19:00:06.000000000 +0200
+++ isc-kea-2.6.3/debian/changelog 2026-06-12 17:58:34.000000000 +0200
@@ -1,3 +1,9 @@
+isc-kea (2.6.3-1+deb13u1) trixie; urgency=medium
+
+ * CVE-2026-3608
+
+ -- Moritz Mühlenhoff <jmm@debian.org> 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 01:00:00.000000000 +0100
+++ isc-kea-2.6.3/debian/patches/CVE-2026-3608.patch 2026-06-12 17:58:27.000000000 +0200
@@ -0,0 +1,2645 @@
+From 5dc0f6612aa5f6003eb7b8e5299f7774f46e5849 Mon Sep 17 00:00:00 2001
+From: Razvan Becheriu <razvan@isc.org>
+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 <cstdio>
+ #include <iostream>
+ #include <iomanip>
++#include <set>
+ #include <string>
+ #include <sstream>
+ #include <fstream>
+@@ -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<Element>(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<ElementPtr>& 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>(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>(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<ConstElementPtr> 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 <exceptions/exceptions.h>
+
+-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<ElementPtr>& 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<std::string, ConstElementPtr>& 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<ElementPtr>& t) const;
++
++ /// @brief Get the map value.
++ ///
++ /// @param t The reference to the map.
++ /// @return false.
+ virtual bool getValue(std::map<std::string, ConstElementPtr>& 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<long long int>(i))); };
+- bool setValue(const int i) { return (setValue(static_cast<long long int>(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<ElementPtr>& v);
++
++ /// @brief Set the map value.
++ ///
++ /// @param v The new map value.
++ /// @return False.
+ virtual bool setValue(const std::map<std::string, ConstElementPtr>& 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<long long int>(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<long long int>(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<Element>(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<FunctionMap> 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<FunctionMap> 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<unsigned>(-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<ConstElementPtr>& 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<ConstElementPtr> 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 <typename ElementPtrType>
+ ElementPtrType
+-redact(ElementPtrType const& element, list<string> json_path) {
++ redact(ElementPtrType const& element, list<string> 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<string> const& json_path) {
+- return redact(element, json_path);
++redactConfig(ConstElementPtr const& element, list<string> 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<std::string> const& json_path = {"*"});
++ std::list<std::string> 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 18:58:00.000000000 +0200
+++ isc-kea-2.6.3/debian/patches/series 2026-06-12 17:58:08.000000000 +0200
@@ -1 +1,2 @@
0009-disable-database-tests.patch
+CVE-2026-3608.patch