-
Notifications
You must be signed in to change notification settings - Fork 7
/
DataNode.cpp
1262 lines (1133 loc) · 40.5 KB
/
DataNode.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
*
* Written by Václav Kubernát <kubernat@cesnet.cz>
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <algorithm>
#include <cstring>
#include <functional>
#include <libyang-cpp/Collection.hpp>
#include <libyang-cpp/DataNode.hpp>
#include <libyang-cpp/Set.hpp>
#include <libyang-cpp/Utils.hpp>
#include <libyang/libyang.h>
#include <libyang/tree_data.h>
#include <stdexcept>
#include <string>
#include "libyang-cpp/Module.hpp"
#include "utils/deleters.hpp"
#include "utils/enum.hpp"
#include "utils/newPath.hpp"
#include "utils/ref_count.hpp"
#ifdef _MSC_VER
# define __builtin_unreachable() __assume(0)
#endif
namespace libyang {
/**
* @brief Wraps a completely new tree. Used only internally.
*/
DataNode::DataNode(lyd_node* node, std::shared_ptr<ly_ctx> ctx)
: m_node(node)
, m_refs(std::make_shared<internal_refcount>(ctx))
{
registerRef();
}
/**
* @brief Wraps an unmanaged (non-owning) tree.
*/
DataNode::DataNode(lyd_node* node, const unmanaged_tag)
: m_node(node)
, m_refs(nullptr)
{
}
/**
* @brief Wraps an existing tree. Used only internally.
*/
DataNode::DataNode(lyd_node* node, std::shared_ptr<internal_refcount> viewCount)
: m_node(node)
, m_refs(viewCount)
{
registerRef();
}
DataNode::~DataNode()
{
unregisterRef();
freeIfNoRefs();
}
DataNode::DataNode(const DataNode& other)
: m_node(other.m_node)
, m_refs(other.m_refs)
{
registerRef();
}
DataNode& DataNode::operator=(const DataNode& other)
{
if (this == &other) {
return *this;
}
unregisterRef();
freeIfNoRefs();
this->m_node = other.m_node;
this->m_refs = other.m_refs;
registerRef();
return *this;
}
void DataNode::registerRef()
{
if (m_refs) {
m_refs->nodes.emplace(this);
}
}
void DataNode::unregisterRef()
{
if (m_refs) {
m_refs->nodes.erase(this);
}
}
void DataNode::freeIfNoRefs()
{
if (!m_refs) {
return;
}
if (m_refs->nodes.size() == 0) {
for (const auto& set : m_refs->dataSets) {
set->invalidate();
}
for (const auto& collection : m_refs->dataCollectionsDfs) {
collection->invalidate();
}
for (const auto& collection : m_refs->dataCollectionsSibling) {
collection->invalidate();
}
lyd_free_all(m_node);
}
}
/**
* @brief Returns the first sibling of this node list.
*
* Wraps `lyd_first_sibling`.
*/
DataNode DataNode::firstSibling() const
{
return DataNode{lyd_first_sibling(m_node), m_refs};
}
/**
* @brief Returns the previous sibling node. If there's no previous sibling node, returns this node.
*
* Wraps `lyd_node::prev`.
*/
DataNode DataNode::previousSibling() const
{
return DataNode{m_node->prev, m_refs};
}
/**
* @brief Returns the next sibling node. If there's no next sibling node, returns std::nullopt.
*
* Wraps `lyd_node::next`.
*/
std::optional<DataNode> DataNode::nextSibling() const
{
if (!m_node->next) {
return std::nullopt;
}
return DataNode{m_node->next, m_refs};
}
/**
* @brief Returns the first child of this node. Works for opaque nodes as well.
* @return The first child. std::nullopt if this node has no children.
*
* Wraps `lyd_child`.
*/
std::optional<DataNode> DataNode::child() const
{
auto node = lyd_child(m_node);
if (!node) {
return std::nullopt;
}
return DataNode{node, m_refs};
}
/**
* @brief Returns the parent of this node.
* @return The parent. std::nullopt if this node is a top-level node.
*
* Wraps `lyd_node::parent`.
*/
std::optional<DataNode> DataNode::parent() const
{
if (!m_node->parent) {
return std::nullopt;
}
return DataNode{reinterpret_cast<lyd_node*>(m_node->parent), m_refs};
}
/**
* @brief Prints the tree into a string.
* @param format Format of the output string.
*
* Wraps `lyd_print_mem`.
*/
std::optional<std::string> DataNode::printStr(const DataFormat format, const PrintFlags flags) const
{
char* str;
std::optional<std::string> res;
auto err = lyd_print_mem(&str, m_node, utils::toLydFormat(format), utils::toPrintFlags(flags));
throwIfError(err, "DataNode::printStr");
if (!str) {
return std::nullopt;
}
auto strDeleter = std::unique_ptr<char, deleter_free_t>(str);
return str;
}
/**
* @brief Returns a node specified by `path`.
* If the node is not found, returns std::nullopt.
* Throws on errors.
*
* @param path Node to search for.
* @param inputOutputNodes Consider input or output nodes
* @return DataView is the node is found, other std::nullopt.
*
* Wraps `lyd_find_path`.
*/
std::optional<DataNode> DataNode::findPath(const std::string& path, const InputOutputNodes inputOutputNodes) const
{
lyd_node* node;
auto err = lyd_find_path(m_node, path.c_str(), inputOutputNodes == InputOutputNodes::Output ? true : false, &node);
switch (err) {
case LY_SUCCESS:
return DataNode{node, m_refs};
case LY_ENOTFOUND:
case LY_EINCOMPLETE: // TODO: is this really important?
return std::nullopt;
default:
throwError(err, "Error in DataNode::findPath");
}
}
/**
* @brief Returns the path of the pointed-to node.
*
* Wraps `lyd_path`.
*/
std::string DataNode::path() const
{
// TODO: handle all path types, not just LYD_PATH_STD
auto strDeleter = std::unique_ptr<char, deleter_free_t>(lyd_path(m_node, LYD_PATH_STD, nullptr, 0));
if (!strDeleter) {
throw std::bad_alloc();
}
return strDeleter.get();
}
/**
* @brief Creates a new node with the supplied path, changing this tree.
*
* This method also creates all parents of the specified node if needed. When using `CreationOptions::Update`, and the specified node exists, it is not recreated and only the value is updated.
*
* @param path Path of the new node.
* @param value String representation of the value. Use std::nullopt for non-leaf nodes and the `empty` type.
* @param options Options that change the behavior of this method.
* @return Returns the first created node. If no nodes were created, returns std::nullopt.
*
* Wraps `lyd_new_path`.
*/
std::optional<DataNode> DataNode::newPath(const std::string& path, const std::optional<std::string>& value, const std::optional<CreationOptions> options) const
{
return impl::newPath(m_node, nullptr, m_refs, path, value, options);
}
/**
* @brief Creates a new node with the supplied path, changing this tree.
*
* This method also creates all parents of the specified node if needed. When using `CreationOptions::Update`, and the specified node exists, it is not recreated and only the value is updated.
*
* @param path Path of the new node.
* @param value String representation of the value. Use std::nullopt for non-leaf nodes and the `empty` type.
* @param options Options that change the behavior of this method.
* @return Returns the first created parent and also the node specified by `path`. These might be the same node.
*
* Wraps `lyd_new_path2`.
*/
CreatedNodes DataNode::newPath2(const std::string& path, const std::optional<std::string>& value, const std::optional<CreationOptions> options) const
{
return impl::newPath2(m_node, nullptr, m_refs, path, value ? value->c_str() : nullptr, AnydataValueType::String, options);
}
/**
* @brief Creates a new AnyData node with the supplied path, with a JSON value, changing this tree.
*
* This method also creates all parents of the specified node if needed. When using `CreationOptions::Update`, and the specified node exists, it is not recreated and only the value is updated.
*
* @param path Path of the new node.
* @param json JSON value.
* @param options Options that change the behavior of this method.
* @return Returns the first created parent and also the node specified by `path`. These might be the same node.
*
* Wraps `lyd_new_path2`.
*/
CreatedNodes DataNode::newPath2(const std::string& path, libyang::JSON json, const std::optional<CreationOptions> options) const
{
return impl::newPath2(m_node, nullptr, m_refs, path, json.content.data(), AnydataValueType::JSON, options);
}
/**
* @brief Creates a new anyxml node with the supplied path, changing this tree.
*
* @param path Path of the new node.
* @param xml XML value.
* @param options Options that change the behavior of this method.
* @return Returns the first created parent and also the node specified by `path`. These might be the same node.
*
* Wraps `lyd_new_path2`.
*/
CreatedNodes DataNode::newPath2(const std::string& path, libyang::XML xml, const std::optional<CreationOptions> options) const
{
return impl::newPath2(m_node, nullptr, m_refs, path, xml.content.data(), AnydataValueType::XML, options);
}
/**
* @brief Creates a new extension node with the supplied path, changing this tree.
*
* @param path Path of the new node.
* @param value String representation of the value. Use std::nullopt for non-leaf nodes and the `empty` type.
* @param ext Extension instance where the node being created is defined.
* @param options Options that change the behavior of this method.
* @return Returns the first created parent.
*/
std::optional<DataNode> DataNode::newExtPath(const std::string& path, const std::optional<std::string>& value, const ExtensionInstance& ext, const std::optional<CreationOptions> options) const
{
auto out = impl::newExtPath(m_node, ext.m_ext, nullptr, path, value, options);
if (!out) {
throw std::logic_error("Expected a new node to be created");
}
return *out;
}
/**
* @brief Check whether this is a term node (a leaf or a leaf-list).
*
* Wraps `LYD_NODE_TERM`.
*/
bool DataNode::isTerm() const
{
return m_node->schema && (m_node->schema->nodetype & LYD_NODE_TERM);
}
/**
* @brief Try to cast this DataNode to a DataNodeTerm node (i.e. a leaf or a leaf-list).
* @throws Error If not a leaf or a leaflist
*/
DataNodeTerm DataNode::asTerm() const
{
if (!isTerm()) {
throw Error("Node is not a leaf or a leaflist");
}
return DataNodeTerm{m_node, m_refs};
}
/**
* @brief Try to cast this DataNode to a DataNodeAny node (i.e. anydata or anyxml node).
* @throws Error If not an anydata or anyxml node
*/
DataNodeAny DataNode::asAny() const
{
if (!m_node->schema || !(m_node->schema->nodetype & LYS_ANYDATA)) {
throw Error("Node is not anydata/anyxml");
}
return DataNodeAny{m_node, m_refs};
}
/**
* @brief Parses YANG data into an operation data tree.
*
* Use this method to parse an operation whose format requires some out-of-band information, and the schema
* from the Context. This method can therefore parse:
*
* - a RESTCONF RPC,
* - a NETCONF RPC reply,
* - a RESTCONF RPC reply.
*
* In case of a RESTCONF RPC, the RPC name is encoded in the URL. In case of a response to an RPC, the RPC that
* this response "belongs to" is known out-of-band based on the communication history. In any of these cases, create
* an empty RPC/action node via newPath() to provide the original context, and then use this method to parse the
* data. Or parse the original NETCONF RPC via Context::parseOp(), and then invoke the result's .op->parseOp() to
* parse the NETCONF RPC reply.
*
* The returned `tree` contains opaque nodes holding the information extracted from the "envelopes" (i.e., an
* almost-useless `input` or `output` node for RPC and its reply, respectively). The returned `op` is always empty.
* Actual parsed payload is appended as extra child nodes to `this` object.
*
* Wraps `lyd_parse_op`.
*/
ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const
{
auto in = wrap_ly_in_new_memory(input);
switch (opType) {
case OperationType::ReplyNetconf:
case OperationType::RpcRestconf:
case OperationType::ReplyRestconf: {
lyd_node* op = nullptr;
lyd_node* tree = nullptr;
auto err = lyd_parse_op(m_node->schema->module->ctx, m_node, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, nullptr);
ParsedOp res{
.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt,
.op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt
};
throwIfError(err, "Can't parse into operation data tree");
return res;
}
case OperationType::RpcNetconf:
case OperationType::NotificationNetconf:
case OperationType::NotificationRestconf:
throw Error("To parse a notification, or a NETCONF RPC, use Context::parseOp");
default:
throw Error("Context::parseOp: unsupported op");
}
}
/**
* @brief Releases the contained value from the tree.
*
* In case of DataNode, this returned value takes ownership of the node, and the value will no longer be available.
* In case of JSON or XML, no ownership is transferred and one can call this function repeatedly.
*/
AnydataValue DataNodeAny::releaseValue()
{
auto any = reinterpret_cast<lyd_node_any*>(m_node);
switch (any->value_type) {
case LYD_ANYDATA_DATATREE: {
if (!any->value.tree) {
return std::nullopt;
}
auto res = DataNode{any->value.tree, m_refs->context};
any->value.tree = nullptr;
return res;
}
case LYD_ANYDATA_JSON:
if (!any->value.json) {
return std::nullopt;
}
return JSON{any->value.json};
case LYD_ANYDATA_XML:
if (!any->value.xml) {
return std::nullopt;
}
return XML{any->value.xml};
default:
throw std::logic_error{std::string{"Unsupported anydata value type: "} + std::to_string(any->value_type)};
}
__builtin_unreachable();
}
/**
* @brief Check if both operands point to the same node in the same tree.
*
* This comparison is a pointer comparison, meaning you could have two identical nodes in the same tree and they won't
* be equal.
*/
bool DataNode::operator==(const DataNode& node) const
{
return this->m_node == node.m_node;
}
namespace {
bool isDescendantOrEqual(lyd_node* node, lyd_node* target)
{
do {
if (node == target) {
return true;
}
node = reinterpret_cast<lyd_node*>(node->parent);
} while (node);
return false;
}
}
/**
* @brief Creates a copy of this DataNode.
* @return The first duplicated node.
*
* Wraps `lyd_dup_single`.
*/
DataNode DataNode::duplicate(const std::optional<DuplicationOptions> opts) const
{
lyd_node* dup;
auto ret = lyd_dup_single(m_node, nullptr, opts ? utils::toDuplicationOptions(*opts) : 0, &dup);
throwIfError(ret, "DataNode::duplicate:");
return DataNode{dup, m_refs->context};
}
/**
* @brief Creates a copy of this DataNode with siblings to the right of this nodes (following siblings).
* @return The first duplicated node.
*
* Wraps `lyd_dup_siblings`.
*/
DataNode DataNode::duplicateWithSiblings(const std::optional<DuplicationOptions> opts) const
{
lyd_node* dup;
auto ret = lyd_dup_siblings(m_node, nullptr, opts ? utils::toDuplicationOptions(*opts) : 0, &dup);
throwIfError(ret, "DataNode::duplicateWithSiblings:");
return DataNode{dup, m_refs->context};
}
enum class OperationScope {
JustThisNode,
AffectsFollowingSiblings,
};
/**
* @brief INTERNAL: handle memory management when working with low-level tree functions
*
* Some C-level libyang operations might split or combine trees into independent forests, which means that the
* C++-wrapper's ideas about "what lyd_node belongs to which internal_refcount have to be updated. That's what
* this function is doing.
*
* First, we gather nodes that are going to be affected (i.e., whose internal_refount should be switched to the new one).
* Then, the requested operation is invoked.
* Finally, if there are any orphaned, leftover C nodes, these are released.
*/
template <typename Operation, typename OperationScope>
void handleLyTreeOperation(DataNode* affectedNode, Operation operation, OperationScope scope, std::shared_ptr<internal_refcount> newRefs)
{
std::vector<DataNode*> wrappedSiblings{affectedNode};
if (scope == OperationScope::AffectsFollowingSiblings) {
auto fs = affectedNode->gatherReachableFollowingSiblings();
wrappedSiblings.reserve(fs.size() + 1 /* the original node */);
std::copy(fs.begin(), fs.end(), std::back_inserter(wrappedSiblings));
}
auto oldRefs = affectedNode->m_refs;
if (!oldRefs) {
// The node is an unmanaged node, we will do nothing.
operation();
return;
}
// Find a node that is not going to be affected by the operation. That node will be used as a handle to the old
// part of the original tree (the one to be released if it becomes unreachable from the C++ wrappers).
//
// Let's try the parent of the affected node as the first candidate.
auto oldTree = reinterpret_cast<lyd_node*>(affectedNode->m_node->parent);
if (!oldTree) {
// If there's no parent, consider all siblings, starting from the first (ever) one, and ignoring those
// siblings that cannot be used.
auto candidate = lyd_first_sibling(affectedNode->m_node);
while (candidate) {
if (candidate != affectedNode->m_node) {
oldTree = candidate;
break;
}
if (scope == OperationScope::AffectsFollowingSiblings) {
// all the remaining ones are going to be affected -> no luck
break;
}
// none of the preceding siblings have matched so far; try the next ones
candidate = candidate->next;
}
// If we didn't find any such sibling (oldTree == nullptr), we're updating the refcounter of all siblings,
// and there's nothing that's being orphaned.
}
if (oldRefs != newRefs) { // If the nodes already have the new refcounter, then there's nothing to do.
for (auto& node : wrappedSiblings) {
node->unregisterRef();
node->m_refs = newRefs;
node->registerRef();
// All references to this node and its children will need to have this new refcounter.
for (auto it = oldRefs->nodes.begin(); it != oldRefs->nodes.end(); /* nothing */) {
if (isDescendantOrEqual((*it)->m_node, node->m_node)) {
// The child needs to be added to the new refcounter and removed from the old refcounter.
(*it)->m_refs = node->m_refs;
(*it)->registerRef();
// Can't use unregisterRef, because I need the value from the erase method to continue the
// iteration. FIXME: maybe unregisterRef can return that? doesn't make much sense tho.
it = oldRefs->nodes.erase(it);
} else {
it++;
}
}
// If we're updating a node that belongs to a collection, we need to invalidate it.
// For example:
// A <- iterator starts here (that's (*it)->m_start)
// | |
// B C <- we're updating this one (that's m_node). The iterator's m_current might also be this node.
//
// We must invalidate the whole collection and all its iterators, because the iterators might point to the node
// being updated.
//
// If a collection is a descendant of currently updated node, we also invalidate it, because the whole
// subtree now has a different m_refs and it's difficult to keep track of that.
for (const auto& it : oldRefs->dataCollectionsDfs) {
if (isDescendantOrEqual(node->m_node, it->m_start) || isDescendantOrEqual(it->m_start, node->m_node)) {
it->invalidate();
}
}
// We need to invalidate all DataSets unconditionally, we can't be sure what's in them, potentially anything.
for (const auto& it : oldRefs->dataSets) {
it->invalidate();
}
// Sibling collections also have to be unvalidated, in case we free something we hold an iterator to.
for (const auto& it : oldRefs->dataCollectionsSibling) {
it->invalidate();
}
}
}
operation();
// If oldTree exists and we don't hold any references to it, we must also free it.
if (oldTree && oldRefs->nodes.size() == 0) {
lyd_free_all(reinterpret_cast<lyd_node*>(oldTree));
}
}
/**
* @brief Unlinks this node, creating a new tree.
*
* Wraps `lyd_unlink_tree`.
*/
void DataNode::unlink()
{
handleLyTreeOperation(this, [this] () {
lyd_unlink_tree(m_node);
}, OperationScope::JustThisNode, std::make_shared<internal_refcount>(m_refs->context));
}
/**
* @brief Gather all directly reachable instances of the C++ wrapper of all following siblings
*
* This is very different from "all following siblings"; only those nodes which have a libyang::DataNode already
* instantiated (and reachable through the same reference_wrapper) are returned.
* */
std::vector<DataNode*> DataNode::gatherReachableFollowingSiblings()
{
std::vector<DataNode*> res;
if (!m_refs) {
return res;
}
auto sibling = m_node->next;
while (sibling) {
std::copy_if(m_refs->nodes.begin(), m_refs->nodes.end(), std::back_inserter(res), [&sibling](auto ref) { return ref->m_node == sibling; });
sibling = sibling->next;
}
return res;
}
/**
* @brief Unlinks this node, together with all following siblings, creating a new tree.
*
* Wraps `lyd_unlink_siblings`.
*/
void DataNode::unlinkWithSiblings()
{
handleLyTreeOperation(this, [this] {
lyd_unlink_siblings(m_node);
}, OperationScope::AffectsFollowingSiblings, std::make_shared<internal_refcount>(m_refs->context));
}
/**
* @brief Inserts `toInsert` below `this`.
*
* 1) If `toInsert` has a parent, `toInsert` is automatically unlinked from its old tree.
* 2) If `toInsert` does not have a parent, this method also inserts all its following siblings.
*
* Wraps `lyd_insert_child`.
*/
void DataNode::insertChild(DataNode toInsert)
{
handleLyTreeOperation(&toInsert, [this, &toInsert] {
lyd_insert_child(this->m_node, toInsert.m_node);
}, toInsert.parent() ? OperationScope::JustThisNode : OperationScope::AffectsFollowingSiblings, m_refs);
}
/**
* @brief Inserts `toInsert` as a sibling to `this`.
*
* 1) If `toInsert` has a parent, `toInsert` is automatically unlinked from its old tree.
* 2) If `toInsert` does not have a parent, this method also inserts all its following siblings.
*
* @return The first sibling after insertion.
*
* Wraps `lyd_insert_sibling`.
*/
DataNode DataNode::insertSibling(DataNode toInsert)
{
lyd_node* firstSibling;
handleLyTreeOperation(&toInsert, [this, &toInsert, &firstSibling] {
lyd_insert_sibling(this->m_node, toInsert.m_node, &firstSibling);
}, toInsert.parent() ? OperationScope::JustThisNode : OperationScope::AffectsFollowingSiblings, m_refs);
return DataNode{m_node, m_refs};
}
/**
* @brief Inserts `toInsert` as a following sibling to `this`.
*
* Wraps `lyd_insert_after`.
*/
void DataNode::insertAfter(DataNode toInsert)
{
handleLyTreeOperation(&toInsert, [this, &toInsert] {
lyd_insert_after(this->m_node, toInsert.m_node);
}, OperationScope::JustThisNode, m_refs);
}
/**
* @brief Inserts `toInsert` as a preceeding sibling to `this`.
*
* Wraps `lyd_insert_before`.
*/
void DataNode::insertBefore(DataNode toInsert)
{
handleLyTreeOperation(&toInsert, [this, &toInsert] {
lyd_insert_before(this->m_node, toInsert.m_node);
}, OperationScope::JustThisNode, m_refs);
}
/**
* @brief Merges `toMerge` into `this`. After the operation, `this` will always point to the first sibling.
*
* Both `this` and `toMerge` must be a top-level node.
*
* Wraps `lyd_merge_tree`.
*/
void DataNode::merge(DataNode toMerge)
{
// No memory management needed, the original tree is left untouched. The m_refs is not shared between `this` and
// `toMerge` after this operation. Merge in this situation is more like a "copy stuff from `toMerge` to `this`".
// lyd_merge_tree can also spend the source tree using LYD_MERGE_DESTRUCT, but this method does not implement that.
// TODO: implement LYD_MERGE_DESTRUCT
lyd_merge_tree(&this->m_node, toMerge.m_node, 0);
}
/**
* @brief Gets the value of this term node as a string_view.
*
* The string_view must not outlive the DataNodeTerm's lifetime.
*/
std::string_view DataNodeTerm::valueStr() const
{
return lyd_get_value(m_node);
}
/**
* @brief Checks whether this DataNodeTerm contains the default value.
*
* Wraps `lyd_is_default`.
*/
bool DataNodeTerm::isDefaultValue() const
{
return lyd_is_default(m_node);
}
namespace {
/**
* This function emulates LYD_VALUE_GET. I can't use that macro directly, because it implicitly converts void* to Type*
* and that's not allowed in C++.
*/
template <typename Type>
const Type* valueGetSpecial(const lyd_value* value)
{
if (sizeof(Type) > LYD_VALUE_FIXED_MEM_SIZE) {
return static_cast<const Type*>(value->dyn_mem);
} else {
return reinterpret_cast<const Type*>(value->fixed_mem);
}
}
}
/**
* @brief Retrieves a value in a machine-readable format.
*
* The lifetime of the value is not connected to the lifetime of the original DataNodeTerm.
*/
Value DataNodeTerm::value() const
{
std::function<Value(lyd_value)> impl = [this, &impl](const lyd_value value) -> Value {
auto baseType = value.realtype->basetype;
switch (baseType) {
case LY_TYPE_INT8:
return value.int8;
case LY_TYPE_INT16:
return value.int16;
case LY_TYPE_INT32:
return value.int32;
case LY_TYPE_INT64:
return value.int64;
case LY_TYPE_UINT8:
return value.uint8;
case LY_TYPE_UINT16:
return value.uint16;
case LY_TYPE_UINT32:
return value.uint32;
case LY_TYPE_UINT64:
return value.uint64;
case LY_TYPE_BOOL:
return static_cast<bool>(value.boolean);
case LY_TYPE_EMPTY:
return Empty{};
case LY_TYPE_BINARY: {
auto binValue = valueGetSpecial<lyd_value_binary>(&value);
Binary res;
std::copy(static_cast<uint8_t*>(binValue->data), static_cast<uint8_t*>(binValue->data) + binValue->size, std::back_inserter(res.data));
res.base64 = valueStr();
return res;
}
case LY_TYPE_STRING:
// valueStr gives a string_view, so here I have to copy the string.
return std::string(valueStr());
case LY_TYPE_UNION:
return impl(value.subvalue->value);
case LY_TYPE_DEC64: {
auto schemaDef = reinterpret_cast<const lysc_node_leaf*>(m_node->schema);
auto dec = reinterpret_cast<const lysc_type_dec*>(schemaDef->type);
return Decimal64{value.dec64, dec->fraction_digits};
}
case LY_TYPE_BITS: {
auto bits = valueGetSpecial<lyd_value_bits>(&value);
std::vector<Bit> res;
std::transform(bits->items, bits->items + LY_ARRAY_COUNT(bits->items), std::back_inserter(res), [](const lysc_type_bitenum_item* bit) {
return Bit{.position = bit->position, .name = bit->name};
});
return res;
}
case LY_TYPE_ENUM:
return Enum{.name = value.enum_item->name, .value = value.enum_item->value};
case LY_TYPE_IDENT:
return IdentityRef{.module = value.ident->module->name, .name = value.ident->name, .schema = Identity(value.ident, this->m_refs ? this->m_refs->context : nullptr)};
case LY_TYPE_INST: {
lyd_node* out;
auto err = lyd_find_target(value.target, m_node, &out);
switch (err) {
case LY_SUCCESS:
return InstanceIdentifier{lyd_get_value(m_node), DataNode{out, m_refs}};
case LY_ENOTFOUND:
return InstanceIdentifier{lyd_get_value(m_node), std::nullopt};
default:
throwError(err, "Error when finding inst-id target");
}
}
case LY_TYPE_LEAFREF:
// Leafrefs are resolved to the underlying types, so this should never happen.
throw std::logic_error("Unknown type");
case LY_TYPE_UNKNOWN:
throw Error("Unknown type");
}
__builtin_unreachable();
};
return impl(reinterpret_cast<const lyd_node_term*>(m_node)->value);
}
/**
* @brief Returns the actual, resolved type which holds the current value
*
* This might be a different type compared to the type returned through the associated schema node; the schema node
* represents type information about *possible* data types, while this function returns a representation of a type
* which is used for storing the current value of this data node.
*
* Due to the libyang limitations, the returned type will not contain any parsed-type representation, and therefore
* even calling types::Type::name() on it will fail (possibly with a misleading exception
* "Context not created with libyang::ContextOptions::SetPrivParsed" even when the context *was* created with this
* option).
*/
types::Type DataNodeTerm::valueType() const
{
std::function<types::Type(lyd_value)> impl = [this, &impl](const lyd_value value) -> types::Type {
switch (value.realtype->basetype) {
case LY_TYPE_UNION:
return impl(value.subvalue->value);
default:
return types::Type{value.realtype, nullptr, m_refs->context};
}
};
return impl(reinterpret_cast<const lyd_node_term*>(m_node)->value);
}
/**
* @brief Returns a collection for iterating depth-first over the subtree this instance points to.
*
* Any kind of low-level manipulation (e.g. unlinking) of the subtree invalidates the iterator.
* If the `DataNodeCollectionDfs` object gets destroyed, all iterators associated with it get invalidated.
*/
Collection<DataNode, IterationType::Dfs> DataNode::childrenDfs() const
{
return Collection<DataNode, IterationType::Dfs>{m_node, m_refs};
}
/**
* @brief Returns a collection for iterating over the following siblings of this instance.
*
* Preceeding siblings are not part of the iteration. The iteration does not wrap, it ends when there are no more
* following siblings.
*/
Collection<DataNode, IterationType::Sibling> DataNode::siblings() const
{
return Collection<DataNode, IterationType::Sibling>{m_node, m_refs};
}
/**
* @brief Returns a collection for iterating over the immediate children of this instance.
*
* This is a convenience function for iterating over this->child().siblings() which does not throw even if this is a leaf.
*/
Collection<DataNode, IterationType::Sibling> DataNode::immediateChildren() const
{
auto c = child();
return c ? c->siblings() : Collection<DataNode, IterationType::Sibling>{nullptr, nullptr};
}
/**
* @brief Returns the associated SchemaNode to this DataNode.
*
* Does not work for opaque nodes.
*/
SchemaNode DataNode::schema() const
{
if (isOpaque()) {
throw Error{"DataNode::schema(): node is opaque"};
}
return SchemaNode{m_node->schema, m_refs ? m_refs->context : nullptr};
}
/**
* @brief Creates metadata for the node.
*
* Wraps `lyd_new_meta`.
*/
void DataNode::newMeta(const Module& module, const std::string& name, const std::string& value)
{
if (!m_node->schema) {
throw Error{"DataNode::newMeta: can't add attributes to opaque nodes"};
}
// TODO: allow setting the clear_dflt argument
// TODO: allow returning the lyd_meta struct
auto ret = lyd_new_meta(m_refs->context.get(), m_node, module.m_module, name.c_str(), value.c_str(), false, nullptr);
throwIfError(ret, "DataNode::newMeta: couldn't add metadata for " + std::string{path()});
}
/**
* @brief Returns a collection of metadata of this node.
*
* Wraps `lyd_node::meta`.
*/
MetaCollection DataNode::meta() const
{
return MetaCollection{m_node->meta, *this};
}
Meta::Meta(lyd_meta* meta, std::shared_ptr<ly_ctx> ctx)
: m_name(meta->name)
, m_value(lyd_get_meta_value(meta))
, m_mod(meta->annotation->module, ctx)
{
}
std::string Meta::name() const
{
return m_name;
}
std::string Meta::valueStr() const
{
return m_value;
}
Module Meta::module() const
{
return m_mod;
}
/**
* Creates a JSON attribute for an opaque data node.