kea/CVE-2026-3608.patch
2026-04-10 20:29:23 -04:00

1828 lines
71 KiB
Diff

commit 6ef02087bdf0f5158ad0654d9bdc30c166241e19
Author: Razvan Becheriu <razvan@isc.org>
Date: Thu Mar 12 12:38:45 2026 +0200
[#4387] backport #4275, #4288 to v3_0
[Martin Osvald <mosvald@redhat.com>]
NOTE: Tests were removed.
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index f48bed5e81..49a9359ae2 100644
--- a/src/lib/cc/data.cc
+++ b/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\r\t";
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 Element::Position& pos) {
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, const std::string& file, int& line,
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, const std::string& file, int& line,
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, const std::string& file, int& line,
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, const std::string& file, int& line,
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, const std::string& file_name, bool preproc)
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, const std::string& file, int& line,
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& file_name, bool preproc) {
// 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) const {
}
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) const {
}
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) const {
}
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) const {
} 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) const {
}
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, ConstElementPtr& t) const {
}
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) const {
}
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& other) const {
}
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& index /* = std::string() */) {
}
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) const {
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, ConstElementPtr other) {
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, ElementPtr& other,
// 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, ElementPtr& other,
(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, ElementPtr& other,
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, ElementPtr& other,
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, ElementPtr& other,
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, ElementPtr& other,
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, const std::string& extension,
}
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, const std::string& extension,
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, const std::string& extension,
}
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, ConstElementPtr b) {
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::ostream& out,
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::ostream& out,
// 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::ostream& out,
}
}
+} // 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& in, std::stringstream& out) {
}
}
+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
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index b93e2ec29e..e473f947b3 100644
--- a/src/lib/cc/data.h
+++ b/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);
@@ -171,37 +184,53 @@ protected:
public:
- // base class; make dtor virtual
+ // 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;
+ /// @throw BadValue when nesting depth is more than level.
+ virtual bool equals(const Element& other,
+ unsigned level = MAX_NESTING_LEVEL) const = 0;
- /// Converts the Element to JSON format and appends it to
- /// the given stringstream.
- virtual void toJSON(std::ostream& ss) 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,21 +271,38 @@ 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");
@@ -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
@@ -679,8 +849,10 @@ public:
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 {
@@ -694,16 +866,20 @@ public:
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;
+ 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 {
@@ -717,8 +893,10 @@ public:
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 {
@@ -748,10 +926,12 @@ public:
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 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> HierarchyDescriptor;
/// @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> HierarchyDescriptor;
/// 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, ElementPtr& other,
/// 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, ElementPtr& other,
/// @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, ConstElementPtr b);
/// 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& out, const Element::Position& pos);
/// 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
diff --git a/src/lib/process/redact_config.cc b/src/lib/process/redact_config.cc
index 2127c724d4..10ae6fec73 100644
--- a/src/lib/process/redact_config.cc
+++ b/src/lib/process/redact_config.cc
@@ -18,10 +18,14 @@ namespace {
template <typename ElementPtrType>
ElementPtrType
-redact(ElementPtrType const& element, list<string> json_path, string obscure) {
+ 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, list<string> json_path, string obscure) {
// Then redact all children.
result = Element::createList();
for (ElementPtr const& child : element->listValue()) {
- result->add(redact(child, json_path, obscure));
+ 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
@@ -64,23 +68,25 @@ redact(ElementPtrType const& element, list<string> json_path, string obscure) {
result->set(key, value);
} else {
// We are looking for anything '*' so redact further.
- result->set(key, redact(value, json_path, obscure));
+ 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, obscure));
- return result;
+ result->set(next_key,
+ redact(child, json_path, obscure, level - 1));
+ return (result);
}
}
}
- return element;
+ return (element);
}
} // namespace
@@ -90,8 +96,8 @@ namespace process {
ConstElementPtr
redactConfig(ConstElementPtr const& element, list<string> const& json_path,
- string obscure) {
- return redact(element, json_path, obscure);
+ string obscure, unsigned max_nesting_depth) {
+ return (redact(element, json_path, obscure, max_nesting_depth));
}
} // namespace process
diff --git a/src/lib/process/redact_config.h b/src/lib/process/redact_config.h
index 0b66e22970..6ea5d3a06d 100644
--- a/src/lib/process/redact_config.h
+++ b/src/lib/process/redact_config.h
@@ -26,13 +26,15 @@ namespace process {
/// 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::string obscure = "*****");
+ std::string obscure = "*****",
+ unsigned max_nesting_depth = isc::data::Element::MAX_NESTING_LEVEL);
} // namespace process
} // namespace isc
commit 316e8d04aedf9392bf9a2edcb35bf2a719c17b13
Author: Razvan Becheriu <razvan@isc.org>
Date: Thu Mar 12 14:56:47 2026 +0200
[#4388] backport #4365 to v3_0
[Martin Osvald <mosvald@redhat.com>]
NOTE: Tests were removed.
diff --git a/src/bin/agent/simple_parser.cc b/src/bin/agent/simple_parser.cc
index b4564fa5ce..182abcbf06 100644
--- a/src/bin/agent/simple_parser.cc
+++ b/src/bin/agent/simple_parser.cc
@@ -16,6 +16,7 @@
#include <http/basic_auth_config.h>
using namespace isc::data;
+using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::config;
@@ -151,6 +152,10 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
if (ctrl_sockets) {
auto const& sockets_map = ctrl_sockets->mapValue();
for (auto const& cs : sockets_map) {
+ if (!cs.second->get("socket-name")) {
+ isc_throw(DhcpConfigError, "missing parameter 'socket-name' from '" << cs.first
+ << "' service map (" << cs.second->getPosition() << ")");
+ }
// Add a validated socket name so we can suppress it in
// toElement() but don't have to revalidate it every time we
// want to use it.
commit f1647a5b6d531c1cac4063bef22c4d97568a788e
Author: Razvan Becheriu <razvan@isc.org>
Date: Fri Mar 13 11:54:10 2026 +0200
[#4402] bump up library versions for 3.0.3 release
[Martin Osvald <mosvald@redhat.com>]
NOTE: Version and KEA_HOOKS_VERSION did not match.
diff --git a/src/lib/cc/meson.build b/src/lib/cc/meson.build
index c22a1a1..b9924f7 100644
--- a/src/lib/cc/meson.build
+++ b/src/lib/cc/meson.build
@@ -21,7 +21,7 @@ kea_cc_lib = shared_library(
install_rpath: INSTALL_RPATH,
build_rpath: BUILD_RPATH,
link_with: LIBS_BUILT_SO_FAR,
- version: '82.0.0',
+ version: '83.0.0',
)
LIBS_BUILT_SO_FAR = [kea_cc_lib] + LIBS_BUILT_SO_FAR
subdir('tests')
diff --git a/src/lib/config/meson.build b/src/lib/config/meson.build
index 803cdfb..efd11a7 100644
--- a/src/lib/config/meson.build
+++ b/src/lib/config/meson.build
@@ -20,7 +20,7 @@ kea_config_lib = shared_library(
install_rpath: INSTALL_RPATH,
build_rpath: BUILD_RPATH,
link_with: LIBS_BUILT_SO_FAR,
- version: '83.0.0',
+ version: '84.0.0',
cpp_args: [f'-DCONTROL_SOCKET_DIR="@RUNSTATEDIR_INSTALLED@"'],
)
LIBS_BUILT_SO_FAR = [kea_config_lib] + LIBS_BUILT_SO_FAR
diff --git a/src/lib/hooks/hooks.h b/src/lib/hooks/hooks.h
index 27be442..222c2e5 100644
--- a/src/lib/hooks/hooks.h
+++ b/src/lib/hooks/hooks.h
@@ -12,8 +12,15 @@
namespace {
-// Version 30001 of the hooks framework, set for Kea 3.0.1
-const int KEA_HOOKS_VERSION = 30001;
+// [Martin Osvald <mosvald@redhat.com>]
+// NOTE: I am not sure how many custom hooks exist that compile different
+// code depending on the value of KEA_HOOKS_VERSION and expect
+// specific functionality to be present.
+// We have not backported the changes that led to the bump to 30002.
+// I am concerned that a SONAME version bump alone may not be
+// sufficient with changes fixing CVE-2026-3608.
+// Version 30003 of the hooks framework, set for Kea 3.0.3
+const int KEA_HOOKS_VERSION = 30003;
// Names of the framework functions.
const char* const LOAD_FUNCTION_NAME = "load";
diff --git a/src/lib/hooks/meson.build b/src/lib/hooks/meson.build
index 51256d7..bc3b977 100644
--- a/src/lib/hooks/meson.build
+++ b/src/lib/hooks/meson.build
@@ -23,7 +23,7 @@ kea_hooks_lib = shared_library(
install_rpath: INSTALL_RPATH,
build_rpath: BUILD_RPATH,
link_with: LIBS_BUILT_SO_FAR,
- version: '119.0.0',
+ version: '121.0.0',
)
LIBS_BUILT_SO_FAR = [kea_hooks_lib] + LIBS_BUILT_SO_FAR
subdir('tests')
diff --git a/src/lib/process/meson.build b/src/lib/process/meson.build
index ce49503..7ffaa74 100644
--- a/src/lib/process/meson.build
+++ b/src/lib/process/meson.build
@@ -22,7 +22,7 @@ kea_process_lib = shared_library(
install_rpath: INSTALL_RPATH,
build_rpath: BUILD_RPATH,
link_with: LIBS_BUILT_SO_FAR,
- version: '90.0.0',
+ version: '91.0.0',
)
LIBS_BUILT_SO_FAR = [kea_process_lib] + LIBS_BUILT_SO_FAR
subdir('testutils')
diff --git a/src/lib/util/meson.build b/src/lib/util/meson.build
index d3e1efa..3a6d85d 100644
--- a/src/lib/util/meson.build
+++ b/src/lib/util/meson.build
@@ -25,7 +25,7 @@ kea_util_lib = shared_library(
install_rpath: INSTALL_RPATH,
build_rpath: BUILD_RPATH,
link_with: LIBS_BUILT_SO_FAR,
- version: '101.0.0',
+ version: '102.0.0',
)
subdir('io')
subdir('unittests')