blob: a453b5014348eaff0fa32bb34dc28026baeaf133 [file] [log] [blame]
Václav Kubernát0d4db442018-07-18 17:18:43 +02001/*
2 * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
3 * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
4 *
5 * Written by Václav Kubernát <kubervac@fit.cvut.cz>
6 *
7*/
8
9#include <libyang/Libyang.hpp>
Václav Kubernátc31bd602019-03-07 11:44:48 +010010#include <libyang/Tree_Data.hpp>
Václav Kubernát0d4db442018-07-18 17:18:43 +020011#include <libyang/Tree_Schema.hpp>
12#include <string_view>
Václav Kubernát26b56082020-02-03 18:28:56 +010013#include "UniqueResource.hpp"
Václav Kubernát0d4db442018-07-18 17:18:43 +020014#include "utils.hpp"
15#include "yang_schema.hpp"
16
17class YangLoadError : public std::runtime_error {
18public:
19 using std::runtime_error::runtime_error;
20 ~YangLoadError() override = default;
21};
22
23class UnsupportedYangTypeException : public std::runtime_error {
24public:
25 using std::runtime_error::runtime_error;
26 ~UnsupportedYangTypeException() override = default;
27};
28
29class InvalidSchemaQueryException : public std::runtime_error {
30public:
31 using std::runtime_error::runtime_error;
32 ~InvalidSchemaQueryException() override = default;
33};
34
Václav Kubernát2eaceb82018-10-08 19:56:30 +020035template <typename T>
36std::string pathToYangAbsSchemPath(const T& path)
Václav Kubernát0d4db442018-07-18 17:18:43 +020037{
38 std::string res = "/";
39 std::string currentModule;
40
41 for (const auto& it : path.m_nodes) {
42 const auto name = nodeToSchemaString(it);
43
44 if (it.m_suffix.type() == typeid(module_)) {
45 currentModule = name;
46 continue;
47 } else {
48 res += currentModule + ":";
49 res += name + "/";
50 }
51 }
52
53 return res;
54}
55
56YangSchema::YangSchema()
Václav Kubernáta6c5fff2018-09-07 15:16:25 +020057 : m_context(std::make_shared<libyang::Context>(nullptr, LY_CTX_DISABLE_SEARCHDIRS | LY_CTX_DISABLE_SEARCHDIR_CWD))
Václav Kubernát0d4db442018-07-18 17:18:43 +020058{
Václav Kubernát0d4db442018-07-18 17:18:43 +020059}
60
Václav Kubernát1d50a5b2020-02-03 16:44:22 +010061YangSchema::YangSchema(std::shared_ptr<libyang::Context> lyCtx)
62 : m_context(lyCtx)
63{
64
65}
66
Václav Kubernát0d4db442018-07-18 17:18:43 +020067YangSchema::~YangSchema() = default;
68
69void YangSchema::addSchemaString(const char* schema)
70{
71 if (!m_context->parse_module_mem(schema, LYS_IN_YANG)) {
72 throw YangLoadError("Couldn't load schema");
73 }
74}
75
76void YangSchema::addSchemaDirectory(const char* directoryName)
77{
78 if (m_context->set_searchdir(directoryName)) {
79 throw YangLoadError("Couldn't add schema search directory");
80 }
81}
82
83void YangSchema::addSchemaFile(const char* filename)
84{
85 if (!m_context->parse_module_path(filename, LYS_IN_YANG)) {
86 throw YangLoadError("Couldn't load schema");
87 }
88}
89
Václav Kubernát75877de2019-11-20 17:43:02 +010090bool YangSchema::isModule(const std::string& name) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020091{
92 const auto set = modules();
93 return set.find(name) != set.end();
94}
95
Václav Kubernát2eaceb82018-10-08 19:56:30 +020096bool YangSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020097{
98 if (!isList(location, node))
99 return false;
100 const auto keys = listKeys(location, node);
101 return keys.find(key) != keys.end();
102}
103
Václav Kubernátc3866792020-02-20 14:12:56 +0100104bool YangSchema::leafIsKey(const std::string& leafPath) const
105{
106 auto node = getSchemaNode(leafPath);
107 if (!node || node->nodetype() != LYS_LEAF)
108 return false;
109
110 return libyang::Schema_Node_Leaf{node}.is_key().get();
111}
112
Václav Kubernát47a3f672019-11-08 15:42:43 +0100113libyang::S_Schema_Node YangSchema::impl_getSchemaNode(const std::string& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200114{
Václav Kubernátf2b91e02019-04-11 15:36:48 +0200115 // If no node is found find_path prints an error message, so we have to
116 // disable logging
117 // https://github.com/CESNET/libyang/issues/753
118 {
119 int oldOptions;
120 auto logBlocker = make_unique_resource(
121 [&oldOptions]() {
122 oldOptions = libyang::set_log_options(0);
123 },
124 [&oldOptions]() {
125 libyang::set_log_options(oldOptions);
126 });
Václav Kubernát47a3f672019-11-08 15:42:43 +0100127 return m_context->get_node(nullptr, node.c_str());
Václav Kubernátf2b91e02019-04-11 15:36:48 +0200128 }
Václav Kubernát0d4db442018-07-18 17:18:43 +0200129}
130
Václav Kubernát47a3f672019-11-08 15:42:43 +0100131
132libyang::S_Schema_Node YangSchema::getSchemaNode(const std::string& node) const
133{
134 return impl_getSchemaNode(node);
135}
136
137libyang::S_Schema_Node YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const
138{
Václav Kubernátefcac932020-01-10 15:26:32 +0100139 std::string absPath = joinPaths(pathToSchemaString(location, Prefixes::Always), fullNodeName(location, node));
Václav Kubernát47a3f672019-11-08 15:42:43 +0100140
141 return impl_getSchemaNode(absPath);
142}
143
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200144const std::set<std::string> YangSchema::listKeys(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200145{
146 std::set<std::string> keys;
147 if (!isList(location, node))
148 return keys;
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200149 libyang::Schema_Node_List list(getSchemaNode(location, node));
Jan Kundrát4030b772018-08-23 15:54:56 +0200150 const auto& keysVec = list.keys();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200151
Václav Kubernát90de9502019-11-20 17:19:44 +0100152 std::transform(keysVec.begin(), keysVec.end(), std::inserter(keys, keys.begin()), [](const auto& it) { return it->name(); });
Václav Kubernát0d4db442018-07-18 17:18:43 +0200153 return keys;
154}
155
Václav Kubernát3a99f002020-03-31 02:27:41 +0200156namespace {
Václav Kubernát2984f442020-02-20 17:43:35 +0100157std::set<enum_> enumValues(const libyang::S_Type& typeArg)
Václav Kubernát0d4db442018-07-18 17:18:43 +0200158{
Václav Kubernát2984f442020-02-20 17:43:35 +0100159 auto type = typeArg;
Václav Kubernát3a99f002020-03-31 02:27:41 +0200160 auto enm = type->info()->enums()->enm();
161 // The enum can be a derived type and enm() only returns values,
162 // if that specific typedef changed the possible values. So we go
163 // up the hierarchy until we find a typedef that defined these values.
164 while (enm.empty()) {
165 type = type->der()->type();
166 enm = type->info()->enums()->enm();
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200167 }
Václav Kubernát3a99f002020-03-31 02:27:41 +0200168
169 std::vector<libyang::S_Type_Enum> enabled;
170 std::copy_if(enm.begin(), enm.end(), std::back_inserter(enabled), [](const libyang::S_Type_Enum& it) {
171 auto iffeatures = it->iffeature();
172 return std::all_of(iffeatures.begin(), iffeatures.end(), [](auto it) { return it->value(); });
173 });
174
175 std::set<enum_> enumSet;
176 std::transform(enabled.begin(), enabled.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return enum_{it->name()}; });
177 return enumSet;
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200178}
179
Václav Kubernát2984f442020-02-20 17:43:35 +0100180std::set<identityRef_> validIdentities(const libyang::S_Type& type)
Václav Kubernát3a99f002020-03-31 02:27:41 +0200181{
182 std::set<identityRef_> identSet;
183
184 // auto topLevelModule = leaf->module();
185
Václav Kubernát2984f442020-02-20 17:43:35 +0100186 auto info = type->info();
Václav Kubernát3a99f002020-03-31 02:27:41 +0200187 for (auto base : info->ident()->ref()) { // Iterate over all bases
188 identSet.emplace(base->module()->name(), base->name());
189 // Iterate over derived identities (this is recursive!)
190 for (auto derived : base->der()->schema()) {
191 identSet.emplace(derived->module()->name(), derived->name());
192 }
193 }
194
195 return identSet;
196}
197
Václav Kubernát2984f442020-02-20 17:43:35 +0100198std::string leafrefPath(const libyang::S_Type& type)
Václav Kubernát3a99f002020-03-31 02:27:41 +0200199{
Václav Kubernát2984f442020-02-20 17:43:35 +0100200 return type->info()->lref()->target()->path(LYS_PATH_FIRST_PREFIX);
Václav Kubernát3a99f002020-03-31 02:27:41 +0200201}
202}
203
204yang::LeafDataType YangSchema::impl_leafType(const libyang::S_Schema_Node& node) const
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200205{
206 using namespace std::string_literals;
Václav Kubernát3a99f002020-03-31 02:27:41 +0200207 auto leaf = std::make_shared<libyang::Schema_Node_Leaf>(node);
Václav Kubernát2984f442020-02-20 17:43:35 +0100208 std::function<yang::LeafDataType(std::shared_ptr<libyang::Type>)> resolveType;
209 resolveType = [this, &resolveType, leaf] (auto type) -> yang::LeafDataType {
210 switch (type->base()) {
211 case LY_TYPE_STRING:
212 return yang::String{};
213 case LY_TYPE_DEC64:
214 return yang::Decimal{};
215 case LY_TYPE_BOOL:
216 return yang::Bool{};
217 case LY_TYPE_INT8:
218 return yang::Int8{};
219 case LY_TYPE_INT16:
220 return yang::Int16{};
221 case LY_TYPE_INT32:
222 return yang::Int32{};
223 case LY_TYPE_INT64:
224 return yang::Int64{};
225 case LY_TYPE_UINT8:
226 return yang::Uint8{};
227 case LY_TYPE_UINT16:
228 return yang::Uint16{};
229 case LY_TYPE_UINT32:
230 return yang::Uint32{};
231 case LY_TYPE_UINT64:
232 return yang::Uint64{};
233 case LY_TYPE_BINARY:
234 return yang::Binary{};
235 case LY_TYPE_ENUM:
236 return yang::Enum{enumValues(type)};
237 case LY_TYPE_IDENT:
238 return yang::IdentityRef{validIdentities(type)};
239 case LY_TYPE_LEAFREF:
240 return yang::LeafRef{::leafrefPath(type), std::make_unique<yang::LeafDataType>(leafType(::leafrefPath(type)))};
241 case LY_TYPE_UNION:
242 {
243 auto res = yang::Union{};
244 for (auto unionType : type->info()->uni()->types()) {
245 res.m_unionTypes.push_back(resolveType(unionType));
246 }
247 return res;
248 }
249 default:
250 using namespace std::string_literals;
251 throw UnsupportedYangTypeException("the type of "s + leaf->name() + " is not supported: " + std::to_string(leaf->type()->base()));
252 }
253 };
254 return resolveType(leaf->type());
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200255}
256
Václav Kubernát3a99f002020-03-31 02:27:41 +0200257yang::LeafDataType YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát9bf36852020-02-18 17:47:56 +0100258{
259 return impl_leafType(getSchemaNode(location, node));
260}
261
Václav Kubernát3a99f002020-03-31 02:27:41 +0200262yang::LeafDataType YangSchema::leafType(const std::string& path) const
Václav Kubernát9bf36852020-02-18 17:47:56 +0100263{
264 return impl_leafType(getSchemaNode(path));
265}
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200266
Václav Kubernát6fcd0282020-02-21 16:33:08 +0100267std::optional<std::string> YangSchema::leafTypeName(const std::string& path) const
268{
269 libyang::Schema_Node_Leaf leaf(getSchemaNode(path));
270 return leaf.type()->der().get() ? std::optional{leaf.type()->der()->name()} : std::nullopt;
271}
272
Václav Kubernátbd5e3c22020-02-19 15:22:00 +0100273std::string YangSchema::leafrefPath(const std::string& leafrefPath) const
274{
275 using namespace std::string_literals;
276 libyang::Schema_Node_Leaf leaf(getSchemaNode(leafrefPath));
277 return leaf.type()->info()->lref()->target()->path(LYS_PATH_FIRST_PREFIX);
278}
Václav Kubernát0d4db442018-07-18 17:18:43 +0200279
280std::set<std::string> YangSchema::modules() const
281{
Jan Kundrát4030b772018-08-23 15:54:56 +0200282 const auto& modules = m_context->get_module_iter();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200283
284 std::set<std::string> res;
Václav Kubernát90de9502019-11-20 17:19:44 +0100285 std::transform(modules.begin(), modules.end(), std::inserter(res, res.end()), [](const auto module) { return module->name(); });
Václav Kubernát0d4db442018-07-18 17:18:43 +0200286 return res;
287}
288
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200289std::set<std::string> YangSchema::childNodes(const schemaPath_& path, const Recursion recursion) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200290{
291 using namespace std::string_view_literals;
292 std::set<std::string> res;
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200293 std::vector<libyang::S_Schema_Node> nodes;
294
Václav Kubernát0d4db442018-07-18 17:18:43 +0200295 if (path.m_nodes.empty()) {
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200296 nodes = m_context->data_instantiables(0);
Václav Kubernát0d4db442018-07-18 17:18:43 +0200297 } else {
Václav Kubernátefcac932020-01-10 15:26:32 +0100298 const auto pathString = pathToSchemaString(path, Prefixes::Always);
299 const auto node = getSchemaNode(pathString);
Václav Kubernát47a3f672019-11-08 15:42:43 +0100300 nodes = node->child_instantiables(0);
Václav Kubernát0d4db442018-07-18 17:18:43 +0200301 }
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200302
303 for (const auto node : nodes) {
304 if (node->module()->name() == "ietf-yang-library"sv)
Václav Kubernát89728d82018-09-13 16:28:28 +0200305 continue;
Václav Kubernátfa81c8c2020-02-13 17:22:46 +0100306 // FIXME: This is a temporary fix to filter out RPC nodes in
307 // tab-completion. The method will have to be changed/reworked when RPC
308 // support gets added.
309 if (node->nodetype() == LYS_RPC)
310 continue;
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200311 if (recursion == Recursion::Recursive) {
312 for (auto it : node->tree_dfs()) {
313 res.insert(it->path(LYS_PATH_FIRST_PREFIX));
314 }
315 } else {
Václav Kubernát4f77a252019-02-19 16:51:30 +0100316 std::string toInsert;
317 if (path.m_nodes.empty() || path.m_nodes.front().m_prefix.get().m_name != node->module()->name()) {
318 toInsert += node->module()->name();
319 toInsert += ":";
320 }
321 toInsert += node->name();
322 res.insert(toInsert);
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200323 }
324 }
325
Václav Kubernát0d4db442018-07-18 17:18:43 +0200326 return res;
327}
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200328
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200329std::set<std::string> YangSchema::moduleNodes(const module_& module, const Recursion recursion) const
330{
331 std::set<std::string> res;
332 const auto yangModule = m_context->get_module(module.m_name.c_str());
333
334 std::vector<libyang::S_Schema_Node> nodes;
335
336 for (const auto node : yangModule->data_instantiables(0)) {
337 if (recursion == Recursion::Recursive) {
338 for (const auto it : node->tree_dfs()) {
339 res.insert(it->path(LYS_PATH_FIRST_PREFIX));
340 }
341 } else {
342 res.insert(module.m_name + ":" + node->name());
343 }
344 }
345
346 return res;
347}
348
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200349void YangSchema::loadModule(const std::string& moduleName)
350{
351 m_context->load_module(moduleName.c_str());
352}
353
Václav Kubernátbf9c6112019-11-04 16:03:35 +0100354void YangSchema::enableFeature(const std::string& moduleName, const std::string& featureName)
355{
356 m_context->get_module(moduleName.c_str())->feature_enable(featureName.c_str());
357}
358
Václav Kubernátb52dc252019-12-04 13:03:39 +0100359void YangSchema::registerModuleCallback(const std::function<std::string(const char*, const char*, const char*, const char*)>& clb)
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200360{
361 auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) {
362 (void)submod_revision;
Václav Kubernátb52dc252019-12-04 13:03:39 +0100363 auto moduleSource = clb(mod_name, mod_revision, submod_name, submod_revision);
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200364 if (moduleSource.empty()) {
365 return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, nullptr};
366 }
367 return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, strdup(moduleSource.c_str())};
368 };
369
370 auto deleter = [](void* data) {
371 free(data);
372 };
373 m_context->add_missing_module_callback(lambda, deleter);
374}
Václav Kubernátc31bd602019-03-07 11:44:48 +0100375
376std::shared_ptr<libyang::Data_Node> YangSchema::dataNodeFromPath(const std::string& path, const std::optional<const std::string> value) const
377{
378 return std::make_shared<libyang::Data_Node>(m_context,
379 path.c_str(),
380 value ? value.value().c_str() : nullptr,
381 LYD_ANYDATA_CONSTSTRING,
Václav Kubernátab612e92019-11-26 19:51:31 +0100382 LYD_PATH_OPT_EDIT);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100383}
384
385std::shared_ptr<libyang::Module> YangSchema::getYangModule(const std::string& name)
386{
387 return m_context->get_module(name.c_str(), nullptr, 0);
388}
Václav Kubernát34ee85a2020-02-18 17:12:12 +0100389
390namespace {
391yang::NodeTypes impl_nodeType(const libyang::S_Schema_Node& node)
392{
393 if (!node) {
394 throw InvalidNodeException();
395 }
396 switch (node->nodetype()) {
397 case LYS_CONTAINER:
398 return libyang::Schema_Node_Container{node}.presence() ? yang::NodeTypes::PresenceContainer : yang::NodeTypes::Container;
399 case LYS_LEAF:
400 return yang::NodeTypes::Leaf;
401 case LYS_LIST:
402 return yang::NodeTypes::List;
403 default:
Václav Kubernát2a141392020-02-18 17:12:32 +0100404 throw InvalidNodeException(); // FIXME: Implement all types.
Václav Kubernát34ee85a2020-02-18 17:12:12 +0100405 }
406}
407}
408
409yang::NodeTypes YangSchema::nodeType(const schemaPath_& location, const ModuleNodePair& node) const
410{
411 return impl_nodeType(getSchemaNode(location, node));
412}
413
414yang::NodeTypes YangSchema::nodeType(const std::string& path) const
415{
416 return impl_nodeType(getSchemaNode(path));
417}
Václav Kubernát1e09bd62020-02-17 15:13:38 +0100418
419std::optional<std::string> YangSchema::description(const std::string& path) const
420{
421 auto node = getSchemaNode(path.c_str());
422 return node->dsc() ? std::optional{node->dsc()} : std::nullopt;
423}
424
425std::optional<std::string> YangSchema::units(const std::string& path) const
426{
427 auto node = getSchemaNode(path.c_str());
428 if (node->nodetype() != LYS_LEAF) {
429 return std::nullopt;
430 }
431 libyang::Schema_Node_Leaf leaf{node};
432 auto units = leaf.units();
433
434 // A leaf can specify units as part of its definition.
435 if (units) {
436 return units;
437 }
438
439 // A typedef (or its parent typedefs) can specify units too. We'll use the first `units` we find.
440 for (auto parentTypedef = leaf.type()->der(); parentTypedef; parentTypedef = parentTypedef->type()->der()) {
441 units = parentTypedef->units();
442 if (units) {
443 return units;
444 }
445 }
446
447 return std::nullopt;
448}