Kea: data.cc Source File

| Kea 3.0.3-git

data.cc
Go to the documentation of this file.
1// Copyright (C) 2010-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <cc/data.h>
10#include <util/bigints.h>
11
12#include <cstring>
13#include <cassert>
14#include <climits>
15#include <list>
16#include <map>
17#include <cstdio>
18#include <iostream>
19#include <iomanip>
20#include <set>
21#include <string>
22#include <sstream>
23#include <fstream>
24#include <cerrno>
25
26#include <boost/lexical_cast.hpp>
27
28#include <cmath>
29
30using namespace std;
31
32using isc::util::int128_t;
33
34namespace {
35const char* const WHITESPACE = " \b\f\n\r\t";
36} // end anonymous namespace
37
38namespace isc {
39namespace data {
40
41constexpr unsigned Element::MAX_NESTING_LEVEL;
42
43std::string
44Element::Position::str() const {
45 std::ostringstream ss;
46 ss << file_ << ":" << line_ << ":" << pos_;
47 return (ss.str());
48}
49
50std::ostream&
51 operator<<(std::ostream& out, const Element::Position& pos) {
52 out << pos.str();
53 return (out);
54}
55
56void
57Element::removeEmptyContainersRecursively(unsigned level) {
58 if (level <= 0) {
59 // Cycles are by definition not empty so no need to throw.
60 return;
61 }
62 if (type_ == list || type_ == map) {
63 size_t s(size());
64 for (size_t i = 0; i < s; ++i) {
65 // Get child.
66 ElementPtr child;
67 if (type_ == list) {
68 child = getNonConst(i);
69 } else if (type_ == map) {
70 std::string const key(get(i)->stringValue());
71 // The ElementPtr - ConstElementPtr disparity between
72 // ListElement and MapElement is forcing a const cast here.
73 // It's undefined behavior to modify it after const casting.
74 // The options are limited. I've tried templating, moving
75 // this function from a member function to free-standing and
76 // taking the Element template as argument. I've tried
77 // making it a virtual function with overridden
78 // implementations in ListElement and MapElement. Nothing
79 // works.
80 child = boost::const_pointer_cast<Element>(get(key));
81 }
82
83 // Makes no sense to continue for non-container children.
84 if (child->getType() != list && child->getType() != map) {
85 continue;
86 }
87
88 // Recurse if not empty.
89 if (!child->empty()){
90 child->removeEmptyContainersRecursively(level - 1);
91 }
92
93 // When returning from recursion, remove if empty.
94 if (child->empty()) {
95 remove(i);
96 --i;
97 --s;
98 }
99 }
100 }
101}
102
103std::string
104Element::str() const {
105 std::stringstream ss;
106 toJSON(ss);
107 return (ss.str());
108}
109
110std::string
111Element::toWire() const {
112 std::stringstream ss;
113 toJSON(ss);
114 return (ss.str());
115}
116
117void
118 Element::toWire(std::ostream& ss) const {
119 toJSON(ss);
120}
121
122bool
123Element::getValue(int64_t&) const {
124 return (false);
125}
126
127bool
128 Element::getValue(double&) const {
129 return (false);
130}
131
132bool
133 Element::getValue(bool&) const {
134 return (false);
135}
136
137bool
138 Element::getValue(std::string&) const {
139 return (false);
140}
141
142bool
143 Element::getValue(std::vector<ElementPtr>&) const {
144 return (false);
145}
146
147bool
148 Element::getValue(std::map<std::string, ConstElementPtr>&) const {
149 return (false);
150}
151
152bool
153Element::setValue(const long long int) {
154 return (false);
155}
156
157bool
158 Element::setValue(isc::util::int128_t const&) {
159 return (false);
160}
161
162bool
163 Element::setValue(const double) {
164 return (false);
165}
166
167bool
168 Element::setValue(const bool) {
169 return (false);
170}
171
172bool
173 Element::setValue(const std::string&) {
174 return (false);
175}
176
177bool
178 Element::setValue(const std::vector<ElementPtr>&) {
179 return (false);
180}
181
182bool
183 Element::setValue(const std::map<std::string, ConstElementPtr>&) {
184 return (false);
185}
186
187 ConstElementPtr
188Element::get(const int) const {
189 throwTypeError("get(int) called on a non-container Element");
190}
191
192 ElementPtr
193Element::getNonConst(const int) const {
194 throwTypeError("get(int) called on a non-container Element");
195}
196
197void
198Element::set(const size_t, ElementPtr) {
199 throwTypeError("set(int, element) called on a non-list Element");
200}
201
202void
203Element::add(ElementPtr) {
204 throwTypeError("add() called on a non-list Element");
205}
206
207void
208Element::remove(const int) {
209 throwTypeError("remove(int) called on a non-container Element");
210}
211
212size_t
213Element::size() const {
214 throwTypeError("size() called on a non-list Element");
215}
216
217bool
218Element::empty() const {
219 throwTypeError("empty() called on a non-container Element");
220}
221
222 ConstElementPtr
223 Element::get(const std::string&) const {
224 throwTypeError("get(string) called on a non-map Element");
225}
226
227void
228 Element::set(const std::string&, ConstElementPtr) {
229 throwTypeError("set(name, element) called on a non-map Element");
230}
231
232void
233 Element::remove(const std::string&) {
234 throwTypeError("remove(string) called on a non-map Element");
235}
236
237bool
238Element::contains(const std::string&) const {
239 throwTypeError("contains(string) called on a non-map Element");
240}
241
242 ConstElementPtr
243Element::find(const std::string&) const {
244 throwTypeError("find(string) called on a non-map Element");
245}
246
247bool
248 Element::find(const std::string&, ConstElementPtr&) const {
249 return (false);
250}
251
252namespace {
253inline void
254throwJSONError(const std::string& error, const std::string& file, int line,
255 int pos) {
256 std::stringstream ss;
257 ss << error << " in " + file + ":" << line << ":" << pos;
258 isc_throw(JSONError, ss.str());
259}
260} // end anonymous namespace
261
262std::ostream&
263 operator<<(std::ostream& out, const Element& e) {
264 return (out << e.str());
265}
266
267bool
268operator==(const Element& a, const Element& b) {
269 return (a.equals(b));
270}
271
272 bool operator!=(const Element& a, const Element& b) {
273 return (!a.equals(b));
274}
275
276 bool
277 operator<(Element const& a, Element const& b) {
278 if (a.getType() != b.getType()) {
279 isc_throw(BadValue, "cannot compare Elements of different types");
280 }
281 switch (a.getType()) {
282 case Element::integer:
283 return a.intValue() < b.intValue();
284 case Element::real:
285 return a.doubleValue() < b.doubleValue();
286 case Element::boolean:
287 return b.boolValue() || !a.boolValue();
288 case Element::string:
289 return std::strcmp(a.stringValue().c_str(), b.stringValue().c_str()) < 0;
290 default:
291 isc_throw(BadValue, "cannot compare Elements of type " << to_string(a.getType()));
292 }
293}
294
295//
296// factory functions
297//
298 ElementPtr
299Element::create(const Position& pos) {
300 return (ElementPtr(new NullElement(pos)));
301}
302
303 ElementPtr
304 Element::create(const long long int i, const Position& pos) {
305 return (ElementPtr(new IntElement(static_cast<int64_t>(i), pos)));
306}
307
308 ElementPtr
309 Element::create(const isc::util::int128_t& i, const Position& pos) {
310 return (ElementPtr(new BigIntElement(i, pos)));
311}
312
313 ElementPtr
314 Element::create(const int i, const Position& pos) {
315 return (create(static_cast<long long int>(i), pos));
316}
317
318 ElementPtr
319 Element::create(const long int i, const Position& pos) {
320 return (create(static_cast<long long int>(i), pos));
321}
322
323 ElementPtr
324 Element::create(const uint32_t i, const Position& pos) {
325 return (create(static_cast<long long int>(i), pos));
326}
327
328 ElementPtr
329 Element::create(const double d, const Position& pos) {
330 return (ElementPtr(new DoubleElement(d, pos)));
331}
332
333 ElementPtr
334 Element::create(const bool b, const Position& pos) {
335 return (ElementPtr(new BoolElement(b, pos)));
336}
337
338 ElementPtr
339 Element::create(const std::string& s, const Position& pos) {
340 return (ElementPtr(new StringElement(s, pos)));
341}
342
343 ElementPtr
344 Element::create(const char *s, const Position& pos) {
345 return (create(std::string(s), pos));
346}
347
348 ElementPtr
349Element::createList(const Position& pos) {
350 return (ElementPtr(new ListElement(pos)));
351}
352
353 ElementPtr
354Element::createMap(const Position& pos) {
355 return (ElementPtr(new MapElement(pos)));
356}
357
358
359//
360// helper functions for fromJSON factory
361//
362namespace {
363bool
364charIn(const int c, const char* chars) {
365 const size_t chars_len = std::strlen(chars);
366 for (size_t i = 0; i < chars_len; ++i) {
367 if (chars[i] == c) {
368 return (true);
369 }
370 }
371 return (false);
372}
373
374void
375skipChars(std::istream& in, const char* chars, int& line, int& pos) {
376 int c = in.peek();
377 while (charIn(c, chars) && c != EOF) {
378 if (c == '\n') {
379 ++line;
380 pos = 1;
381 } else {
382 ++pos;
383 }
384 in.ignore();
385 c = in.peek();
386 }
387}
388
389// skip on the input stream to one of the characters in chars
390// if another character is found this function throws JSONError
391// unless that character is specified in the optional may_skip
392//
393// It returns the found character (as an int value).
394int
395skipTo(std::istream& in, const std::string& file, int& line, int& pos,
396 const char* chars, const char* may_skip="") {
397 int c = in.get();
398 ++pos;
399 while (c != EOF) {
400 if (c == '\n') {
401 pos = 1;
402 ++line;
403 }
404 if (charIn(c, may_skip)) {
405 c = in.get();
406 ++pos;
407 } else if (charIn(c, chars)) {
408 while (charIn(in.peek(), may_skip)) {
409 if (in.peek() == '\n') {
410 pos = 1;
411 ++line;
412 } else {
413 ++pos;
414 }
415 in.ignore();
416 }
417 return (c);
418 } else {
419 throwJSONError(std::string("'") + std::string(1, c) + "' read, one of \"" + chars + "\" expected", file, line, pos);
420 }
421 }
422 throwJSONError(std::string("EOF read, one of \"") + chars + "\" expected", file, line, pos);
423 return (c); // shouldn't reach here, but some compilers require it
424}
425
426// TODO: Should we check for all other official escapes here (and
427// error on the rest)?
428std::string
429strFromStringstream(std::istream& in, const std::string& file,
430 const int line, int& pos) {
431 std::stringstream ss;
432 int c = in.get();
433 ++pos;
434 if (c == '"') {
435 c = in.get();
436 ++pos;
437 } else {
438 throwJSONError("String expected", file, line, pos);
439 }
440
441 while (c != EOF && c != '"') {
442 if (c == '\\') {
443 // see the spec for allowed escape characters
444 int d;
445 switch (in.peek()) {
446 case '"':
447 c = '"';
448 break;
449 case '/':
450 c = '/';
451 break;
452 case '\\':
453 c = '\\';
454 break;
455 case 'b':
456 c = '\b';
457 break;
458 case 'f':
459 c = '\f';
460 break;
461 case 'n':
462 c = '\n';
463 break;
464 case 'r':
465 c = '\r';
466 break;
467 case 't':
468 c = '\t';
469 break;
470 case 'u':
471 // skip first 0
472 in.ignore();
473 ++pos;
474 c = in.peek();
475 if (c != '0') {
476 throwJSONError("Unsupported unicode escape", file, line, pos);
477 }
478 // skip second 0
479 in.ignore();
480 ++pos;
481 c = in.peek();
482 if (c != '0') {
483 throwJSONError("Unsupported unicode escape", file, line, pos - 2);
484 }
485 // get first digit
486 in.ignore();
487 ++pos;
488 d = in.peek();
489 if ((d >= '0') && (d <= '9')) {
490 c = (d - '0') << 4;
491 } else if ((d >= 'A') && (d <= 'F')) {
492 c = (d - 'A' + 10) << 4;
493 } else if ((d >= 'a') && (d <= 'f')) {
494 c = (d - 'a' + 10) << 4;
495 } else {
496 throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 3);
497 }
498 // get second digit
499 in.ignore();
500 ++pos;
501 d = in.peek();
502 if ((d >= '0') && (d <= '9')) {
503 c |= d - '0';
504 } else if ((d >= 'A') && (d <= 'F')) {
505 c |= d - 'A' + 10;
506 } else if ((d >= 'a') && (d <= 'f')) {
507 c |= d - 'a' + 10;
508 } else {
509 throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 4);
510 }
511 break;
512 default:
513 throwJSONError("Bad escape", file, line, pos);
514 }
515 // drop the escaped char
516 in.ignore();
517 ++pos;
518 }
519 ss.put(c);
520 c = in.get();
521 ++pos;
522 }
523 if (c == EOF) {
524 throwJSONError("Unterminated string", file, line, pos);
525 }
526 return (ss.str());
527}
528
529std::string
530wordFromStringstream(std::istream& in, int& pos) {
531 std::stringstream ss;
532 while (isalpha(in.peek())) {
533 ss << (char) in.get();
534 }
535 pos += ss.str().size();
536 return (ss.str());
537}
538
539std::string
540numberFromStringstream(std::istream& in, int& pos) {
541 std::stringstream ss;
542 while (isdigit(in.peek()) || in.peek() == '+' || in.peek() == '-' ||
543 in.peek() == '.' || in.peek() == 'e' || in.peek() == 'E') {
544 ss << (char) in.get();
545 }
546 pos += ss.str().size();
547 return (ss.str());
548}
549
550// Should we change from IntElement and DoubleElement to NumberElement
551// that can also hold an e value? (and have specific getters if the
552// value is larger than an int can handle)
553//
554// At the moment of writing, the only way that the code flow can reach the
555// int128_t cast, and not throw, is by retrieving one of the few bigint
556// statistics through kea-ctrl-agent. If kea-ctrl-agent ever gets removed, and
557// its HTTP listener embedded in Kea, then the cast to int128_t can be removed as
558// well, as there is no deserialization of bigints required, although the only
559// benefit would be better performance for error cases, so it's arguable.
560 ElementPtr
561fromStringstreamNumber(std::istream& in, const std::string& file,
562 const int line, int& pos) {
563 // Remember position where the value starts. It will be set in the
564 // Position structure of the Element to be created.
565 const uint32_t start_pos = pos;
566 // This will move the pos to the end of the value.
567 const std::string number = numberFromStringstream(in, pos);
568
569 // Is it a double?
570 if (number.find_first_of(".eE") < number.size()) {
571 try {
572 return (Element::create(boost::lexical_cast<double>(number),
573 Element::Position(file, line, start_pos)));
574 } catch (const boost::bad_lexical_cast& exception) {
575 throwJSONError("Number overflow while trying to cast '" + number +
576 "' to double: " + exception.what(),
577 file, line, start_pos);
578 }
579 }
580
581 // Is it an integer?
582 try {
583 return (Element::create(boost::lexical_cast<int64_t>(number),
584 Element::Position(file, line, start_pos)));
585 } catch (const boost::bad_lexical_cast& exception64) {
586 // Is it a big integer?
587 try {
588 return (Element::create(int128_t(number),
589 Element::Position(file, line, start_pos)));
590 } catch (overflow_error const& exception128) {
591 throwJSONError("Number overflow while trying to cast '" + number +
592 "' to int64 and subsequently to int128: " +
593 exception64.what() + ", " + exception128.what(),
594 file, line, start_pos);
595 }
596 }
597 return (ElementPtr());
598}
599
600 ElementPtr
601fromStringstreamBool(std::istream& in, const std::string& file,
602 const int line, int& pos) {
603 // Remember position where the value starts. It will be set in the
604 // Position structure of the Element to be created.
605 const uint32_t start_pos = pos;
606 // This will move the pos to the end of the value.
607 const std::string word = wordFromStringstream(in, pos);
608
609 if (word == "true") {
610 return (Element::create(true, Element::Position(file, line,
611 start_pos)));
612 } else if (word == "false") {
613 return (Element::create(false, Element::Position(file, line,
614 start_pos)));
615 } else {
616 throwJSONError(std::string("Bad boolean value: ") + word, file,
617 line, start_pos);
618 }
619 return (ElementPtr());
620}
621
622 ElementPtr
623fromStringstreamNull(std::istream& in, const std::string& file,
624 const int line, int& pos) {
625 // Remember position where the value starts. It will be set in the
626 // Position structure of the Element to be created.
627 const uint32_t start_pos = pos;
628 // This will move the pos to the end of the value.
629 const std::string word = wordFromStringstream(in, pos);
630 if (word == "null") {
631 return (Element::create(Element::Position(file, line, start_pos)));
632 } else {
633 throwJSONError(std::string("Bad null value: ") + word, file,
634 line, start_pos);
635 return (ElementPtr());
636 }
637}
638
639 ElementPtr
640fromStringstreamString(std::istream& in, const std::string& file, int& line,
641 int& pos) {
642 // Remember position where the value starts. It will be set in the
643 // Position structure of the Element to be created.
644 const uint32_t start_pos = pos;
645 // This will move the pos to the end of the value.
646 const std::string string_value = strFromStringstream(in, file, line, pos);
647 return (Element::create(string_value, Element::Position(file, line,
648 start_pos)));
649}
650
651 ElementPtr
652fromStringstreamList(std::istream& in, const std::string& file, int& line,
653 int& pos, unsigned level) {
654 if (level == 0) {
655 isc_throw(JSONError, "fromJSON elements nested too deeply");
656 }
657 int c = 0;
658 ElementPtr list = Element::createList(Element::Position(file, line, pos));
659 ElementPtr cur_list_element;
660
661 skipChars(in, WHITESPACE, line, pos);
662 while (c != EOF && c != ']') {
663 if (in.peek() != ']') {
664 cur_list_element =
665 Element::fromJSON(in, file, line, pos, level - 1);
666 list->add(cur_list_element);
667 c = skipTo(in, file, line, pos, ",]", WHITESPACE);
668 } else {
669 c = in.get();
670 ++pos;
671 }
672 }
673 return (list);
674}
675
676 ElementPtr
677fromStringstreamMap(std::istream& in, const std::string& file, int& line,
678 int& pos, unsigned level) {
679 if (level == 0) {
680 isc_throw(JSONError, "fromJSON elements nested too deeply");
681 }
682 ElementPtr map = Element::createMap(Element::Position(file, line, pos));
683 skipChars(in, WHITESPACE, line, pos);
684 int c = in.peek();
685 if (c == EOF) {
686 throwJSONError(std::string("Unterminated map, <string> or } expected"), file, line, pos);
687 } else if (c == '}') {
688 // empty map, skip closing curly
689 in.ignore();
690 } else {
691 while (c != EOF && c != '}') {
692 std::string key = strFromStringstream(in, file, line, pos);
693
694 skipTo(in, file, line, pos, ":", WHITESPACE);
695 // skip the :
696
697 ConstElementPtr value =
698 Element::fromJSON(in, file, line, pos, level - 1);
699 map->set(key, value);
700
701 c = skipTo(in, file, line, pos, ",}", WHITESPACE);
702 }
703 }
704 return (map);
705}
706} // end anonymous namespace
707
708std::string
709Element::typeToName(Element::types type) {
710 switch (type) {
711 case Element::integer:
712 return (std::string("integer"));
713 case Element::bigint:
714 return (std::string("bigint"));
715 case Element::real:
716 return (std::string("real"));
717 case Element::boolean:
718 return (std::string("boolean"));
719 case Element::string:
720 return (std::string("string"));
721 case Element::list:
722 return (std::string("list"));
723 case Element::map:
724 return (std::string("map"));
725 case Element::null:
726 return (std::string("null"));
727 case Element::any:
728 return (std::string("any"));
729 default:
730 return (std::string("unknown"));
731 }
732}
733
734 Element::types
735Element::nameToType(const std::string& type_name) {
736 if (type_name == "integer") {
737 return (Element::integer);
738 } else if (type_name == "bigint") {
739 return (Element::bigint);
740 } else if (type_name == "real") {
741 return (Element::real);
742 } else if (type_name == "boolean") {
743 return (Element::boolean);
744 } else if (type_name == "string") {
745 return (Element::string);
746 } else if (type_name == "list") {
747 return (Element::list);
748 } else if (type_name == "map") {
749 return (Element::map);
750 } else if (type_name == "named_set") {
751 return (Element::map);
752 } else if (type_name == "null") {
753 return (Element::null);
754 } else if (type_name == "any") {
755 return (Element::any);
756 } else {
757 isc_throw(TypeError, type_name + " is not a valid type name");
758 }
759}
760
761 ElementPtr
762 Element::fromJSON(std::istream& in, bool preproc) {
763
764 int line = 1, pos = 1;
765 stringstream filtered;
766 if (preproc) {
767 preprocess(in, filtered);
768 }
769
770 ElementPtr value = fromJSON(preproc ? filtered : in, "<istream>", line, pos);
771
772 return (value);
773}
774
775 ElementPtr
776 Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc) {
777 int line = 1, pos = 1;
778 stringstream filtered;
779 if (preproc) {
780 preprocess(in, filtered);
781 }
782 return (fromJSON(preproc ? filtered : in, file_name, line, pos));
783}
784
785 ElementPtr
786 Element::fromJSON(std::istream& in, const std::string& file, int& line,
787 int& pos, unsigned level) {
788 if (level == 0) {
789 isc_throw(JSONError, "fromJSON elements nested too deeply");
790 }
791 int c = 0;
792 ElementPtr element;
793 bool el_read = false;
794 skipChars(in, WHITESPACE, line, pos);
795 while (c != EOF && !el_read) {
796 c = in.get();
797 pos++;
798 switch(c) {
799 case '1':
800 case '2':
801 case '3':
802 case '4':
803 case '5':
804 case '6':
805 case '7':
806 case '8':
807 case '9':
808 case '0':
809 case '-':
810 case '+':
811 case '.':
812 in.putback(c);
813 --pos;
814 element = fromStringstreamNumber(in, file, line, pos);
815 el_read = true;
816 break;
817 case 't':
818 case 'f':
819 in.putback(c);
820 --pos;
821 element = fromStringstreamBool(in, file, line, pos);
822 el_read = true;
823 break;
824 case 'n':
825 in.putback(c);
826 --pos;
827 element = fromStringstreamNull(in, file, line, pos);
828 el_read = true;
829 break;
830 case '"':
831 in.putback('"');
832 --pos;
833 element = fromStringstreamString(in, file, line, pos);
834 el_read = true;
835 break;
836 case '[':
837 element = fromStringstreamList(in, file, line, pos, level);
838 el_read = true;
839 break;
840 case '{':
841 element = fromStringstreamMap(in, file, line, pos, level);
842 el_read = true;
843 break;
844 case EOF:
845 break;
846 default:
847 throwJSONError(std::string("error: unexpected character ") + std::string(1, c), file, line, pos);
848 break;
849 }
850 }
851 if (el_read) {
852 return (element);
853 } else {
854 isc_throw(JSONError, "nothing read");
855 }
856}
857
858 ElementPtr
859Element::fromJSON(const std::string& in, bool preproc) {
860 std::stringstream ss;
861 ss << in;
862
863 int line = 1, pos = 1;
864 stringstream filtered;
865 if (preproc) {
866 preprocess(ss, filtered);
867 }
868 ElementPtr result(fromJSON(preproc ? filtered : ss, "<string>", line, pos));
869 skipChars(ss, WHITESPACE, line, pos);
870 // ss must now be at end
871 if (ss.peek() != EOF) {
872 throwJSONError("Extra data", "<string>", line, pos);
873 }
874 return result;
875}
876
877 ElementPtr
878Element::fromJSONFile(const std::string& file_name, bool preproc) {
879 // zero out the errno to be safe
880 errno = 0;
881
882 std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary);
883 if (!infile.is_open()) {
884 const char* error = strerror(errno);
885 isc_throw(InvalidOperation, "failed to read file '" << file_name
886 << "': " << error);
887 }
888
889 return (fromJSON(infile, file_name, preproc));
890}
891
892// to JSON format
893
894void
895IntElement::toJSON(std::ostream& ss, unsigned) const {
896 ss << intValue();
897}
898
899void
900BigIntElement::toJSON(std::ostream& ss, unsigned) const {
901 ss << bigIntValue();
902}
903
904void
905DoubleElement::toJSON(std::ostream& ss, unsigned) const {
906 // The default output for doubles nicely drops off trailing
907 // zeros, however this produces strings without decimal points
908 // for whole number values. When reparsed this will create
909 // IntElements not DoubleElements. Rather than used a fixed
910 // precision, we'll just tack on an ".0" when the decimal point
911 // is missing.
912 ostringstream val_ss;
913 val_ss << doubleValue();
914 ss << val_ss.str();
915 if (val_ss.str().find_first_of('.') == string::npos) {
916 ss << ".0";
917 }
918}
919
920void
921BoolElement::toJSON(std::ostream& ss, unsigned) const {
922 if (boolValue()) {
923 ss << "true";
924 } else {
925 ss << "false";
926 }
927}
928
929void
930NullElement::toJSON(std::ostream& ss, unsigned) const {
931 ss << "null";
932}
933
934void
935StringElement::toJSON(std::ostream& ss, unsigned) const {
936 ss << "\"";
937 const std::string& str = stringValue();
938 for (size_t i = 0; i < str.size(); ++i) {
939 const signed char c = str[i];
940 // Escape characters as defined in JSON spec
941 // Note that we do not escape forward slash; this
942 // is allowed, but not mandatory.
943 switch (c) {
944 case '"':
945 ss << '\\' << c;
946 break;
947 case '\\':
948 ss << '\\' << c;
949 break;
950 case '\b':
951 ss << '\\' << 'b';
952 break;
953 case '\f':
954 ss << '\\' << 'f';
955 break;
956 case '\n':
957 ss << '\\' << 'n';
958 break;
959 case '\r':
960 ss << '\\' << 'r';
961 break;
962 case '\t':
963 ss << '\\' << 't';
964 break;
965 default:
966 if (c < 0x20 || c == 0x7f) {
967 std::ostringstream esc;
968 esc << "\\u"
969 << hex
970 << setw(4)
971 << setfill('0')
972 << (static_cast<unsigned>(c) & 0xff);
973 ss << esc.str();
974 } else {
975 ss << c;
976 }
977 }
978 }
979 ss << "\"";
980}
981
982void
983ListElement::toJSON(std::ostream& ss, unsigned level) const {
984 if (level == 0) {
985 isc_throw(BadValue, "toJSON got infinite recursion: "
986 "arguments include cycles");
987 }
988 ss << "[ ";
989
990 const std::vector<ElementPtr>& v = listValue();
991 bool first = true;
992 for (auto const& it : v) {
993 if (!first) {
994 ss << ", ";
995 } else {
996 first = false;
997 }
998 it->toJSON(ss, level - 1);
999 }
1000 ss << " ]";
1001}
1002
1003void
1004MapElement::toJSON(std::ostream& ss, unsigned level) const {
1005 if (level == 0) {
1006 isc_throw(BadValue, "toJSON got infinite recursion: "
1007 "arguments include cycles");
1008 }
1009 ss << "{ ";
1010
1011 bool first = true;
1012 for (auto const& it : m) {
1013 if (!first) {
1014 ss << ", ";
1015 } else {
1016 first = false;
1017 }
1018 ss << "\"" << it.first << "\": ";
1019 if (it.second) {
1020 it.second->toJSON(ss, level - 1);
1021 } else {
1022 ss << "None";
1023 }
1024 }
1025 ss << " }";
1026}
1027
1028// throws when one of the types in the path (except the one
1029// we're looking for) is not a MapElement
1030// returns 0 if it could simply not be found
1031// should that also be an exception?
1032 ConstElementPtr
1033MapElement::find(const std::string& id) const {
1034 const size_t sep = id.find('/');
1035 if (sep == std::string::npos) {
1036 return (get(id));
1037 } else {
1038 ConstElementPtr ce = get(id.substr(0, sep));
1039 if (ce) {
1040 // ignore trailing slash
1041 if (sep + 1 != id.size()) {
1042 return (ce->find(id.substr(sep + 1)));
1043 } else {
1044 return (ce);
1045 }
1046 } else {
1047 return (ElementPtr());
1048 }
1049 }
1050}
1051
1052 ElementPtr
1053 Element::fromWire(const std::string& s) {
1054 std::stringstream ss;
1055 ss << s;
1056 int line = 0, pos = 0;
1057 return (fromJSON(ss, "<wire>", line, pos));
1058}
1059
1060 ElementPtr
1061Element::fromWire(std::stringstream& in, int) {
1062 //
1063 // Check protocol version
1064 //
1065 //for (int i = 0 ; i < 4 ; ++i) {
1066 // const unsigned char version_byte = get_byte(in);
1067 // if (PROTOCOL_VERSION[i] != version_byte) {
1068 // throw DecodeError("Protocol version incorrect");
1069 // }
1070 //}
1071 //length -= 4;
1072 int line = 0, pos = 0;
1073 return (fromJSON(in, "<wire>", line, pos));
1074}
1075
1076void
1077MapElement::set(const std::string& key, ConstElementPtr value) {
1078 m[key] = value;
1079}
1080
1081bool
1082 MapElement::find(const std::string& id, ConstElementPtr& t) const {
1083 try {
1084 ConstElementPtr p = find(id);
1085 if (p) {
1086 t = p;
1087 return (true);
1088 }
1089 } catch (const TypeError&) {
1090 // ignore
1091 }
1092 return (false);
1093}
1094
1095bool
1096IntElement::equals(const Element& other, unsigned) const {
1097 // Let's not be very picky with constraining the integer types to be the
1098 // same. Equality is sometimes checked from high-up in the Element hierarchy.
1099 // That is a context which, most of the time, does not have information on
1100 // the type of integers stored on Elements lower in the hierarchy. So it
1101 // would be difficult to differentiate between the integer types.
1102 return (other.getType() == Element::integer && i == other.intValue()) ||
1103 (other.getType() == Element::bigint && i == other.bigIntValue());
1104}
1105
1106bool
1107BigIntElement::equals(const Element& other, unsigned) const {
1108 // Let's not be very picky with constraining the integer types to be the
1109 // same. Equality is sometimes checked from high-up in the Element hierarchy.
1110 // That is a context which, most of the time, does not have information on
1111 // the type of integers stored on Elements lower in the hierarchy. So it
1112 // would be difficult to differentiate between the integer types.
1113 return (other.getType() == Element::bigint && i_ == other.bigIntValue()) ||
1114 (other.getType() == Element::integer && i_ == other.intValue());
1115}
1116
1117bool
1118DoubleElement::equals(const Element& other, unsigned) const {
1119 return (other.getType() == Element::real) &&
1120 (fabs(d - other.doubleValue()) < 1e-14);
1121}
1122
1123bool
1124BoolElement::equals(const Element& other, unsigned) const {
1125 return (other.getType() == Element::boolean) &&
1126 (b == other.boolValue());
1127}
1128
1129bool
1130NullElement::equals(const Element& other, unsigned) const {
1131 return (other.getType() == Element::null);
1132}
1133
1134bool
1135StringElement::equals(const Element& other, unsigned) const {
1136 return (other.getType() == Element::string) &&
1137 (s == other.stringValue());
1138}
1139
1140bool
1141ListElement::equals(const Element& other, unsigned level) const {
1142 if (level == 0) {
1143 isc_throw(BadValue, "equals got infinite recursion: "
1144 "arguments include cycles");
1145 }
1146 if (other.getType() == Element::list) {
1147 const size_t s = size();
1148 if (s != other.size()) {
1149 return (false);
1150 }
1151 for (size_t i = 0; i < s; ++i) {
1152 if (!get(i)->equals(*other.get(i), level - 1)) {
1153 return (false);
1154 }
1155 }
1156 return (true);
1157 } else {
1158 return (false);
1159 }
1160}
1161
1162void
1163ListElement::sort(std::string const& index /* = std::string() */) {
1164 if (l.empty()) {
1165 return;
1166 }
1167
1168 int const t(l.at(0)->getType());
1169 std::function<bool(ElementPtr, ElementPtr)> comparator;
1170 if (t == map) {
1171 if (index.empty()) {
1172 isc_throw(BadValue, "index required when sorting maps");
1173 }
1174 comparator = [&](ElementPtr const& a, ElementPtr const& b) {
1175 ConstElementPtr const& ai(a->get(index));
1176 ConstElementPtr const& bi(b->get(index));
1177 if (ai && bi) {
1178 return *ai < *bi;
1179 }
1180 return true;
1181 };
1182 } else if (t == list) {
1183 // Nested lists. Not supported.
1184 return;
1185 } else {
1186 // Assume scalars.
1187 if (!index.empty()) {
1188 isc_throw(BadValue, "index given when sorting scalars?");
1189 }
1190 comparator = [&](ElementPtr const& a, ElementPtr const& b) {
1191 return *a < *b;
1192 };
1193 }
1194
1195 std::sort(l.begin(), l.end(), comparator);
1196}
1197
1198bool
1199MapElement::equals(const Element& other, unsigned level) const {
1200 if (level == 0) {
1201 isc_throw(BadValue, "equals got infinite recursion: "
1202 "arguments include cycles");
1203 }
1204 if (other.getType() == Element::map) {
1205 if (size() != other.size()) {
1206 return (false);
1207 }
1208 for (auto const& kv : mapValue()) {
1209 auto key = kv.first;
1210 if (other.contains(key)) {
1211 if (!get(key)->equals(*other.get(key), level - 1)) {
1212 return (false);
1213 }
1214 } else {
1215 return (false);
1216 }
1217 }
1218 return (true);
1219 } else {
1220 return (false);
1221 }
1222}
1223
1224bool
1225isNull(ConstElementPtr p) {
1226 return (!p);
1227}
1228
1229void
1230removeIdentical(ElementPtr a, ConstElementPtr b) {
1231 if (!b) {
1232 return;
1233 }
1234 if (a->getType() != Element::map || b->getType() != Element::map) {
1235 isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
1236 }
1237
1238 // As maps do not allow entries with multiple keys, we can either iterate
1239 // over a checking for identical entries in b or vice-versa. As elements
1240 // are removed from a if a match is found, we choose to iterate over b to
1241 // avoid problems with element removal affecting the iterator.
1242 for (auto const& kv : b->mapValue()) {
1243 auto key = kv.first;
1244 if (a->contains(key)) {
1245 if (a->get(key)->equals(*b->get(key))) {
1246 a->remove(key);
1247 }
1248 }
1249 }
1250}
1251
1252 ConstElementPtr
1253 removeIdentical(ConstElementPtr a, ConstElementPtr b) {
1254 ElementPtr result = Element::createMap();
1255
1256 if (!b) {
1257 return (result);
1258 }
1259
1260 if (a->getType() != Element::map || b->getType() != Element::map) {
1261 isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
1262 }
1263
1264 for (auto const& kv : a->mapValue()) {
1265 auto key = kv.first;
1266 if (!b->contains(key) ||
1267 !a->get(key)->equals(*b->get(key))) {
1268 result->set(key, kv.second);
1269 }
1270 }
1271
1272 return (result);
1273}
1274
1275void
1276merge(ElementPtr element, ConstElementPtr other) {
1277 if (element->getType() != Element::map ||
1278 other->getType() != Element::map) {
1279 isc_throw(TypeError, "merge arguments not MapElements");
1280 }
1281
1282 for (auto const& kv : other->mapValue()) {
1283 auto key = kv.first;
1284 auto value = kv.second;
1285 if (value && value->getType() != Element::null) {
1286 element->set(key, value);
1287 } else if (element->contains(key)) {
1288 element->remove(key);
1289 }
1290 }
1291}
1292
1293void
1294mergeDiffAdd(ElementPtr& element, ElementPtr& other,
1295 HierarchyDescriptor& hierarchy, std::string key, size_t idx,
1296 unsigned level) {
1297 if (level == 0) {
1298 isc_throw(BadValue, "mergeDiffAdd got infinite recursion: "
1299 "arguments include cycles");
1300 }
1301 if (element->getType() != other->getType()) {
1302 isc_throw(TypeError, "mergeDiffAdd arguments not same type");
1303 }
1304
1305 if (element->getType() == Element::list) {
1306 // Store new elements in a separate container so we don't overwrite
1307 // options as we add them (if there are duplicates).
1308 ElementPtr new_elements = Element::createList();
1309 for (auto const& right : other->listValue()) {
1310 // Check if we have any description of the key in the configuration
1311 // hierarchy.
1312 auto f = hierarchy[idx].find(key);
1313 if (f != hierarchy[idx].end()) {
1314 bool found = false;
1315 ElementPtr mutable_right = boost::const_pointer_cast<Element>(right);
1316 for (auto const& left : element->listValue()) {
1317 ElementPtr mutable_left = boost::const_pointer_cast<Element>(left);
1318 // Check if the elements refer to the same configuration
1319 // entity.
1320 if (f->second.match_(mutable_left, mutable_right)) {
1321 found = true;
1322 mergeDiffAdd(mutable_left, mutable_right, hierarchy,
1323 key, idx, level - 1);
1324 }
1325 }
1326 if (!found) {
1327 new_elements->add(right);
1328 }
1329 } else {
1330 new_elements->add(right);
1331 }
1332 }
1333 // Finally add the new elements.
1334 for (auto const& right : new_elements->listValue()) {
1335 element->add(right);
1336 }
1337 return;
1338 }
1339
1340 if (element->getType() == Element::map) {
1341 for (auto const& kv : other->mapValue()) {
1342 auto current_key = kv.first;
1343 auto value = boost::const_pointer_cast<Element>(kv.second);
1344 if (value && value->getType() != Element::null) {
1345 if (element->contains(current_key) &&
1346 (value->getType() == Element::map ||
1347 value->getType() == Element::list)) {
1348 ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
1349 mergeDiffAdd(mutable_element, value, hierarchy,
1350 current_key, idx + 1, level - 1);
1351 } else {
1352 element->set(current_key, value);
1353 }
1354 }
1355 }
1356 return;
1357 }
1358 element = other;
1359}
1360
1361void
1362mergeDiffDel(ElementPtr& element, ElementPtr& other,
1363 HierarchyDescriptor& hierarchy, std::string key, size_t idx,
1364 unsigned level) {
1365 if (level == 0) {
1366 isc_throw(BadValue, "mergeDiffDel got infinite recursion: "
1367 "arguments include cycles");
1368 }
1369 if (element->getType() != other->getType()) {
1370 isc_throw(TypeError, "mergeDiffDel arguments not same type");
1371 }
1372
1373 if (element->getType() == Element::list) {
1374 for (auto const& value : other->listValue()) {
1375 ElementPtr mutable_right = boost::const_pointer_cast<Element>(value);
1376 for (uint32_t iter = 0; iter < element->listValue().size();) {
1377 bool removed = false;
1378 // Check if we have any description of the key in the
1379 // configuration hierarchy.
1380 auto f = hierarchy[idx].find(key);
1381 if (f != hierarchy[idx].end()) {
1382 ElementPtr mutable_left = boost::const_pointer_cast<Element>(element->listValue().at(iter));
1383 // Check if the elements refer to the same configuration
1384 // entity.
1385 if (f->second.match_(mutable_left, mutable_right)) {
1386 // Check if the user supplied data only contains
1387 // identification information, so the intent is to
1388 // delete the element, not just element data.
1389 if (f->second.no_data_(mutable_right)) {
1390 element->remove(iter);
1391 removed = true;
1392 } else {
1393 mergeDiffDel(mutable_left, mutable_right,
1394 hierarchy, key, idx, level - 1);
1395 if (mutable_left->empty()) {
1396 element->remove(iter);
1397 removed = true;
1398 }
1399 }
1400 }
1401 } else if (element->listValue().at(iter)->equals(*value)) {
1402 element->remove(iter);
1403 removed = true;
1404 }
1405 if (!removed) {
1406 ++iter;
1407 }
1408 }
1409 }
1410 return;
1411 }
1412
1413 if (element->getType() == Element::map) {
1414 // If the resulting element still contains data, we need to restore the
1415 // key parameters, so we store them here.
1416 ElementPtr new_elements = Element::createMap();
1417 for (auto const& kv : other->mapValue()) {
1418 auto current_key = kv.first;
1419 auto value = boost::const_pointer_cast<Element>(kv.second);
1420 if (value && value->getType() != Element::null) {
1421 if (element->contains(current_key)) {
1422 ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
1423 if (mutable_element->getType() == Element::map ||
1424 mutable_element->getType() == Element::list) {
1425 mergeDiffDel(mutable_element, value, hierarchy,
1426 current_key, idx + 1, level - 1);
1427 if (mutable_element->empty()) {
1428 element->remove(current_key);
1429 }
1430 } else {
1431 // Check if we have any description of the key in the
1432 // configuration hierarchy.
1433 auto f = hierarchy[idx].find(key);
1434 if (f != hierarchy[idx].end()) {
1435 // Check if the key is used for element
1436 // identification.
1437 if (f->second.is_key_(current_key)) {
1438 // Store the key parameter.
1439 new_elements->set(current_key, mutable_element);
1440 }
1441 }
1442 element->remove(current_key);
1443 }
1444 }
1445 }
1446 }
1447 // If the element still contains data, restore the key elements.
1448 if (element->size()) {
1449 for (auto const& kv : new_elements->mapValue()) {
1450 element->set(kv.first, kv.second);
1451 }
1452 }
1453 return;
1454 }
1455 element = ElementPtr(new NullElement());
1456}
1457
1458void
1459extend(const std::string& container, const std::string& extension,
1460 ElementPtr& element, ElementPtr& other, HierarchyDescriptor& hierarchy,
1461 std::string key, size_t idx, bool alter, unsigned level) {
1462
1463 if (level == 0) {
1464 isc_throw(BadValue, "extend got infinite recursion: "
1465 "arguments include cycles");
1466 }
1467 if (element->getType() != other->getType()) {
1468 isc_throw(TypeError, "extend arguments not same type");
1469 }
1470
1471 if (element->getType() == Element::list) {
1472 for (auto const& right : other->listValue()) {
1473 // Check if we have any description of the key in the configuration
1474 // hierarchy.
1475 auto f = hierarchy[idx].find(key);
1476 if (f != hierarchy[idx].end()) {
1477 ElementPtr mutable_right = boost::const_pointer_cast<Element>(right);
1478 for (auto const& left : element->listValue()) {
1479 ElementPtr mutable_left = boost::const_pointer_cast<Element>(left);
1480 if (container == key) {
1481 alter = true;
1482 }
1483 if (f->second.match_(mutable_left, mutable_right)) {
1484 extend(container, extension, mutable_left, mutable_right,
1485 hierarchy, key, idx, alter, level - 1);
1486 }
1487 }
1488 }
1489 }
1490 return;
1491 }
1492
1493 if (element->getType() == Element::map) {
1494 for (auto const& kv : other->mapValue()) {
1495 auto current_key = kv.first;
1496 auto value = boost::const_pointer_cast<Element>(kv.second);
1497 if (value && value->getType() != Element::null) {
1498 if (element->contains(current_key) &&
1499 (value->getType() == Element::map ||
1500 value->getType() == Element::list)) {
1501 ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
1502 if (container == key) {
1503 alter = true;
1504 }
1505 extend(container, extension, mutable_element, value,
1506 hierarchy, current_key, idx + 1, alter, level - 1);
1507 } else if (alter && current_key == extension) {
1508 element->set(current_key, value);
1509 }
1510 }
1511 }
1512 return;
1513 }
1514}
1515
1516 ElementPtr
1517copy(ConstElementPtr from, unsigned level) {
1518 if (!from) {
1519 isc_throw(BadValue, "copy got a null pointer");
1520 }
1521 int from_type = from->getType();
1522 if (from_type == Element::integer) {
1523 return (ElementPtr(new IntElement(from->intValue())));
1524 } else if (from_type == Element::real) {
1525 return (ElementPtr(new DoubleElement(from->doubleValue())));
1526 } else if (from_type == Element::boolean) {
1527 return (ElementPtr(new BoolElement(from->boolValue())));
1528 } else if (from_type == Element::null) {
1529 return (ElementPtr(new NullElement()));
1530 } else if (from_type == Element::string) {
1531 return (ElementPtr(new StringElement(from->stringValue())));
1532 } else if (from_type == Element::list) {
1533 ElementPtr result = ElementPtr(new ListElement());
1534 for (auto const& elem : from->listValue()) {
1535 if (level == 0) {
1536 result->add(elem);
1537 } else {
1538 result->add(copy(elem, level - 1));
1539 }
1540 }
1541 return (result);
1542 } else if (from_type == Element::map) {
1543 ElementPtr result = ElementPtr(new MapElement());
1544 for (auto const& kv : from->mapValue()) {
1545 auto key = kv.first;
1546 auto value = kv.second;
1547 if (level == 0) {
1548 result->set(key, value);
1549 } else {
1550 result->set(key, copy(value, level - 1));
1551 }
1552 }
1553 return (result);
1554 } else {
1555 isc_throw(BadValue, "copy got an element of type: " << from_type);
1556 }
1557}
1558
1559namespace {
1560
1561// Helper function which blocks infinite recursion
1562bool
1563isEquivalent0(ConstElementPtr a, ConstElementPtr b, unsigned level) {
1564 // check looping forever on cycles
1565 if (!level) {
1566 isc_throw(BadValue, "isEquivalent got infinite recursion: "
1567 "arguments include cycles");
1568 }
1569 if (!a || !b) {
1570 isc_throw(BadValue, "isEquivalent got a null pointer");
1571 }
1572 // check types
1573 if (a->getType() != b->getType()) {
1574 return (false);
1575 }
1576 if (a->getType() == Element::list) {
1577 // check empty
1578 if (a->empty()) {
1579 return (b->empty());
1580 }
1581 // check size
1582 if (a->size() != b->size()) {
1583 return (false);
1584 }
1585
1586 // copy b into a list
1587 const size_t s = a->size();
1588 std::list<ConstElementPtr> l;
1589 for (size_t i = 0; i < s; ++i) {
1590 l.push_back(b->get(i));
1591 }
1592
1593 // iterate on a
1594 for (size_t i = 0; i < s; ++i) {
1595 ConstElementPtr item = a->get(i);
1596 // lookup this item in the list
1597 bool found = false;
1598 for (auto it = l.begin(); it != l.end(); ++it) {
1599 // if found in the list remove it
1600 if (isEquivalent0(item, *it, level - 1)) {
1601 found = true;
1602 l.erase(it);
1603 break;
1604 }
1605 }
1606 // if not found argument differs
1607 if (!found) {
1608 return (false);
1609 }
1610 }
1611
1612 // sanity check: the list must be empty
1613 if (!l.empty()) {
1614 isc_throw(Unexpected, "isEquivalent internal error");
1615 }
1616 return (true);
1617 } else if (a->getType() == Element::map) {
1618 // check sizes
1619 if (a->size() != b->size()) {
1620 return (false);
1621 }
1622 // iterate on the first map
1623 for (auto const& kv : a->mapValue()) {
1624 // get the b value for the given keyword and recurse
1625 ConstElementPtr item = b->get(kv.first);
1626 if (!item || !isEquivalent0(kv.second, item, level - 1)) {
1627 return (false);
1628 }
1629 }
1630 return (true);
1631 } else {
1632 return (a->equals(*b));
1633 }
1634}
1635
1636} // end anonymous namespace
1637
1638bool
1639isEquivalent(ConstElementPtr a, ConstElementPtr b) {
1640 return (isEquivalent0(a, b, 100));
1641}
1642
1643namespace {
1644
1645void
1646prettyPrint0(ConstElementPtr element, std::ostream& out,
1647 unsigned indent, unsigned step, unsigned level) {
1648 if (level == 0) {
1649 isc_throw(BadValue, "prettyPrint got infinite recursion: "
1650 "arguments include cycles");
1651 }
1652 if (!element) {
1653 isc_throw(BadValue, "prettyPrint got a null pointer");
1654 }
1655 if (element->getType() == Element::list) {
1656 // empty list case
1657 if (element->empty()) {
1658 out << "[ ]";
1659 return;
1660 }
1661
1662 // complex ? multiline : oneline
1663 if (!element->get(0)) {
1664 isc_throw(BadValue, "prettyPrint got a null pointer");
1665 }
1666 int first_type = element->get(0)->getType();
1667 bool complex = false;
1668 if ((first_type == Element::list) || (first_type == Element::map)) {
1669 complex = true;
1670 }
1671 std::string separator = complex ? ",\n" : ", ";
1672
1673 // open the list
1674 out << "[" << (complex ? "\n" : " ");
1675
1676 // iterate on items
1677 auto const& l = element->listValue();
1678 bool first = true;
1679 for (auto const& it : l) {
1680 // add the separator if not the first item
1681 if (!first) {
1682 out << separator;
1683 } else {
1684 first = false;
1685 }
1686 // add indentation
1687 if (complex) {
1688 out << std::string(indent + step, ' ');
1689 }
1690 // recursive call
1691 prettyPrint0(it, out, indent + step, step, level - 1);
1692 }
1693
1694 // close the list
1695 if (complex) {
1696 out << "\n" << std::string(indent, ' ');
1697 } else {
1698 out << " ";
1699 }
1700 out << "]";
1701 } else if (element->getType() == Element::map) {
1702 // empty map case
1703 if (element->size() == 0) {
1704 out << "{ }";
1705 return;
1706 }
1707
1708 // open the map
1709 out << "{\n";
1710
1711 // iterate on keyword: value
1712 auto const& m = element->mapValue();
1713 bool first = true;
1714 for (auto const& it : m) {
1715 // add the separator if not the first item
1716 if (first) {
1717 first = false;
1718 } else {
1719 out << ",\n";
1720 }
1721 // add indentation
1722 out << std::string(indent + step, ' ');
1723 // add keyword:
1724 out << "\"" << it.first << "\": ";
1725 // recursive call
1726 prettyPrint0(it.second, out, indent + step, step, level - 1);
1727 }
1728
1729 // close the map
1730 out << "\n" << std::string(indent, ' ') << "}";
1731 } else {
1732 // not a list or a map
1733 element->toJSON(out);
1734 }
1735}
1736
1737} // end anonymous namespace
1738
1739void
1740prettyPrint(ConstElementPtr element, std::ostream& out,
1741 unsigned indent, unsigned step) {
1742 prettyPrint0(element, out, indent, step, Element::MAX_NESTING_LEVEL);
1743}
1744
1745std::string
1746 prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) {
1747 std::stringstream ss;
1748 prettyPrint(element, ss, indent, step);
1749 return (ss.str());
1750}
1751
1752 void Element::preprocess(std::istream& in, std::stringstream& out) {
1753
1754 std::string line;
1755
1756 while (std::getline(in, line)) {
1757 // If this is a comments line, replace it with empty line
1758 // (so the line numbers will still match
1759 if (!line.empty() && line[0] == '#') {
1760 line = "";
1761 }
1762
1763 // getline() removes end line characters. Unfortunately, we need
1764 // it for getting the line numbers right (in case we report an
1765 // error.
1766 out << line;
1767 out << "\n";
1768 }
1769}
1770
1771namespace {
1772
1773// Type of arcs.
1774typedef std::set<ConstElementPtr> Arc;
1775
1776// Helper function walking on the supposed tree.
1777bool
1778IsCircular0(ConstElementPtr element, Arc arc) {
1779 // Sanity check.
1780 if (!element) {
1781 return (false);
1782 }
1783 auto type = element->getType();
1784 // Container?
1785 if ((type != Element::list) && (type != Element::map)) {
1786 return (false);
1787 }
1788 // Empty? A cycle requires at least one element.
1789 if (element->empty()) {
1790 return (false);
1791 }
1792 // In the arc?
1793 if (arc.count(element) > 0) {
1794 return (true);
1795 }
1796 // This requires to work on a copy of the arc but it should be small.
1797 arc.insert(element);
1798 if (type == Element::list) {
1799 for (auto const& it : element->listValue()) {
1800 if (IsCircular0(it, arc)) {
1801 return (true);
1802 }
1803 }
1804 return (false);
1805 }
1806 // The argument is a map.
1807 for (auto const& it : element->mapValue()) {
1808 if (IsCircular0(it.second, arc)) {
1809 return (true);
1810 }
1811 }
1812 return (false);
1813}
1814
1815} // end anonymous namespace
1816
1817bool
1818IsCircular(ConstElementPtr element) {
1819 return (IsCircular0(element, Arc()));
1820}
1821
1822unsigned
1823getNestDepth(ConstElementPtr element, unsigned max_depth) {
1824 if (max_depth == 0U) {
1825 return (0U);
1826 }
1827 if (!element) {
1828 return (0U);
1829 }
1830 unsigned ret = 1U;
1831 if (element->getType() == Element::list) {
1832 for (auto const& i : element->listValue()) {
1833 unsigned sub = getNestDepth(i, max_depth - 1);
1834 if (sub == max_depth - 1) {
1835 return (max_depth);
1836 }
1837 if (sub + 1 > ret) {
1838 ret = sub + 1;
1839 }
1840 }
1841 } else if (element->getType() == Element::map) {
1842 for (auto const& i : element->mapValue()) {
1843 unsigned sub = getNestDepth(i.second, max_depth - 1);
1844 if (sub == max_depth - 1) {
1845 return (max_depth);
1846 }
1847 if (sub + 1 > ret) {
1848 ret = sub + 1;
1849 }
1850 }
1851
1852 }
1853 return (ret);
1854}
1855
1856} // end of isc::data namespace
1857} // end of isc namespace
bigints.h
Element::map
@ map
Definition data.h:160
Element::list
@ list
Definition data.h:159
isc::BadValue
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Definition exceptions/exceptions.h:132
isc::InvalidOperation
A generic exception that is thrown if a function is called in a prohibited way.
Definition exceptions/exceptions.h:143
isc::data::BigIntElement
Wrapper over int128_t.
Definition data.h:793
isc::data::BigIntElement::toJSON
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const override
Converts the Element to JSON format and appends it to the given stringstream.
Definition data.cc:900
isc::data::BigIntElement::equals
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const override
Checks whether the other Element is equal.
Definition data.cc:1107
isc::data::BoolElement
Definition data.h:858
isc::data::BoolElement::equals
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1124
isc::data::BoolElement::toJSON
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:921
isc::data::DoubleElement
Definition data.h:841
isc::data::DoubleElement::toJSON
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:905
isc::data::DoubleElement::equals
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1118
isc::data::Element
The Element class represents a piece of data, used by the command channel and configuration parts.
Definition data.h:73
isc::data::Element::create
static ElementPtr create(const Position &pos=ZERO_POSITION())
Create a NullElement.
Definition data.cc:299
isc::data::Element::getValue
virtual bool getValue(int64_t &t) const
Get the integer value.
Definition data.cc:123
isc::data::Element::typeToName
static std::string typeToName(Element::types type)
Returns the name of the given type as a string.
Definition data.cc:709
isc::data::Element::intValue
virtual int64_t intValue() const
Return the integer value.
Definition data.h:275
isc::data::Element::MAX_NESTING_LEVEL
static constexpr unsigned MAX_NESTING_LEVEL
Maximum nesting level of Element objects.
Definition data.h:86
isc::data::Element::str
std::string str() const
Returns a string representing the Element and all its child elements.
Definition data.cc:104
isc::data::Element::stringValue
virtual std::string stringValue() const
Return the string value.
Definition data.h:295
isc::data::Element::toWire
std::string toWire() const
Returns the wireformat for the Element and all its child elements.
Definition data.cc:111
isc::data::Element::toJSON
virtual void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const =0
Converts the Element to JSON format and appends it to the given output stream.
isc::data::Element::fromWire
static ElementPtr fromWire(std::stringstream &in, int length)
These function parse the wireformat at the given stringstream (of the given length).
Definition data.cc:1061
isc::data::Element::setValue
virtual bool setValue(const long long int v)
Set the integer value.
Definition data.cc:153
isc::data::Element::fromJSONFile
static ElementPtr fromJSONFile(const std::string &file_name, bool preproc=false)
Reads contents of specified file and interprets it as JSON.
Definition data.cc:878
isc::data::Element::empty
virtual bool empty() const
Return true if there are no elements in the list.
Definition data.cc:218
isc::data::Element::remove
virtual void remove(const int i)
Removes the element at the given position.
Definition data.cc:208
isc::data::Element::contains
virtual bool contains(const std::string &name) const
Checks if there is data at the given key.
Definition data.cc:238
isc::data::Element::find
virtual ConstElementPtr find(const std::string &identifier) const
Recursively finds any data at the given identifier.
Definition data.cc:243
isc::data::Element::size
virtual size_t size() const
Returns the number of elements in the list.
Definition data.cc:213
isc::data::Element::Element
Element(types t, const Position &pos=ZERO_POSITION())
Constructor.
Definition data.h:181
isc::data::Element::mapValue
virtual const std::map< std::string, ConstElementPtr > & mapValue() const
Return the map value.
Definition data.h:306
isc::data::Element::add
virtual void add(ElementPtr element)
Adds an ElementPtr to the list.
Definition data.cc:203
isc::data::Element::listValue
virtual const std::vector< ElementPtr > & listValue() const
Return the list value.
Definition data.h:300
isc::data::Element::equals
virtual bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const =0
Test equality.
isc::data::Element::fromJSON
static ElementPtr fromJSON(const std::string &in, bool preproc=false)
These functions will parse the given string (JSON) representation of a compound element.
Definition data.cc:859
isc::data::Element::get
virtual ConstElementPtr get(const int i) const
Returns the ElementPtr at the given index.
Definition data.cc:188
isc::data::Element::types
types
The types that an Element can hold.
Definition data.h:152
isc::data::Element::bigint
@ bigint
Definition data.h:158
isc::data::Element::real
@ real
Definition data.h:154
isc::data::Element::map
@ map
Definition data.h:160
isc::data::Element::null
@ null
Definition data.h:156
isc::data::Element::integer
@ integer
Definition data.h:153
isc::data::Element::any
@ any
Definition data.h:161
isc::data::Element::boolean
@ boolean
Definition data.h:155
isc::data::Element::list
@ list
Definition data.h:159
isc::data::Element::string
@ string
Definition data.h:157
isc::data::Element::createMap
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:354
isc::data::Element::nameToType
static Element::types nameToType(const std::string &type_name)
Converts the string to the corresponding type Throws a TypeError if the name is unknown.
Definition data.cc:735
isc::data::Element::createList
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:349
isc::data::Element::set
virtual void set(const size_t i, ElementPtr element)
Sets the ElementPtr at the given index.
Definition data.cc:198
isc::data::Element::doubleValue
virtual double doubleValue() const
Return the double value.
Definition data.h:285
isc::data::Element::bigIntValue
virtual isc::util::int128_t bigIntValue() const
Return the big integer value.
Definition data.h:280
isc::data::Element::getType
types getType() const
Definition data.h:191
isc::data::Element::boolValue
virtual bool boolValue() const
Return the boolean value.
Definition data.h:290
isc::data::Element::preprocess
static void preprocess(std::istream &in, std::stringstream &out)
input text preprocessor.
Definition data.cc:1752
isc::data::Element::getNonConst
virtual ElementPtr getNonConst(const int i) const
returns element as non-const pointer.
Definition data.cc:193
isc::data::Element::removeEmptyContainersRecursively
void removeEmptyContainersRecursively(unsigned level=MAX_NESTING_LEVEL)
Remove all empty maps and lists from this Element and its descendants.
Definition data.cc:57
isc::data::IntElement
Notes: IntElement type is changed to int64_t.
Definition data.h:776
isc::data::IntElement::toJSON
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:895
isc::data::IntElement::equals
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1096
isc::data::JSONError
A standard Data module exception that is thrown if a parse error is encountered when constructing an ...
Definition data.h:50
isc::data::ListElement
Definition data.h:902
isc::data::ListElement::sort
void sort(std::string const &index=std::string())
Sorts the elements inside the list.
Definition data.cc:1163
isc::data::ListElement::toJSON
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:983
isc::data::ListElement::equals
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1141
isc::data::MapElement
Definition data.h:948
isc::data::MapElement::find
ConstElementPtr find(const std::string &id) const override
Recursively finds any data at the given identifier.
Definition data.cc:1033
isc::data::MapElement::set
void set(const std::string &key, ConstElementPtr value) override
Sets the ElementPtr at the given key.
Definition data.cc:1077
isc::data::MapElement::equals
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const override
Test equality.
Definition data.cc:1199
isc::data::MapElement::toJSON
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const override
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:1004
isc::data::NullElement
Definition data.h:875
isc::data::NullElement::equals
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1130
isc::data::NullElement::toJSON
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:930
isc::data::StringElement
Definition data.h:885
isc::data::StringElement::equals
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1135
isc::data::StringElement::toJSON
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:935
isc::data::TypeError
A standard Data module exception that is thrown if a function is called for an Element that has a wro...
Definition data.h:37
ConstElementPtr
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
ElementPtr
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
data.h
throwTypeError
#define throwTypeError(error)
Add the position to a TypeError message should be used in place of isc_throw(TypeError,...
Definition data.h:234
isc_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition exceptions/exceptions.h:210
isc::data
Definition base_stamped_element.cc:12
isc::data::operator==
bool operator==(const Element &a, const Element &b)
Test equality.
Definition data.cc:268
isc::data::extend
void extend(const std::string &container, const std::string &extension, ElementPtr &element, ElementPtr &other, HierarchyDescriptor &hierarchy, std::string key, size_t idx, bool alter, unsigned level)
Extends data by adding the specified 'extension' elements from 'other' inside the 'container' element...
Definition data.cc:1459
isc::data::removeIdentical
void removeIdentical(ElementPtr a, ConstElementPtr b)
Remove all values from the first ElementPtr that are equal in the second.
Definition data.cc:1230
isc::data::merge
void merge(ElementPtr element, ConstElementPtr other)
Merges the data from other into element. (on the first level).
Definition data.cc:1276
isc::data::isEquivalent
bool isEquivalent(ConstElementPtr a, ConstElementPtr b)
Compares the data with other using unordered lists.
Definition data.cc:1639
isc::data::operator<
bool operator<(Element const &a, Element const &b)
Test less than.
Definition data.cc:277
isc::data::IsCircular
bool IsCircular(ConstElementPtr element)
Check if the data is circular.
Definition data.cc:1818
isc::data::prettyPrint
void prettyPrint(ConstElementPtr element, std::ostream &out, unsigned indent, unsigned step)
Pretty prints the data into stream.
Definition data.cc:1740
isc::data::copy
ElementPtr copy(ConstElementPtr from, unsigned level)
Copy the data up to a nesting level.
Definition data.cc:1517
isc::data::ConstElementPtr
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
isc::data::mergeDiffAdd
void mergeDiffAdd(ElementPtr &element, ElementPtr &other, HierarchyDescriptor &hierarchy, std::string key, size_t idx, unsigned level)
Merges the diff data by adding the missing elements from 'other' to 'element' (recursively).
Definition data.cc:1294
isc::data::getNestDepth
unsigned getNestDepth(ConstElementPtr element, unsigned max_depth)
Compute the nesting depth.
Definition data.cc:1823
isc::data::isNull
bool isNull(ConstElementPtr p)
Checks whether the given ElementPtr is a null pointer.
Definition data.cc:1225
isc::data::operator<<
std::ostream & operator<<(std::ostream &out, const Element::Position &pos)
Insert Element::Position as a string into stream.
Definition data.cc:51
isc::data::mergeDiffDel
void mergeDiffDel(ElementPtr &element, ElementPtr &other, HierarchyDescriptor &hierarchy, std::string key, size_t idx, unsigned level)
Merges the diff data by removing the data present in 'other' from 'element' (recursively).
Definition data.cc:1362
isc::data::operator!=
bool operator!=(const Element &a, const Element &b)
Test inequality.
Definition data.cc:272
isc::data::ElementPtr
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
isc::data::HierarchyDescriptor
std::vector< FunctionMap > HierarchyDescriptor
Hierarchy descriptor of the containers in a specific Element hierarchy tree.
Definition data.h:1119
isc::util::int128_t
boost::multiprecision::checked_int128_t int128_t
Definition bigints.h:19
isc
Defines the logger used by the top-level component of kea-lfc.
Definition agent_parser.cc:148
isc::data::Element::Position
Represents the position of the data element within a configuration string.
Definition data.h:107
isc::data::Element::Position::pos_
uint32_t pos_
Position within the line.
Definition data.h:110
isc::data::Element::Position::str
std::string str() const
Returns the position in the textual format.
Definition data.cc:44
isc::data::Element::Position::line_
uint32_t line_
Line number.
Definition data.h:109
isc::data::Element::Position::file_
std::string file_
File name.
Definition data.h:108