Migrate to libyang2

libnetconf2: getSchema and getConfig were no longer used in netconf-cli,
so I deleted them. They can get readded once the bindings get split into
a separate project.

sysrepo_access: Some sr_val stuff was removed.

YangSchema: type descriptions are not available
            availableNodes returns only input nodes for RPC nodes
            impl_getSchemaNode: no longer disables error printing

libyang: No longer supports leafrefs without the leaf it points to.

Depends-on: https://cesnet-gerrit-czechlight/c/CzechLight/dependencies/+/5171
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/5171
Change-Id: Ie49381a003a61a7bb028be7b2fa1d9d926ac4e58
diff --git a/src/netconf-client.cpp b/src/netconf-client.cpp
index 4638bcb..0685db6 100644
--- a/src/netconf-client.cpp
+++ b/src/netconf-client.cpp
@@ -7,7 +7,8 @@
 */
 
 #include <cstring>
-#include <libyang/Tree_Data.hpp>
+#include <libyang-cpp/Context.hpp>
+#include <libyang-cpp/DataNode.hpp>
 #include <mutex>
 extern "C" {
 #include <nc_client.h>
@@ -58,15 +59,6 @@
 
 static std::mutex clientOptions;
 
-inline void custom_free_nc_reply_data(nc_reply_data* reply)
-{
-    nc_reply_free(reinterpret_cast<nc_reply*>(reply));
-}
-inline void custom_free_nc_reply_error(nc_reply_error* reply)
-{
-    nc_reply_free(reinterpret_cast<nc_reply*>(reply));
-}
-
 char* ssh_auth_interactive_cb(const char* auth_name, const char* instruction, const char* prompt, int echo, void* priv)
 {
     const auto cb = static_cast<const client::KbdInteractiveCb*>(priv);
@@ -74,24 +66,19 @@
     return ::strdup(res.c_str());
 }
 
-template <typename Type> using deleter_type_for = void (*)(Type*);
-template <typename Type> deleter_type_for<Type> const deleter_for;
-
-template <> const auto deleter_for<nc_rpc> = nc_rpc_free;
-template <> const auto deleter_for<nc_reply> = nc_reply_free;
-template <> const auto deleter_for<nc_reply_data> = custom_free_nc_reply_data;
-template <> const auto deleter_for<nc_reply_error> = custom_free_nc_reply_error;
-
-template <typename T>
-using unique_ptr_for = std::unique_ptr<T, decltype(deleter_for<T>)>;
-
-template <typename T>
-auto guarded(T* ptr)
+auto guarded(nc_rpc* ptr)
 {
-    return unique_ptr_for<T>(ptr, deleter_for<T>);
+    return std::unique_ptr<nc_rpc, decltype(&nc_rpc_free)>(ptr, nc_rpc_free);
 }
 
-unique_ptr_for<struct nc_reply> do_rpc(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
+namespace {
+const auto getData_path = "/ietf-netconf-nmda:get-data/data";
+const auto get_path = "/ietf-netconf:get/data";
+}
+
+using managed_rpc = std::invoke_result_t<decltype(guarded), nc_rpc*>;
+
+std::optional<libyang::DataNode> do_rpc(client::Session* session, managed_rpc&& rpc, const char* dataIdentifier)
 {
     uint64_t msgid;
     NC_MSG_TYPE msgtype;
@@ -104,11 +91,11 @@
         throw std::runtime_error{"Timeout sending an RPC"};
     }
 
-    struct nc_reply* raw_reply;
+    lyd_node* raw_reply;
+    lyd_node* envp;
     while (true) {
-        msgtype = nc_recv_reply(session->session_internal(), rpc.get(), msgid, 20000, LYD_OPT_DESTRUCT | LYD_OPT_NOSIBLINGS, &raw_reply);
-        auto reply = guarded(raw_reply);
-        raw_reply = nullptr;
+        msgtype = nc_recv_reply(session->session_internal(), rpc.get(), msgid, 20000, &envp, &raw_reply);
+        auto replyInfo = libyang::wrapRawNode(envp);
 
         switch (msgtype) {
         case NC_MSG_ERROR:
@@ -120,76 +107,75 @@
         case NC_MSG_NOTIF:
             continue;
         default:
-            return reply;
+            if (!raw_reply) { // <ok> reply, or empty data node, or error
+                std::string msg;
+                for (const auto& child : replyInfo.child()->siblings()) {
+                    if (child.asOpaque().name().name == "rpc-error") {
+                        for (const auto& error : child.childrenDfs()) {
+                            if (error.asOpaque().name().name == "error-message") {
+                                msg += "Error: ";
+                                msg += error.asOpaque().value();
+                            }
+
+                            if (error.asOpaque().name().name == "error-path") {
+                                msg += "Path: ";
+                                msg += error.asOpaque().value();
+                            }
+
+                            if (error.asOpaque().name().name == "error-type") {
+                                msg += "Type: ";
+                                msg += error.asOpaque().value();
+                            }
+
+                            if (error.asOpaque().name().name == "error-tag") {
+                                msg += "Tag: ";
+                                msg += error.asOpaque().value();
+                            }
+
+                            if (error.asOpaque().name().name == "error-app-tag") {
+                                msg += "App-tag: ";
+                                msg += error.asOpaque().value();
+                            }
+                        }
+
+                        msg += "\n";
+                    }
+                }
+
+                if (!msg.empty()) {
+                    throw client::ReportedError{msg};
+                }
+
+                return std::nullopt;
+            }
+            auto wrapped = libyang::wrapRawNode(raw_reply);
+
+            // If we have a dataIdentifier, then we'll need to look for it.
+            // Some operations don't have that, and then the result data are just the wrapped node.
+            if (!dataIdentifier) {
+                return wrapped;
+            }
+
+            auto anydataValue = wrapped.findPath(dataIdentifier, libyang::OutputNodes::Yes)->asAny().releaseValue();
+
+            // If there's no anydata value, then that means we get empty (but valid) data.
+            if (!anydataValue) {
+                return std::nullopt;
+            }
+
+            return std::get<libyang::DataNode>(*anydataValue);
         }
     }
     __builtin_unreachable();
 }
 
-client::ReportedError make_error(unique_ptr_for<struct nc_reply>&& reply)
+void do_rpc_ok(client::Session* session, managed_rpc&& rpc)
 {
-    if (reply->type != NC_RPL_ERROR) {
-        throw std::logic_error{"Cannot extract an error from a non-error server reply"};
-    }
-
-    auto errorReply = guarded(reinterpret_cast<struct nc_reply_error*>(reply.release()));
-
-    // TODO: capture the error details, not just that human-readable string
-    std::ostringstream ss;
-    ss << "Server error:" << std::endl;
-    for (uint32_t i = 0; i < errorReply->count; ++i) {
-        const auto e = errorReply->err[i];
-        ss << " #" << i << ": " << e.message;
-        if (e.path) {
-            ss << " (XPath " << e.path << ")";
-        }
-        ss << std::endl;
-    }
-    return client::ReportedError{ss.str()};
-}
-
-std::optional<unique_ptr_for<struct nc_reply_data>> do_rpc_data_or_ok(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
-    auto x = do_rpc(session, std::move(rpc));
-
-    switch (x->type) {
-    case NC_RPL_DATA:
-        return guarded(reinterpret_cast<struct nc_reply_data*>(x.release()));
-    case NC_RPL_OK:
-        return std::nullopt;
-    case NC_RPL_ERROR:
-        throw make_error(std::move(x));
-    default:
-        throw std::runtime_error{"Unhandled reply type"};
-    }
-}
-
-unique_ptr_for<struct nc_reply_data> do_rpc_data(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
-    auto x = do_rpc_data_or_ok(session, std::move(rpc));
-    if (!x) {
-        throw std::runtime_error{"Received OK instead of a data reply"};
-    }
-    return std::move(*x);
-}
-
-void do_rpc_ok(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
-    auto x = do_rpc_data_or_ok(session, std::move(rpc));
+    auto x = do_rpc(session, std::move(rpc), nullptr);
     if (x) {
         throw std::runtime_error{"Unexpected DATA reply"};
     }
 }
-
-std::shared_ptr<libyang::Data_Node> do_get(client::Session* session, unique_ptr_for<nc_rpc> rpc)
-{
-    auto reply = impl::do_rpc_data(session, std::move(rpc));
-    auto dataNode = libyang::create_new_Data_Node(reply->data);
-    // TODO: can we do without copying?
-    // If we just default-construct a new node (or use the create_new_Data_Node) and then set reply->data to nullptr,
-    // there are mem leaks and even libnetconf2 complains loudly.
-    return dataNode ? dataNode->dup_withsiblings(1) : nullptr;
-}
 }
 
 namespace client {
@@ -210,9 +196,9 @@
     return m_session;
 }
 
