blob: 2619943ca7e79e49d4c211d22fcf34807f286453 [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átf2b91e02019-04-11 15:36:48 +020013#include "UniqueResource.h"
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
61YangSchema::~YangSchema() = default;
62
63void YangSchema::addSchemaString(const char* schema)
64{
65 if (!m_context->parse_module_mem(schema, LYS_IN_YANG)) {
66 throw YangLoadError("Couldn't load schema");
67 }
68}
69
70void YangSchema::addSchemaDirectory(const char* directoryName)
71{
72 if (m_context->set_searchdir(directoryName)) {
73 throw YangLoadError("Couldn't add schema search directory");
74 }
75}
76
77void YangSchema::addSchemaFile(const char* filename)
78{
79 if (!m_context->parse_module_path(filename, LYS_IN_YANG)) {
80 throw YangLoadError("Couldn't load schema");
81 }
82}
83
Václav Kubernát2eaceb82018-10-08 19:56:30 +020084bool YangSchema::isModule(const schemaPath_&, const std::string& name) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020085{
86 const auto set = modules();
87 return set.find(name) != set.end();
88}
89
Václav Kubernát2eaceb82018-10-08 19:56:30 +020090bool YangSchema::isContainer(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020091{
92 const auto schemaNode = getSchemaNode(location, node);
93 return schemaNode && schemaNode->nodetype() == LYS_CONTAINER;
94}
95
Václav Kubernát2eaceb82018-10-08 19:56:30 +020096bool YangSchema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020097{
98 const auto schemaNode = getSchemaNode(location, node);
99 return schemaNode && schemaNode->nodetype() == LYS_LEAF;
100}
101
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200102bool YangSchema::isList(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200103{
104 const auto schemaNode = getSchemaNode(location, node);
105 return schemaNode && schemaNode->nodetype() == LYS_LIST;
106}
107
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200108bool YangSchema::isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200109{
110 if (!isContainer(location, node))
111 return false;
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200112 return libyang::Schema_Node_Container(getSchemaNode(location, node)).presence();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200113}
114
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200115bool YangSchema::leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200116{
Václav Kubernát989b5de2019-02-20 16:28:35 +0100117 auto enums = enumValues(location, node);
118
119 return std::any_of(enums.begin(), enums.end(), [=](const auto& x) { return x == value; });
120}
121
122const std::set<std::string> YangSchema::enumValues(const schemaPath_& location, const ModuleNodePair& node) const
123{
Václav Kubernát0d4db442018-07-18 17:18:43 +0200124 if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::Enum)
Václav Kubernát989b5de2019-02-20 16:28:35 +0100125 return {};
Václav Kubernát0d4db442018-07-18 17:18:43 +0200126
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200127 libyang::Schema_Node_Leaf leaf(getSchemaNode(location, node));
Václav Kubernát6a713d62018-10-03 18:47:34 +0200128 auto type = leaf.type();
129 auto enm = type->info()->enums()->enm();
130 // The enum can be a derived type and enm() only returns values,
131 // if that specific typedef changed the possible values. So we go
132 // up the hierarchy until we find a typedef that defined these values.
133 while (enm.empty()) {
134 type = type->der()->type();
135 enm = type->info()->enums()->enm();
136 }
Václav Kubernát0d4db442018-07-18 17:18:43 +0200137
Václav Kubernát989b5de2019-02-20 16:28:35 +0100138 std::set<std::string> enumSet;
139 std::transform(enm.begin(), enm.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return it->name(); });
140 return enumSet;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200141}
142
Václav Kubernáteeb38842019-03-20 19:46:05 +0100143const std::set<std::string> YangSchema::validIdentities(const schemaPath_& location, const ModuleNodePair& node, const Prefixes prefixes) const
144{
145 if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::IdentityRef)
146 return {};
147
148 std::set<std::string> identSet;
149
150 auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
151 auto insertToSet = [&identSet, prefixes, topLevelModule](auto module, auto name) {
152 std::string stringIdent;
153 if (prefixes == Prefixes::Always || topLevelModule != module) {
154 stringIdent += module;
155 stringIdent += ":";
156 }
157 stringIdent += name;
158 identSet.emplace(stringIdent);
159 };
160
161 auto leaf = std::make_shared<libyang::Schema_Node_Leaf>(getSchemaNode(location, node));
162 auto info = leaf->type()->info();
163 for (auto base : info->ident()->ref()) { // Iterate over all bases
164 insertToSet(base->module()->name(), base->name());
165 // Iterate over derived identities (this is recursive!)
166 for (auto derived : base->der()->schema()) {
167 insertToSet(derived->module()->name(), derived->name());
168 }
169 }
170
171 return identSet;
172}
173
174bool YangSchema::leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const
175{
176 auto identities = validIdentities(location, node, Prefixes::Always);
177
178 auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
179 auto identModule = value.first ? value.first.value() : topLevelModule;
Václav Kubernát1bf704e2019-04-12 13:30:50 +0200180 return std::any_of(identities.begin(), identities.end(), [toFind = identModule + ":" + value.second](const auto& x) { return x == toFind; });
Václav Kubernáteeb38842019-03-20 19:46:05 +0100181}
182
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200183bool YangSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200184{
185 if (!isList(location, node))
186 return false;
187 const auto keys = listKeys(location, node);
188 return keys.find(key) != keys.end();
189}
190
Václav Kubernát280df4a2019-11-01 14:46:34 +0100191libyang::S_Schema_Node YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200192{
193 std::string absPath = location.m_nodes.empty() ? "" : "/";
194 absPath += pathToAbsoluteSchemaString(location) + "/" + fullNodeName(location, node);
Václav Kubernátf2b91e02019-04-11 15:36:48 +0200195
196 // If no node is found find_path prints an error message, so we have to
197 // disable logging
198 // https://github.com/CESNET/libyang/issues/753
199 {
200 int oldOptions;
201 auto logBlocker = make_unique_resource(
202 [&oldOptions]() {
203 oldOptions = libyang::set_log_options(0);
204 },
205 [&oldOptions]() {
206 libyang::set_log_options(oldOptions);
207 });
Václav Kubernát280df4a2019-11-01 14:46:34 +0100208 return m_context->get_node(nullptr, absPath.c_str());
Václav Kubernátf2b91e02019-04-11 15:36:48 +0200209 }
Václav Kubernát0d4db442018-07-18 17:18:43 +0200210}
211
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200212const std::set<std::string> YangSchema::listKeys(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200213{
214 std::set<std::string> keys;
215 if (!isList(location, node))
216 return keys;
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200217 libyang::Schema_Node_List list(getSchemaNode(location, node));
Jan Kundrát4030b772018-08-23 15:54:56 +0200218 const auto& keysVec = list.keys();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200219
Jan Kundrát4030b772018-08-23 15:54:56 +0200220 std::transform(keysVec.begin(), keysVec.end(), std::inserter(keys, keys.begin()),
Václav Kubernát0d4db442018-07-18 17:18:43 +0200221 [] (const auto& it) {return it->name();});
222 return keys;
223}
224
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200225yang::LeafDataTypes lyTypeToLeafDataTypes(LY_DATA_TYPE type)
Václav Kubernát0d4db442018-07-18 17:18:43 +0200226{
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200227 switch (type) {
Václav Kubernát0d4db442018-07-18 17:18:43 +0200228 case LY_TYPE_STRING:
229 return yang::LeafDataTypes::String;
230 case LY_TYPE_DEC64:
231 return yang::LeafDataTypes::Decimal;
232 case LY_TYPE_BOOL:
233 return yang::LeafDataTypes::Bool;
Ivona Oboňová88c78ca2019-07-02 18:40:07 +0200234 case LY_TYPE_INT8:
235 return yang::LeafDataTypes::Int8;
236 case LY_TYPE_INT16:
237 return yang::LeafDataTypes::Int16;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200238 case LY_TYPE_INT32:
Ivona Oboňová88c78ca2019-07-02 18:40:07 +0200239 return yang::LeafDataTypes::Int32;
240 case LY_TYPE_INT64:
241 return yang::LeafDataTypes::Int64;
242 case LY_TYPE_UINT8:
243 return yang::LeafDataTypes::Uint8;
244 case LY_TYPE_UINT16:
245 return yang::LeafDataTypes::Uint16;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200246 case LY_TYPE_UINT32:
Ivona Oboňová88c78ca2019-07-02 18:40:07 +0200247 return yang::LeafDataTypes::Uint32;
248 case LY_TYPE_UINT64:
249 return yang::LeafDataTypes::Uint64;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200250 case LY_TYPE_ENUM:
251 return yang::LeafDataTypes::Enum;
Václav Kubernátab538992019-03-06 15:30:50 +0100252 case LY_TYPE_BINARY:
253 return yang::LeafDataTypes::Binary;
Václav Kubernáteeb38842019-03-20 19:46:05 +0100254 case LY_TYPE_IDENT:
255 return yang::LeafDataTypes::IdentityRef;
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200256 case LY_TYPE_LEAFREF:
257 return yang::LeafDataTypes::LeafRef;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200258 default:
Václav Kubernát6a8d1d92019-04-24 20:30:36 +0200259 using namespace std::string_literals;
260 throw std::logic_error{"internal error: unsupported libyang data type "s + std::to_string(type)};
261 }
262
263}
264
265yang::LeafDataTypes YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
266{
267 using namespace std::string_literals;
268 if (!isLeaf(location, node))
269 throw InvalidSchemaQueryException(fullNodeName(location, node) + " is not a leaf");
270
271 libyang::Schema_Node_Leaf leaf(getSchemaNode(location, node));
272 auto baseType{leaf.type()->base()};
273 try {
274 return lyTypeToLeafDataTypes(baseType);
275 } catch(std::logic_error& ex) {
276 throw UnsupportedYangTypeException("the type of "s + fullNodeName(location, node) + " is not supported: " + ex.what());
277 }
278}
279
280yang::LeafDataTypes YangSchema::leafrefBase(const schemaPath_& location, const ModuleNodePair & node) const
281{
282 using namespace std::string_literals;
283 libyang::Schema_Node_Leaf leaf(getSchemaNode(location, node));
284 try {
285 return lyTypeToLeafDataTypes(leaf.type()->info()->lref()->target()->type()->base());
286 } catch(std::logic_error& ex) {
287 throw UnsupportedYangTypeException("the type of "s + fullNodeName(location, node) + " is not supported: " + ex.what());
Václav Kubernát0d4db442018-07-18 17:18:43 +0200288 }
289}
290
291std::set<std::string> YangSchema::modules() const
292{
Jan Kundrát4030b772018-08-23 15:54:56 +0200293 const auto& modules = m_context->get_module_iter();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200294
295 std::set<std::string> res;
Jan Kundrát4030b772018-08-23 15:54:56 +0200296 std::transform(modules.begin(), modules.end(),
Václav Kubernát0d4db442018-07-18 17:18:43 +0200297 std::inserter(res, res.end()),
298 [] (const auto module) { return module->name(); });
299 return res;
300}
301
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200302std::set<std::string> YangSchema::childNodes(const schemaPath_& path, const Recursion recursion) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200303{
304 using namespace std::string_view_literals;
305 std::set<std::string> res;
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200306 std::vector<libyang::S_Schema_Node> nodes;
307
Václav Kubernát0d4db442018-07-18 17:18:43 +0200308 if (path.m_nodes.empty()) {
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200309 nodes = m_context->data_instantiables(0);
Václav Kubernát0d4db442018-07-18 17:18:43 +0200310 } else {
311 const auto absolutePath = "/" + pathToAbsoluteSchemaString(path);
312 const auto set = m_context->find_path(absolutePath.c_str());
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200313 const auto schemaSet = set->schema();
Václav Kubernát7d82da72019-04-11 15:16:38 +0200314 nodes = (*schemaSet.begin())->child_instantiables(0);
Václav Kubernát0d4db442018-07-18 17:18:43 +0200315 }
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200316
317 for (const auto node : nodes) {
318 if (node->module()->name() == "ietf-yang-library"sv)
Václav Kubernát89728d82018-09-13 16:28:28 +0200319 continue;
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200320 if (recursion == Recursion::Recursive) {
321 for (auto it : node->tree_dfs()) {
322 res.insert(it->path(LYS_PATH_FIRST_PREFIX));
323 }
324 } else {
Václav Kubernát4f77a252019-02-19 16:51:30 +0100325 std::string toInsert;
326 if (path.m_nodes.empty() || path.m_nodes.front().m_prefix.get().m_name != node->module()->name()) {
327 toInsert += node->module()->name();
328 toInsert += ":";
329 }
330 toInsert += node->name();
331 res.insert(toInsert);
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200332 }
333 }
334
Václav Kubernát0d4db442018-07-18 17:18:43 +0200335 return res;
336}
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200337
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200338std::set<std::string> YangSchema::moduleNodes(const module_& module, const Recursion recursion) const
339{
340 std::set<std::string> res;
341 const auto yangModule = m_context->get_module(module.m_name.c_str());
342
343 std::vector<libyang::S_Schema_Node> nodes;
344
345 for (const auto node : yangModule->data_instantiables(0)) {
346 if (recursion == Recursion::Recursive) {
347 for (const auto it : node->tree_dfs()) {
348 res.insert(it->path(LYS_PATH_FIRST_PREFIX));
349 }
350 } else {
351 res.insert(module.m_name + ":" + node->name());
352 }
353 }
354
355 return res;
356}
357
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200358void YangSchema::loadModule(const std::string& moduleName)
359{
360 m_context->load_module(moduleName.c_str());
361}
362
363void YangSchema::registerModuleCallback(const std::function<std::string(const char*, const char*, const char*)>& clb)
364{
365 auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) {
366 (void)submod_revision;
367 auto moduleSource = clb(mod_name, mod_revision, submod_name);
368 if (moduleSource.empty()) {
369 return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, nullptr};
370 }
371 return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, strdup(moduleSource.c_str())};
372 };
373
374 auto deleter = [](void* data) {
375 free(data);
376 };
377 m_context->add_missing_module_callback(lambda, deleter);
378}
Václav Kubernátc31bd602019-03-07 11:44:48 +0100379
380std::shared_ptr<libyang::Data_Node> YangSchema::dataNodeFromPath(const std::string& path, const std::optional<const std::string> value) const
381{
382 return std::make_shared<libyang::Data_Node>(m_context,
383 path.c_str(),
384 value ? value.value().c_str() : nullptr,
385 LYD_ANYDATA_CONSTSTRING,
386 0);
387}
388
389std::shared_ptr<libyang::Module> YangSchema::getYangModule(const std::string& name)
390{
391 return m_context->get_module(name.c_str(), nullptr, 0);
392}