blob: 6cd7024fcdf63f79a15757a598ed07129584af39 [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>
Václav Kubernátf2b91e02019-04-11 15:36:48 +020012#include "UniqueResource.h"
Václav Kubernát0d4db442018-07-18 17:18:43 +020013#include "utils.hpp"
14#include "yang_schema.hpp"
15
16class YangLoadError : public std::runtime_error {
17public:
18 using std::runtime_error::runtime_error;
19 ~YangLoadError() override = default;
20};
21
22class UnsupportedYangTypeException : public std::runtime_error {
23public:
24 using std::runtime_error::runtime_error;
25 ~UnsupportedYangTypeException() override = default;
26};
27
28class InvalidSchemaQueryException : public std::runtime_error {
29public:
30 using std::runtime_error::runtime_error;
31 ~InvalidSchemaQueryException() override = default;
32};
33
Václav Kubernát2eaceb82018-10-08 19:56:30 +020034template <typename T>
35std::string pathToYangAbsSchemPath(const T& path)
Václav Kubernát0d4db442018-07-18 17:18:43 +020036{
37 std::string res = "/";
38 std::string currentModule;
39
40 for (const auto& it : path.m_nodes) {
41 const auto name = nodeToSchemaString(it);
42
43 if (it.m_suffix.type() == typeid(module_)) {
44 currentModule = name;
45 continue;
46 } else {
47 res += currentModule + ":";
48 res += name + "/";
49 }
50 }
51
52 return res;
53}
54
55YangSchema::YangSchema()
Václav Kubernáta6c5fff2018-09-07 15:16:25 +020056 : 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 +020057{
Václav Kubernát0d4db442018-07-18 17:18:43 +020058}
59
60YangSchema::~YangSchema() = default;
61
62void YangSchema::addSchemaString(const char* schema)
63{
64 if (!m_context->parse_module_mem(schema, LYS_IN_YANG)) {
65 throw YangLoadError("Couldn't load schema");
66 }
67}
68
69void YangSchema::addSchemaDirectory(const char* directoryName)
70{
71 if (m_context->set_searchdir(directoryName)) {
72 throw YangLoadError("Couldn't add schema search directory");
73 }
74}
75
76void YangSchema::addSchemaFile(const char* filename)
77{
78 if (!m_context->parse_module_path(filename, LYS_IN_YANG)) {
79 throw YangLoadError("Couldn't load schema");
80 }
81}
82
Václav Kubernát2eaceb82018-10-08 19:56:30 +020083bool YangSchema::isModule(const schemaPath_&, const std::string& name) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020084{
85 const auto set = modules();
86 return set.find(name) != set.end();
87}
88
Václav Kubernát2eaceb82018-10-08 19:56:30 +020089bool YangSchema::isContainer(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020090{
91 const auto schemaNode = getSchemaNode(location, node);
92 return schemaNode && schemaNode->nodetype() == LYS_CONTAINER;
93}
94
Václav Kubernát2eaceb82018-10-08 19:56:30 +020095bool YangSchema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +020096{
97 const auto schemaNode = getSchemaNode(location, node);
98 return schemaNode && schemaNode->nodetype() == LYS_LEAF;
99}
100
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200101bool YangSchema::isList(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200102{
103 const auto schemaNode = getSchemaNode(location, node);
104 return schemaNode && schemaNode->nodetype() == LYS_LIST;
105}
106
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200107bool YangSchema::isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200108{
109 if (!isContainer(location, node))
110 return false;
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200111 return libyang::Schema_Node_Container(getSchemaNode(location, node)).presence();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200112}
113
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200114bool YangSchema::leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200115{
Václav Kubernát989b5de2019-02-20 16:28:35 +0100116 auto enums = enumValues(location, node);
117
118 return std::any_of(enums.begin(), enums.end(), [=](const auto& x) { return x == value; });
119}
120
121const std::set<std::string> YangSchema::enumValues(const schemaPath_& location, const ModuleNodePair& node) const
122{
Václav Kubernát0d4db442018-07-18 17:18:43 +0200123 if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::Enum)
Václav Kubernát989b5de2019-02-20 16:28:35 +0100124 return {};
Václav Kubernát0d4db442018-07-18 17:18:43 +0200125
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200126 libyang::Schema_Node_Leaf leaf(getSchemaNode(location, node));
Václav Kubernát6a713d62018-10-03 18:47:34 +0200127 auto type = leaf.type();
128 auto enm = type->info()->enums()->enm();
129 // The enum can be a derived type and enm() only returns values,
130 // if that specific typedef changed the possible values. So we go
131 // up the hierarchy until we find a typedef that defined these values.
132 while (enm.empty()) {
133 type = type->der()->type();
134 enm = type->info()->enums()->enm();
135 }
Václav Kubernát0d4db442018-07-18 17:18:43 +0200136
Václav Kubernát989b5de2019-02-20 16:28:35 +0100137 std::set<std::string> enumSet;
138 std::transform(enm.begin(), enm.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return it->name(); });
139 return enumSet;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200140}
141
Václav Kubernáteeb38842019-03-20 19:46:05 +0100142const std::set<std::string> YangSchema::validIdentities(const schemaPath_& location, const ModuleNodePair& node, const Prefixes prefixes) const
143{
144 if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::IdentityRef)
145 return {};
146
147 std::set<std::string> identSet;
148
149 auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
150 auto insertToSet = [&identSet, prefixes, topLevelModule](auto module, auto name) {
151 std::string stringIdent;
152 if (prefixes == Prefixes::Always || topLevelModule != module) {
153 stringIdent += module;
154 stringIdent += ":";
155 }
156 stringIdent += name;
157 identSet.emplace(stringIdent);
158 };
159
160 auto leaf = std::make_shared<libyang::Schema_Node_Leaf>(getSchemaNode(location, node));
161 auto info = leaf->type()->info();
162 for (auto base : info->ident()->ref()) { // Iterate over all bases
163 insertToSet(base->module()->name(), base->name());
164 // Iterate over derived identities (this is recursive!)
165 for (auto derived : base->der()->schema()) {
166 insertToSet(derived->module()->name(), derived->name());
167 }
168 }
169
170 return identSet;
171}
172
173bool YangSchema::leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const
174{
175 auto identities = validIdentities(location, node, Prefixes::Always);
176
177 auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
178 auto identModule = value.first ? value.first.value() : topLevelModule;
Václav Kubernát1bf704e2019-04-12 13:30:50 +0200179 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 +0100180}
181
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200182bool YangSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200183{
184 if (!isList(location, node))
185 return false;
186 const auto keys = listKeys(location, node);
187 return keys.find(key) != keys.end();
188}
189
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200190libyang::S_Set YangSchema::getNodeSet(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200191{
192 std::string absPath = location.m_nodes.empty() ? "" : "/";
193 absPath += pathToAbsoluteSchemaString(location) + "/" + fullNodeName(location, node);
Václav Kubernátf2b91e02019-04-11 15:36:48 +0200194
195 // If no node is found find_path prints an error message, so we have to
196 // disable logging
197 // https://github.com/CESNET/libyang/issues/753
198 {
199 int oldOptions;
200 auto logBlocker = make_unique_resource(
201 [&oldOptions]() {
202 oldOptions = libyang::set_log_options(0);
203 },
204 [&oldOptions]() {
205 libyang::set_log_options(oldOptions);
206 });
207 return m_context->find_path(absPath.c_str());
208 }
Václav Kubernát0d4db442018-07-18 17:18:43 +0200209}
210
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200211libyang::S_Schema_Node YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200212{
213 const auto set = getNodeSet(location, node);
214 if (!set)
215 return nullptr;
Jan Kundrát4030b772018-08-23 15:54:56 +0200216 const auto& schemaSet = set->schema();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200217 if (set->number() != 1)
218 return nullptr;
Jan Kundrát4030b772018-08-23 15:54:56 +0200219 return *schemaSet.begin();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200220}
221
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200222const std::set<std::string> YangSchema::listKeys(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200223{
224 std::set<std::string> keys;
225 if (!isList(location, node))
226 return keys;
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200227 libyang::Schema_Node_List list(getSchemaNode(location, node));
Jan Kundrát4030b772018-08-23 15:54:56 +0200228 const auto& keysVec = list.keys();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200229
Jan Kundrát4030b772018-08-23 15:54:56 +0200230 std::transform(keysVec.begin(), keysVec.end(), std::inserter(keys, keys.begin()),
Václav Kubernát0d4db442018-07-18 17:18:43 +0200231 [] (const auto& it) {return it->name();});
232 return keys;
233}
234
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200235yang::LeafDataTypes YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200236{
237 using namespace std::string_literals;
238 if (!isLeaf(location, node))
239 throw InvalidSchemaQueryException(fullNodeName(location, node) + " is not a leaf");
240
Jan Kundrát4ed0e9f2018-08-23 16:56:58 +0200241 libyang::Schema_Node_Leaf leaf(getSchemaNode(location, node));
Václav Kubernát0d4db442018-07-18 17:18:43 +0200242 switch (leaf.type()->base()) {
243 case LY_TYPE_STRING:
244 return yang::LeafDataTypes::String;
245 case LY_TYPE_DEC64:
246 return yang::LeafDataTypes::Decimal;
247 case LY_TYPE_BOOL:
248 return yang::LeafDataTypes::Bool;
Ivona Oboňová88c78ca2019-07-02 18:40:07 +0200249 case LY_TYPE_INT8:
250 return yang::LeafDataTypes::Int8;
251 case LY_TYPE_INT16:
252 return yang::LeafDataTypes::Int16;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200253 case LY_TYPE_INT32:
Ivona Oboňová88c78ca2019-07-02 18:40:07 +0200254 return yang::LeafDataTypes::Int32;
255 case LY_TYPE_INT64:
256 return yang::LeafDataTypes::Int64;
257 case LY_TYPE_UINT8:
258 return yang::LeafDataTypes::Uint8;
259 case LY_TYPE_UINT16:
260 return yang::LeafDataTypes::Uint16;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200261 case LY_TYPE_UINT32:
Ivona Oboňová88c78ca2019-07-02 18:40:07 +0200262 return yang::LeafDataTypes::Uint32;
263 case LY_TYPE_UINT64:
264 return yang::LeafDataTypes::Uint64;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200265 case LY_TYPE_ENUM:
266 return yang::LeafDataTypes::Enum;
Václav Kubernátab538992019-03-06 15:30:50 +0100267 case LY_TYPE_BINARY:
268 return yang::LeafDataTypes::Binary;
Václav Kubernáteeb38842019-03-20 19:46:05 +0100269 case LY_TYPE_IDENT:
270 return yang::LeafDataTypes::IdentityRef;
Václav Kubernát0d4db442018-07-18 17:18:43 +0200271 default:
272 throw UnsupportedYangTypeException("the type of "s + fullNodeName(location, node) + " is not supported");
273 }
274}
275
276std::set<std::string> YangSchema::modules() const
277{
Jan Kundrát4030b772018-08-23 15:54:56 +0200278 const auto& modules = m_context->get_module_iter();
Václav Kubernát0d4db442018-07-18 17:18:43 +0200279
280 std::set<std::string> res;
Jan Kundrát4030b772018-08-23 15:54:56 +0200281 std::transform(modules.begin(), modules.end(),
Václav Kubernát0d4db442018-07-18 17:18:43 +0200282 std::inserter(res, res.end()),
283 [] (const auto module) { return module->name(); });
284 return res;
285}
286
Václav Kubernát2eaceb82018-10-08 19:56:30 +0200287std::set<std::string> YangSchema::childNodes(const schemaPath_& path, const Recursion recursion) const
Václav Kubernát0d4db442018-07-18 17:18:43 +0200288{
289 using namespace std::string_view_literals;
290 std::set<std::string> res;
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200291 std::vector<libyang::S_Schema_Node> nodes;
292
Václav Kubernát0d4db442018-07-18 17:18:43 +0200293 if (path.m_nodes.empty()) {
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200294 nodes = m_context->data_instantiables(0);
Václav Kubernát0d4db442018-07-18 17:18:43 +0200295 } else {
296 const auto absolutePath = "/" + pathToAbsoluteSchemaString(path);
297 const auto set = m_context->find_path(absolutePath.c_str());
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200298 const auto schemaSet = set->schema();
Václav Kubernát7d82da72019-04-11 15:16:38 +0200299 nodes = (*schemaSet.begin())->child_instantiables(0);
Václav Kubernát0d4db442018-07-18 17:18:43 +0200300 }
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200301
302 for (const auto node : nodes) {
303 if (node->module()->name() == "ietf-yang-library"sv)
Václav Kubernát89728d82018-09-13 16:28:28 +0200304 continue;
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200305 if (recursion == Recursion::Recursive) {
306 for (auto it : node->tree_dfs()) {
307 res.insert(it->path(LYS_PATH_FIRST_PREFIX));
308 }
309 } else {
Václav Kubernát4f77a252019-02-19 16:51:30 +0100310 std::string toInsert;
311 if (path.m_nodes.empty() || path.m_nodes.front().m_prefix.get().m_name != node->module()->name()) {
312 toInsert += node->module()->name();
313 toInsert += ":";
314 }
315 toInsert += node->name();
316 res.insert(toInsert);
Václav Kubernáte7d4aea2018-09-11 18:15:48 +0200317 }
318 }
319
Václav Kubernát0d4db442018-07-18 17:18:43 +0200320 return res;
321}
Václav Kubernáta6c5fff2018-09-07 15:16:25 +0200322
323void YangSchema::loadModule(const std::string& moduleName)
324{
325 m_context->load_module(moduleName.c_str());
326}
327
328void YangSchema::registerModuleCallback(const std::function<std::string(const char*, const char*, const char*)>& clb)
329{
330 auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) {
331 (void)submod_revision;
332 auto moduleSource = clb(mod_name, mod_revision, submod_name);
333 if (moduleSource.empty()) {
334 return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, nullptr};
335 }
336 return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, strdup(moduleSource.c_str())};
337 };
338
339 auto deleter = [](void* data) {
340 free(data);
341 };
342 m_context->add_missing_module_callback(lambda, deleter);
343}