-libyang::S_Context Session::libyangContext()
+libyang::Context Session::libyangContext()
 {
-    return std::make_shared<libyang::Context>(nc_session_get_ctx(m_session), nullptr);
+    return libyang::createUnmanagedContext(const_cast<ly_ctx*>(nc_session_get_ctx(m_session)), nullptr);
 }
 
 Session::Session(struct nc_session* session)
@@ -298,13 +284,13 @@
     return res;
 }
 
-std::shared_ptr<libyang::Data_Node> Session::get(const std::optional<std::string>& filter)
+std::optional<libyang::DataNode> Session::get(const std::optional<std::string>& filter)
 {
     auto rpc = impl::guarded(nc_rpc_get(filter ? filter->c_str() : nullptr, NC_WD_ALL, NC_PARAMTYPE_CONST));
     if (!rpc) {
         throw std::runtime_error("Cannot create get RPC");
     }
-    return impl::do_get(this, std::move(rpc));
+    return impl::do_rpc(this, std::move(rpc), impl::get_path);
 }
 
 const char* datastoreToString(NmdaDatastore datastore)
@@ -322,13 +308,13 @@
     __builtin_unreachable();
 }
 
-std::shared_ptr<libyang::Data_Node> Session::getData(const NmdaDatastore datastore, const std::optional<std::string>& filter)
+std::optional<libyang::DataNode> Session::getData(const NmdaDatastore datastore, const std::optional<std::string>& filter)
 {
     auto rpc = impl::guarded(nc_rpc_getdata(datastoreToString(datastore), filter ? filter->c_str() : nullptr, nullptr, nullptr, 0, 0, 0, 0, NC_WD_ALL, NC_PARAMTYPE_CONST));
     if (!rpc) {
         throw std::runtime_error("Cannot create get RPC");
     }
-    return impl::do_get(this, std::move(rpc));
+    return impl::do_rpc(this, std::move(rpc), impl::getData_path);
 }
 
 void Session::editData(const NmdaDatastore datastore, const std::string& data)
