#1139931 trixie-pu: package isc-kea/2.6.3-1+deb13u1

#1139931#5
Date:
2026-06-13 15:41:52 UTC
From:
To:
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