| /* |
| * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/ |
| * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/ |
| * |
| * Written by Václav Kubernát <kubervac@fit.cvut.cz> |
| * |
| */ |
| |
| #include <libyang-cpp/Enum.hpp> |
| #include <libyang-cpp/Utils.hpp> |
| #include <string_view> |
| #include "UniqueResource.hpp" |
| #include "utils.hpp" |
| #include "yang_schema.hpp" |
| |
| class YangLoadError : public std::runtime_error { |
| public: |
| using std::runtime_error::runtime_error; |
| ~YangLoadError() override = default; |
| }; |
| |
| class UnsupportedYangTypeException : public std::runtime_error { |
| public: |
| using std::runtime_error::runtime_error; |
| ~UnsupportedYangTypeException() override = default; |
| }; |
| |
| class InvalidSchemaQueryException : public std::runtime_error { |
| public: |
| using std::runtime_error::runtime_error; |
| ~InvalidSchemaQueryException() override = default; |
| }; |
| |
| YangSchema::YangSchema() |
| : m_context(nullptr, libyang::ContextOptions::DisableSearchDirs | libyang::ContextOptions::SetPrivParsed) |
| { |
| } |
| |
| YangSchema::YangSchema(libyang::Context lyCtx) |
| : m_context(lyCtx) |
| { |
| } |
| |
| YangSchema::~YangSchema() = default; |
| |
| void YangSchema::addSchemaString(const char* schema) |
| { |
| m_context.parseModuleMem(schema, libyang::SchemaFormat::YANG); |
| } |
| |
| void YangSchema::addSchemaDirectory(const char* directoryName) |
| { |
| m_context.setSearchDir(directoryName); |
| } |
| |
| void YangSchema::addSchemaFile(const char* filename) |
| { |
| m_context.parseModulePath(filename, libyang::SchemaFormat::YANG); |
| } |
| |
| bool YangSchema::isModule(const std::string& name) const |
| { |
| return m_context.getModuleImplemented(name.c_str()).has_value(); |
| } |
| |
| bool YangSchema::listHasKey(const schemaPath_& listPath, const std::string& key) const |
| { |
| const auto keys = listKeys(listPath); |
| return keys.find(key) != keys.end(); |
| } |
| |
| bool YangSchema::leafIsKey(const std::string& leafPath) const |
| { |
| auto node = getSchemaNode(leafPath); |
| if (!node || node->nodeType() != libyang::NodeType::Leaf) { |
| return false; |
| } |
| |
| return node->asLeaf().isKey(); |
| } |
| |
| std::optional<libyang::SchemaNode> YangSchema::impl_getSchemaNode(const std::string& node) const |
| { |
| // libyang::Context::findPath throws an exception, when no matching schema node is found. This exception has the |
| // ValidationFailure error code. We will catch that exception (and rethrow if it's not the correct error code. |
| // |
| // Also, we need to use findPath twice if we're trying to find output nodes. |
| try { |
| return m_context.findPath(node.c_str()); |
| } catch (libyang::ErrorWithCode& err) { |
| if (err.code() != libyang::ErrorCode::ValidationFailure) { |
| throw; |
| } |
| } |
| try { |
| return m_context.findPath(node.c_str(), libyang::OutputNodes::Yes); |
| } catch (libyang::ErrorWithCode& err) { |
| if (err.code() != libyang::ErrorCode::ValidationFailure) { |
| throw; |
| } |
| } |
| |
| // We didn't find a matching node. |
| return std::nullopt; |
| } |
| |
| |
| std::optional<libyang::SchemaNode> YangSchema::getSchemaNode(const std::string& node) const |
| { |
| return impl_getSchemaNode(node); |
| } |
| |
| std::optional<libyang::SchemaNode> YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const |
| { |
| std::string absPath = joinPaths(pathToSchemaString(location, Prefixes::Always), fullNodeName(location, node)); |
| |
| return impl_getSchemaNode(absPath); |
| } |
| |
| std::optional<libyang::SchemaNode> YangSchema::getSchemaNode(const schemaPath_& listPath) const |
| { |
| std::string absPath = pathToSchemaString(listPath, Prefixes::Always); |
| return impl_getSchemaNode(absPath); |
| } |
| |
| const std::set<std::string> YangSchema::listKeys(const schemaPath_& listPath) const |
| { |
| auto node = getSchemaNode(listPath); |
| if (node->nodeType() != libyang::NodeType::List) { |
| return {}; |
| } |
| |
| std::set<std::string> keys; |
| auto keysVec = node->asList().keys(); |
| |
| std::transform(keysVec.begin(), keysVec.end(), std::inserter(keys, keys.begin()), [](const auto& it) { return std::string{it.name()}; }); |
| return keys; |
| } |
| |
| std::set<enum_> enumValues(const libyang::Type& type) |
| { |
| auto enums = type.asEnum().items(); |
| std::set<enum_> enumSet; |
| std::transform(enums.begin(), enums.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return enum_{std::string{it.name}}; }); |
| return enumSet; |
| } |
| |
| std::set<identityRef_> validIdentities(const libyang::Type& type) |
| { |
| std::set<identityRef_> identSet; |
| |
| std::function<void(const std::vector<libyang::Identity>&)> impl = [&identSet, &impl] (const std::vector<libyang::Identity>& idents) { |
| if (idents.empty()) { |
| return; |
| } |
| |
| for (const auto& ident : idents) { |
| identSet.emplace(std::string{ident.module().name()}, std::string{ident.name()}); |
| impl(ident.derived()); |
| } |
| }; |
| |
| for (const auto& base : type.asIdentityRef().bases()) { |
| impl(base.derived()); |
| } |
| |
| return identSet; |
| } |
| |
| std::string leafrefPath(const libyang::Type& type) |
| { |
| return std::string{type.asLeafRef().path()}; |
| } |
| |
| template <typename NodeType> |
| yang::TypeInfo YangSchema::impl_leafType(const NodeType& node) const |
| { |
| using namespace std::string_literals; |
| auto leaf = std::make_shared<NodeType>(node); |
| auto leafUnits = leaf->units(); |
| std::function<yang::TypeInfo(const libyang::Type&)> resolveType; |
| resolveType = [&resolveType, leaf, leafUnits](const libyang::Type& type) -> yang::TypeInfo { |
| yang::LeafDataType resType; |
| switch (type.base()) { |
| case libyang::LeafBaseType::String: |
| resType.emplace<yang::String>(); |
| break; |
| case libyang::LeafBaseType::Dec64: |
| resType.emplace<yang::Decimal>(); |
| break; |
| case libyang::LeafBaseType::Bool: |
| resType.emplace<yang::Bool>(); |
| break; |
| case libyang::LeafBaseType::Int8: |
| resType.emplace<yang::Int8>(); |
| break; |
| case libyang::LeafBaseType::Int16: |
| resType.emplace<yang::Int16>(); |
| break; |
| case libyang::LeafBaseType::Int32: |
| resType.emplace<yang::Int32>(); |
| break; |
| case libyang::LeafBaseType::Int64: |
| resType.emplace<yang::Int64>(); |
| break; |
| case libyang::LeafBaseType::Uint8: |
| resType.emplace<yang::Uint8>(); |
| break; |
| case libyang::LeafBaseType::Uint16: |
| resType.emplace<yang::Uint16>(); |
| break; |
| case libyang::LeafBaseType::Uint32: |
| resType.emplace<yang::Uint32>(); |
| break; |
| case libyang::LeafBaseType::Uint64: |
| resType.emplace<yang::Uint64>(); |
| break; |
| case libyang::LeafBaseType::Binary: |
| resType.emplace<yang::Binary>(); |
| break; |
| case libyang::LeafBaseType::Empty: |
| resType.emplace<yang::Empty>(); |
| break; |
| case libyang::LeafBaseType::Enum: |
| resType.emplace<yang::Enum>(enumValues(type)); |
| break; |
| case libyang::LeafBaseType::IdentityRef: |
| resType.emplace<yang::IdentityRef>(validIdentities(type)); |
| break; |
| case libyang::LeafBaseType::Leafref: |
| resType.emplace<yang::LeafRef>(::leafrefPath(type), std::make_unique<yang::TypeInfo>(resolveType(type.asLeafRef().resolvedType()))); |
| break; |
| case libyang::LeafBaseType::Bits: { |
| auto resBits = yang::Bits{}; |
| for (const auto& bit : type.asBits().items()) { |
| resBits.m_allowedValues.emplace(std::string{bit.name}); |
| } |
| resType.emplace<yang::Bits>(std::move(resBits)); |
| break; |
| } |
| case libyang::LeafBaseType::Union: { |
| auto resUnion = yang::Union{}; |
| for (auto unionType : type.asUnion().types()) { |
| resUnion.m_unionTypes.emplace_back(resolveType(unionType)); |
| } |
| resType.emplace<yang::Union>(std::move(resUnion)); |
| break; |
| } |
| default: |
| using namespace std::string_literals; |
| throw UnsupportedYangTypeException("the type of "s + |
| std::string{leaf->name()} + |
| " is not supported: " + |
| std::to_string(std::underlying_type_t<libyang::LeafBaseType>(leaf->valueType().base()))); |
| } |
| std::optional<std::string_view> typeDesc; |
| |
| try { |
| typeDesc = type.description(); |
| } catch (libyang::ParsedInfoUnavailable&) { |
| // libyang context doesn't have the parsed info. |
| } |
| |
| return yang::TypeInfo(resType, std::optional<std::string>{leafUnits}, std::optional<std::string>{typeDesc}); |
| }; |
| return resolveType(leaf->valueType()); |
| } |
| |
| yang::TypeInfo YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const |
| { |
| auto lyNode = getSchemaNode(location, node); |
| switch (lyNode->nodeType()) { |
| case libyang::NodeType::Leaf: |
| return impl_leafType(lyNode->asLeaf()); |
| case libyang::NodeType::Leaflist: |
| return impl_leafType(lyNode->asLeafList()); |
| default: |
| throw std::logic_error("YangSchema::leafType: type must be leaf or leaflist"); |
| } |
| } |
| |
| yang::TypeInfo YangSchema::leafType(const std::string& path) const |
| { |
| auto lyNode = getSchemaNode(path); |
| switch (lyNode->nodeType()) { |
| case libyang::NodeType::Leaf: |
| return impl_leafType(lyNode->asLeaf()); |
| case libyang::NodeType::Leaflist: |
| return impl_leafType(lyNode->asLeafList()); |
| default: |
| throw std::logic_error("YangSchema::leafType: type must be leaf or leaflist"); |
| } |
| } |
| |
| std::optional<std::string> YangSchema::leafTypeName(const std::string& path) const |
| { |
| auto leaf = getSchemaNode(path)->asLeaf(); |
| try { |
| return std::string{leaf.valueType().name()}; |
| } catch (libyang::ParsedInfoUnavailable&) { |
| return std::nullopt; |
| } |
| } |
| |
| std::string YangSchema::leafrefPath(const std::string& leafrefPath) const |
| { |
| using namespace std::string_literals; |
| return ::leafrefPath(getSchemaNode(leafrefPath)->asLeaf().valueType()); |
| } |
| |
| std::set<std::string> YangSchema::modules() const |
| { |
| const auto& modules = m_context.modules(); |
| |
| std::set<std::string> res; |
| std::transform(modules.begin(), modules.end(), std::inserter(res, res.end()), [](const auto module) { return std::string{module.name()}; }); |
| return res; |
| } |
| |
| std::set<ModuleNodePair> YangSchema::availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const |
| { |
| using namespace std::string_view_literals; |
| std::set<ModuleNodePair> res; |
| std::vector<libyang::ChildInstanstiables> nodeCollections; |
| std::string topLevelModule; |
| |
| if (path.type() == typeid(module_)) { |
| nodeCollections.emplace_back(m_context.getModule(boost::get<module_>(path).m_name.c_str())->childInstantiables()); |
| } else { |
| auto schemaPath = anyPathToSchemaPath(path); |
| if (schemaPath.m_nodes.empty()) { |
| for (const auto& module : m_context.modules()) { |
| if (module.implemented()) { |
| nodeCollections.emplace_back(module.childInstantiables()); |
| } |
| } |
| } else { |
| const auto pathString = pathToSchemaString(schemaPath, Prefixes::Always); |
| const auto node = getSchemaNode(pathString); |
| nodeCollections.emplace_back(node->childInstantiables()); |
| topLevelModule = schemaPath.m_nodes.begin()->m_prefix->m_name; |
| } |
| } |
| |
| for (const auto& coll : nodeCollections) { |
| for (const auto& node : coll) { |
| if (node.module().name() == "ietf-yang-library"sv) { |
| continue; |
| } |
| |
| if (node.module().name() == "ietf-yang-schema-mount"sv) { |
| continue; |
| } |
| |
| if (recursion == Recursion::Recursive) { |
| for (auto it : node.childrenDfs()) { |
| res.insert(ModuleNodePair(boost::none, it.path())); |
| } |
| } else { |
| ModuleNodePair toInsert; |
| if (topLevelModule.empty() || topLevelModule != node.module().name()) { |
| toInsert.first = std::string{node.module().name()}; |
| } |
| toInsert.second = node.name(); |
| res.insert(toInsert); |
| } |
| } |
| } |
| |
| return res; |
| } |
| |
| void YangSchema::loadModule(const std::string& moduleName) |
| { |
| m_context.loadModule(moduleName.c_str()); |
| } |
| |
| void YangSchema::setEnabledFeatures(const std::string& moduleName, const std::vector<std::string>& features) |
| { |
| using namespace std::string_literals; |
| auto module = getYangModule(moduleName); |
| if (!module) { |
| throw std::runtime_error("Module \""s + moduleName + "\" doesn't exist."); |
| } |
| try { |
| module->setImplemented(features); |
| } catch (libyang::ErrorWithCode&) { |
| throw std::runtime_error("Can't enable features for module \"" + moduleName + "\"."); |
| } |
| } |
| |
| void YangSchema::registerModuleCallback(const std::function<std::string(const char*, const char*, const char*, const char*)>& clb) |
| { |
| auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) -> std::optional<libyang::ModuleInfo> { |
| (void)submod_revision; |
| auto moduleSource = clb(mod_name, mod_revision, submod_name, submod_revision); |
| if (moduleSource.empty()) { |
| return std::nullopt; |
| } |
| return libyang::ModuleInfo { |
| .data = moduleSource.c_str(), |
| .format = libyang::SchemaFormat::YANG |
| |
| }; |
| }; |
| |
| m_context.registerModuleCallback(lambda); |
| } |
| |
| libyang::CreatedNodes YangSchema::dataNodeFromPath(const std::string& path, const std::optional<const std::string> value) const |
| { |
| auto options = [this, &path, &value] { |
| // If we're creating a node without a value and it's not the "empty" type, then we also need the Opaque flag. |
| auto schema = getSchemaNode(path); |
| if (schema->nodeType() == libyang::NodeType::Leaf && |
| schema->asLeaf().valueType().base() != libyang::LeafBaseType::Empty && |
| !value) { |
| return std::optional<libyang::CreationOptions>{libyang::CreationOptions::Opaque}; |
| } |
| |
| return std::optional<libyang::CreationOptions>{}; |
| }(); |
| return m_context.newPath2(path.c_str(), value ? value->c_str() : nullptr, options); |
| } |
| |
| std::optional<libyang::Module> YangSchema::getYangModule(const std::string& name) |
| { |
| return m_context.getModuleImplemented(name.c_str()); |
| } |
| |
| namespace { |
| yang::NodeTypes impl_nodeType(const libyang::SchemaNode& node) |
| { |
| switch (node.nodeType()) { |
| case libyang::NodeType::Container: |
| return node.asContainer().isPresence() ? yang::NodeTypes::PresenceContainer : yang::NodeTypes::Container; |
| case libyang::NodeType::Leaf: |
| return yang::NodeTypes::Leaf; |
| case libyang::NodeType::List: |
| return yang::NodeTypes::List; |
| case libyang::NodeType::RPC: |
| return yang::NodeTypes::Rpc; |
| case libyang::NodeType::Action: |
| return yang::NodeTypes::Action; |
| case libyang::NodeType::Notification: |
| return yang::NodeTypes::Notification; |
| case libyang::NodeType::AnyXML: |
| return yang::NodeTypes::AnyXml; |
| case libyang::NodeType::Leaflist: |
| return yang::NodeTypes::LeafList; |
| default: |
| throw InvalidNodeException(); // FIXME: Implement all types. |
| } |
| } |
| } |
| |
| yang::NodeTypes YangSchema::nodeType(const schemaPath_& location, const ModuleNodePair& node) const |
| { |
| return impl_nodeType(*getSchemaNode(location, node)); |
| } |
| |
| yang::NodeTypes YangSchema::nodeType(const std::string& path) const |
| { |
| return impl_nodeType(*getSchemaNode(path)); |
| } |
| |
| std::optional<std::string> YangSchema::description(const std::string& path) const |
| { |
| auto desc = getSchemaNode(path.c_str())->description(); |
| return desc ? std::optional<std::string>{desc} : std::nullopt; |
| |
| } |
| |
| yang::Status YangSchema::status(const std::string& location) const |
| { |
| auto node = getSchemaNode(location.c_str()); |
| switch (node->status()) { |
| case libyang::Status::Deprecated: |
| return yang::Status::Deprecated; |
| case libyang::Status::Obsolete: |
| return yang::Status::Obsolete; |
| case libyang::Status::Current: |
| return yang::Status::Current; |
| } |
| |
| __builtin_unreachable(); |
| } |
| |
| bool YangSchema::hasInputNodes(const std::string& path) const |
| { |
| auto node = getSchemaNode(path.c_str()); |
| if (auto type = node->nodeType(); type != libyang::NodeType::Action && type != libyang::NodeType::RPC) { |
| throw std::logic_error("StaticSchema::hasInputNodes called with non-RPC/action path"); |
| } |
| |
| // The first child gives the /input node and then I check whether it has a child. |
| return node->child()->child().has_value(); |
| } |
| |
| bool YangSchema::isConfig(const std::string& path) const |
| { |
| auto node = getSchemaNode(path.c_str()); |
| try { |
| if (node->config() == libyang::Config::True) { |
| return true; |
| } |
| } catch (libyang::Error&) { |
| // For non-data nodes (like `rpc`), the config value can't be retrieved. In this case, we'll just default to |
| // "false". |
| } |
| |
| return false; |
| } |
| |
| std::optional<std::string> YangSchema::defaultValue(const std::string& leafPath) const |
| { |
| return std::optional<std::string>{getSchemaNode(leafPath)->asLeaf().defaultValueStr()}; |
| } |
| |
| std::string YangSchema::dataPathToSchemaPath(const std::string& path) |
| { |
| return std::string{getSchemaNode(path)->path()}; |
| } |