@@ -340,34 +326,6 @@
     return impl::do_rpc_ok(this, std::move(rpc));
 }
 
-std::string Session::getSchema(const std::string_view identifier, const std::optional<std::string_view> version)
-{
-    auto rpc = impl::guarded(nc_rpc_getschema(identifier.data(), version ? version.value().data() : nullptr, nullptr, NC_PARAMTYPE_CONST));
-    if (!rpc) {
-        throw std::runtime_error("Cannot create get-schema RPC");
-    }
-    auto reply = impl::do_rpc_data(this, std::move(rpc));
-
-    auto node = libyang::create_new_Data_Node(reply->data)->dup_withsiblings(1);
-    auto set = node->find_path("data");
-    for (auto node : set->data()) {
-        if (node->schema()->nodetype() == LYS_ANYXML) {
-            libyang::Data_Node_Anydata anydata(node);
-            return anydata.value().str;
-        }
-    }
-    throw std::runtime_error("Got a reply to get-schema, but it didn't contain the required schema");
-}
-
-std::shared_ptr<libyang::Data_Node> Session::getConfig(const NC_DATASTORE datastore, const std::optional<const std::string> filter)
-{
-    auto rpc = impl::guarded(nc_rpc_getconfig(datastore, filter ? filter->c_str() : nullptr, NC_WD_ALL, NC_PARAMTYPE_CONST));
-    if (!rpc) {
-        throw std::runtime_error("Cannot create get-config RPC");
-    }
-    return impl::do_get(this, std::move(rpc));
-}
-
 void Session::editConfig(const NC_DATASTORE datastore,
                          const NC_RPC_EDIT_DFLTOP defaultOperation,
                          const NC_RPC_EDIT_TESTOPT testOption,
@@ -408,19 +366,14 @@
     impl::do_rpc_ok(this, std::move(rpc));
 }
 
-std::shared_ptr<libyang::Data_Node> Session::rpc_or_action(const std::string& xmlData)
+std::optional<libyang::DataNode> Session::rpc_or_action(const std::string& xmlData)
 {
     auto rpc = impl::guarded(nc_rpc_act_generic_xml(xmlData.c_str(), NC_PARAMTYPE_CONST));
     if (!rpc) {
         throw std::runtime_error("Cannot create generic RPC");
     }
-    auto reply = impl::do_rpc_data_or_ok(this, std::move(rpc));
-    if (reply) {
-        auto dataNode = libyang::create_new_Data_Node((*reply)->data);
-        return dataNode->dup_withsiblings(1);
-    } else {
-        return nullptr;
-    }
+
+    return impl::do_rpc(this, std::move(rpc), nullptr);
 }
 
 void Session::copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination)