blob: 757c9f4d4c6c3fcb66bcc844b92c90229113c70c [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>
10#include <libyang/Tree_Schema.hpp>
11#include <string_view>
12#include "utils.hpp"
13#include "yang_schema.hpp"
14
15class YangLoadError : public std::runtime_error {
16public:
17 using std::runtime_error::runtime_error;
18 ~YangLoadError() override = default;
19};
20
21class UnsupportedYangTypeException : public std::runtime_error {
22public:
23 using std::runtime_error::runtime_error;
24 ~UnsupportedYangTypeException() override = default;
25};
26
27class InvalidSchemaQueryException : public std::runtime_error {
28public:
29 using std::runtime_error::runtime_error;
30 ~InvalidSchemaQueryException() override = default;
31};
32
Václav Kubernát2eaceb82018-10-08 19:56:30 +020033template <typename T>
34std::string pathToYangAbsSchemPath(const T& path)
Václav Kubernát0d4db442018-07-18 17:18:43 +020035{
36 std::string res = "/";
37 std::string currentModule;
38
39 for (const auto& it : path.m_nodes) {
40 const auto name = nodeToSchemaString(it);
41
42 if (it.m_suffix.type() == typeid(module_)) {
43 currentModule = name;
44 continue;
45 } else {
46 res += currentModule + ":";
47 res += name + "/";
48 }
49 }
50
51 return res;
52}
53
54YangSchema::YangSchema()
Václav Kubernáta6c5fff2018-09-07 15:16:25 +020055 : 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 +020056{
Václav Kubernát0d4db442018-07-18 17:18:43 +020057}
58
59YangSchema::~YangSchema() = default;
60
61void YangSchema::addSchemaString(const char* schema)
62{
63 if (!m_context->parse_module_mem(schema, LYS_IN_YANG)) {
64 throw YangLoadError("Couldn't load schema");
65 }
66}
67
68void YangSchema::addSchemaDirectory(const char* directoryName)
69{
70 if (m_context->set_searchdir(directoryName)) {
71 throw YangLoadError("Couldn't add schema search directory");
72 }
73}
74
75void YangSchema::addSchemaFile(const char* filename)
76{
77 if (!m_context->parse_module_path(filename, LYS_IN_YANG)) {
78 throw YangLoadError("Couldn't load schema");
79 }
80}
81
Václav Kubernát2eaceb82018-10-08 19:56:30 +020082bool YangSchema::isModule(const schemaPath_&, const std::string& name) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020083{
84 const auto set = modules();
85 return set.find(name) != set.end();
86}
87
Václav Kubernát2eaceb82018-10-08 19:56:30 +020088bool YangSchema::isContainer(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020089{
90 const auto schemaNode = getSchemaNode(location, node);
91 return schemaNode && schemaNode->nodetype() == LYS_CONTAINER;
92}
93
Václav Kubernát2eaceb82018-10-08 19:56:30 +020094bool YangSchema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020095{
96 const auto schemaNode = getSchemaNode(location, node);
97 return schemaNode && schemaNode->nodetype() == LYS_LEAF;
98}
99
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200100bool YangSchema::isList(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200101{
102 const auto schemaNode = getSchemaNode(location, node);
103 return schemaNode && schemaNode->nodetype() == LYS_LIST;
104}
105
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200106bool YangSchema::isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200107{
108 if (!isContainer(location, node))
109 return false;
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200110 return libyang::Schema_Node_Container(getSchemaNode(location, node)).presence();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200111}
112
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200113bool YangSchema::leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200114{
Václav Kubernát989b5de2019-02-20 16:28:35 +0100115 auto enums = enumValues(location, node);
116
117 return std::any_of(enums.begin(), enums.end(), [=](const auto& x) { return x == value; });
118}
119
120const std::set<std::string> YangSchema::enumValues(const schemaPath_& location, const ModuleNodePair& node) const
121{
Václav Kubernát0d4db442018-07-18 17:18:43 +0200122 if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::Enum)
Václav Kubernát989b5de2019-02-20 16:28:35 +0100123 return {};
Václav Kubernát0d4db442018-07-18 17:18:43 +0200124
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200125 libyang::Schema_Node_Leaf leaf(getSchemaNode(location, node));
Václav Kubernát6a713d62018-10-03 18:47:34 +0200126 auto type = leaf.type();
127 auto enm = type->info()->enums()->enm();
128 // The enum can be a derived type and enm() only returns values,
129 // if that specific typedef changed the possible values. So we go
130 // up the hierarchy until we find a typedef that defined these values.
131 while (enm.empty()) {
132 type = type->der()->type();
133 enm = type->info()->enums()->enm();
134 }
Václav Kubernát0d4db442018-07-18 17:18:43 +0200135
Václav Kubernát989b5de2019-02-20 16:28:35 +0100136 std::set<std::string> enumSet;
137 std::transform(enm.begin(), enm.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return it->name(); });
138 return enumSet;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200139}
140
Václav Kubernáteeb38842019-03-20 19:46:05 +0100141const std::set<std::string> YangSchema::validIdentities(const schemaPath_& location, const ModuleNodePair& node, const Prefixes prefixes) const
142{
143 if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::IdentityRef)
144 return {};
145
146 std::set<std::string> identSet;
147
148 auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
149 auto insertToSet = [&identSet, prefixes, topLevelModule](auto module, auto name) {
150 std::string stringIdent;
151 if (prefixes == Prefixes::Always || topLevelModule != module) {
152 stringIdent += module;
153 stringIdent += ":";
154 }
155 stringIdent += name;
156 identSet.emplace(stringIdent);
157 };
158
159 auto leaf = std::make_shared<libyang::Schema_Node_Leaf>(getSchemaNode(location, node));
160 auto info = leaf->type()->info();
161 for (auto base : info->ident()->ref()) { // Iterate over all bases
162 insertToSet(base->module()->name(), base->name());
163 // Iterate over derived identities (this is recursive!)
164 for (auto derived : base->der()->schema()) {
165 insertToSet(derived->module()->name(), derived->name());
166 }
167 }
168
169 return identSet;
170}
171
172bool YangSchema::leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const
173{
174 auto identities = validIdentities(location, node, Prefixes::Always);
175
176 auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
177 auto identModule = value.first ? value.first.value() : topLevelModule;
178 return std::any_of(identities.begin(), identities.end(), [identModule, value](const auto& x) { return x == identModule + ":" + value.second; });
179}
180
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200181bool YangSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200182{
183 if (!isList(location, node))
184 return false;
185 const auto keys = listKeys(location, node);
186 return keys.find(key) != keys.end();
187}
188
189bool YangSchema::nodeExists(const std::string& location, const std::string& node) const
190{
191 const auto absPath = location + "/" + node;
192 const auto set = m_context->find_path(absPath.c_str());
193 return set->number() == 1;
194}
195
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200196libyang::S_Set YangSchema::getNodeSet(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200197{
198 std::string absPath = location.m_nodes.empty() ? "" : "/";
199 absPath += pathToAbsoluteSchemaString(location) + "/" + fullNodeName(location, node);
200 return m_context->find_path(absPath.c_str());
201}
202
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200203libyang::S_Schema_Node YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200204{
205 const auto set = getNodeSet(location, node);
206 if (!set)
207 return nullptr;
Jan Kundrát4030b772018-08-23 15:54:56 +0200208 const auto& schemaSet = set->schema();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200209 if (set->number() != 1)
210 return nullptr;
Jan Kundrát4030b772018-08-23 15:54:56 +0200211 return *schemaSet.begin();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200212}
213
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200214const std::set<std::string> YangSchema::listKeys(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200215{
216 std::set<std::string> keys;
217 if (!isList(location, node))
218 return keys;
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200219 libyang::Schema_Node_List list(getSchemaNode(location, node));
Jan Kundrát4030b772018-08-23 15:54:56 +0200220 const auto& keysVec = list.keys();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200221
Jan Kundrát4030b772018-08-23 15:54:56 +0200222 std::transform(keysVec.begin(), keysVec.end(), std::inserter(keys, keys.begin()),
Václav Kubernát0d4db442018-07-18 17:18:43 +0200223 [] (const auto& it) {return it->name();});
224 return keys;
225}
226
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200227yang::LeafDataTypes YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200228{
229 using namespace std::string_literals;
230 if (!isLeaf(location, node))
231 throw InvalidSchemaQueryException(fullNodeName(location, node) + " is not a leaf");
232
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200233 libyang::Schema_Node_Leaf leaf(getSchemaNode(location, node));
Václav Kubernát0d4db442018-07-18 17:18:43 +0200234 switch (leaf.type()->base()) {
235 case LY_TYPE_STRING:
236 return yang::LeafDataTypes::String;
237 case LY_TYPE_DEC64:
238 return yang::LeafDataTypes::Decimal;
239 case LY_TYPE_BOOL:
240 return yang::LeafDataTypes::Bool;
241 case LY_TYPE_INT32:
242 return yang::LeafDataTypes::Int;
243 case LY_TYPE_UINT32:
244 return yang::LeafDataTypes::Uint;
245 case LY_TYPE_ENUM:
246 return yang::LeafDataTypes::Enum;
Václav Kubernátab538992019-03-06 15:30:50 +0100247 case LY_TYPE_BINARY:
248 return yang::LeafDataTypes::Binary;
Václav Kubernáteeb38842019-03-20 19:46:05 +0100249 case LY_TYPE_IDENT:
250 return yang::LeafDataTypes::IdentityRef;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200251 default:
252 throw UnsupportedYangTypeException("the type of "s + fullNodeName(location, node) + " is not supported");
253 }
254}
255
256std::set<std::string> YangSchema::modules() const
257{
Jan Kundrát4030b772018-08-23 15:54:56 +0200258 const auto& modules = m_context->get_module_iter();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200259
260 std::set<std::string> res;
Jan Kundrát4030b772018-08-23 15:54:56 +0200261 std::transform(modules.begin(), modules.end(),
Václav Kubernát0d4db442018-07-18 17:18:43 +0200262 std::inserter(res, res.end()),
263 [] (const auto module) { return module->name(); });
264 return res;
265}
266
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200267std::set<std::string> YangSchema::childNodes(const schemaPath_& path, const Recursion recursion) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200268{
269 using namespace std::string_view_literals;
270 std::set<std::string> res;
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200271 std::vector<libyang::S_Schema_Node> nodes;
272
Václav Kubernát0d4db442018-07-18 17:18:43 +0200273 if (path.m_nodes.empty()) {
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200274 nodes = m_context->data_instantiables(0);
Václav Kubernát0d4db442018-07-18 17:18:43 +0200275 } else {
276 const auto absolutePath = "/" + pathToAbsoluteSchemaString(path);
277 const auto set = m_context->find_path(absolutePath.c_str());
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200278 const auto schemaSet = set->schema();
Jan Kundrát4030b772018-08-23 15:54:56 +0200279 for (auto it = (*schemaSet.begin())->child(); it; it = it->next()) {
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200280 nodes.push_back(it);
Václav Kubernát0d4db442018-07-18 17:18:43 +0200281 }
282 }
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200283
284 for (const auto node : nodes) {
285 if (node->module()->name() == "ietf-yang-library"sv)
Václav Kubernát89728d82018-09-13 16:28:28 +0200286 continue;
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200287 if (recursion == Recursion::Recursive) {
288 for (auto it : node->tree_dfs()) {
289 res.insert(it->path(LYS_PATH_FIRST_PREFIX));
290 }
291 } else {
Václav Kubernát4f77a252019-02-19 16:51:30 +0100292 std::string toInsert;
293 if (path.m_nodes.empty() || path.m_nodes.front().m_prefix.get().m_name != node->module()->name()) {
294 toInsert += node->module()->name();
295 toInsert += ":";
296 }
297 toInsert += node->name();
298 res.insert(toInsert);
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200299 }
300 }
301
Václav Kubernát0d4db442018-07-18 17:18:43 +0200302 return res;
303}
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200304
305void YangSchema::loadModule(const std::string& moduleName)
306{
307 m_context->load_module(moduleName.c_str());
308}
309
310void YangSchema::registerModuleCallback(const std::function<std::string(const char*, const char*, const char*)>& clb)
311{
312 auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) {
313 (void)submod_revision;
314 auto moduleSource = clb(mod_name, mod_revision, submod_name);
315 if (moduleSource.empty()) {
316 return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, nullptr};
317 }
318 return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, strdup(moduleSource.c_str())};
319 };
320
321 auto deleter = [](void* data) {
322 free(data);
323 };
324 m_context->add_missing_module_callback(lambda, deleter);
325}