From 92049a8445e7e32f5f3f1ee3eb98e22f21b94118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Tue, 11 Jun 2024 20:18:33 +0200 Subject: [PATCH] Allow parsing standalone notifications in Context::parseOp Even when the libyang docs is not explicit on that, lyd_parse_op is used for parsing standalone notifications too. For such case, the lyd_node* tree and op are the same, or, they point into the nodes in the same tree. So we should not wrap the pointers separately (this would result in a heap-after-use). Change-Id: Ib94114193a1a9073d9a4c6d15522eb41ba9c425d --- src/Context.cpp | 20 ++++-- tests/data_node.cpp | 144 +++++++++++++++++++++++++++++---------- tests/example_schema.hpp | 6 ++ 3 files changed, 128 insertions(+), 42 deletions(-) diff --git a/src/Context.cpp b/src/Context.cpp index cae5280b..f49b2091 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -187,7 +187,8 @@ std::optional Context::parseData( * * - a NETCONF RPC, * - a NETCONF notification, - * - a RESTCONF notification. + * - a RESTCONF notification, + * - a YANG notification. * * Parsing any of these requires just the schema (which is available through the Context), and the textual payload. * All the other information are encoded in the textual payload as per the standard. @@ -211,14 +212,21 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con switch (opType) { case OperationType::RpcNetconf: case OperationType::NotificationNetconf: - case OperationType::NotificationRestconf: { + case OperationType::NotificationRestconf: + case OperationType::NotificationYang: { lyd_node* op = nullptr; lyd_node* tree = nullptr; auto err = lyd_parse_op(m_ctx.get(), nullptr, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, &op); - ParsedOp res { - .tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt, - .op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt - }; + + ParsedOp res; + res.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt; + + if (opType == OperationType::NotificationYang) { + res.op = op && tree ? std::optional{DataNode(op, res.tree->m_refs)} : std::nullopt; + } else { + res.op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt; + } + throwIfError(err, "Can't parse a standalone rpc/action/notification into operation data tree"); return res; } diff --git a/tests/data_node.cpp b/tests/data_node.cpp index e3350e1d..812eb27f 100644 --- a/tests/data_node.cpp +++ b/tests/data_node.cpp @@ -2062,48 +2062,117 @@ TEST_CASE("Data Node manipulation") ctx.loadModule("ietf-netconf-nmda"); DOCTEST_SUBCASE("notifications") { - std::string payload; - auto opType = libyang::OperationType::DataYang; + DOCTEST_SUBCASE("restconf/netconf") { + std::string payload; + auto opType = libyang::OperationType::DataYang; - DOCTEST_SUBCASE("RESTCONF JSON") { - payload = R"( - { - "ietf-restconf:notification" : { - "eventTime" : "2013-12-21T00:01:00Z", - "example-schema:event" : { - "event-class" : "fault" + DOCTEST_SUBCASE("RESTCONF JSON") { + payload = R"( + { + "ietf-restconf:notification" : { + "eventTime" : "2013-12-21T00:01:00Z", + "example-schema:event" : { + "event-class" : "fault" + } + } } - } + )"; + opType = libyang::OperationType::NotificationRestconf; } - )"; - opType = libyang::OperationType::NotificationRestconf; - } - - DOCTEST_SUBCASE("NETCONF XML") { - payload = R"( - - 2013-12-21T00:01:00Z - - fault - - - )"; - opType = libyang::OperationType::NotificationNetconf; + + DOCTEST_SUBCASE("NETCONF XML") { + payload = R"( + + 2013-12-21T00:01:00Z + + fault + + + )"; + opType = libyang::OperationType::NotificationNetconf; + } + + auto notif = ctx.parseOp(payload, dataTypeFor(payload), opType); + REQUIRE(notif.tree); + REQUIRE(notif.tree->path() == "/notification"); + auto node = notif.tree->child(); + REQUIRE(node); + REQUIRE(node->path() == "/notification/eventTime"); + REQUIRE(node->asOpaque().value() == "2013-12-21T00:01:00Z"); + + REQUIRE(notif.op); + node = notif.op->findPath("/example-schema:event/event-class"); + REQUIRE(!!node); + REQUIRE(std::visit(libyang::ValuePrinter{}, node->asTerm().value()) == "fault"); } - auto notif = ctx.parseOp(payload, dataTypeFor(payload), opType); - REQUIRE(notif.tree); - REQUIRE(notif.tree->path() == "/notification"); - auto node = notif.tree->child(); - REQUIRE(node); - REQUIRE(node->path() == "/notification/eventTime"); - REQUIRE(node->asOpaque().value() == "2013-12-21T00:01:00Z"); + DOCTEST_SUBCASE("yang") + { + std::string payload; - REQUIRE(notif.op); - node = notif.op->findPath("/example-schema:event/event-class"); - REQUIRE(!!node); - REQUIRE(std::visit(libyang::ValuePrinter{}, node->asTerm().value()) == "fault"); + DOCTEST_SUBCASE("top-level") + { + DOCTEST_SUBCASE("json") + { + payload = R"({ + "example-schema:event" : { + "event-class" : "fault" + } + })"; + } + DOCTEST_SUBCASE("xml") + { + payload = R"( + + fault + + )"; + } + auto notif = ctx.parseOp(payload, dataTypeFor(payload), libyang::OperationType::NotificationYang); + REQUIRE(notif.tree); + REQUIRE(notif.op); + REQUIRE(notif.op == notif.tree); + REQUIRE(notif.tree->path() == "/example-schema:event"); + auto node = notif.op->findPath("/example-schema:event/event-class"); + REQUIRE(!!node); + REQUIRE(std::visit(libyang::ValuePrinter{}, node->asTerm().value()) == "fault"); + } + + DOCTEST_SUBCASE("nested") + { + DOCTEST_SUBCASE("json") + { + payload = R"({ + "example-schema:person" : [{ + "name": "John", + "event": { + "description" : "fault" + } + }] + })"; + } + DOCTEST_SUBCASE("xml") + { + payload = R"( + + John + + fault + + + )"; + } + auto notif = ctx.parseOp(payload, dataTypeFor(payload), libyang::OperationType::NotificationYang); + REQUIRE(notif.tree); + REQUIRE(notif.op); + REQUIRE(notif.op != notif.tree); + REQUIRE(notif.tree->path() == "/example-schema:person[name='John']"); + auto node = notif.op->findPath("/example-schema:person[name='John']/event/description"); + REQUIRE(!!node); + REQUIRE(std::visit(libyang::ValuePrinter{}, node->asTerm().value()) == "fault"); + } + } } DOCTEST_SUBCASE("invalid notification") { @@ -2116,6 +2185,9 @@ TEST_CASE("Data Node manipulation") REQUIRE_THROWS_WITH_AS(ctx.parseOp("", libyang::DataFormat::XML, libyang::OperationType::NotificationNetconf), "Can't parse a standalone rpc/action/notification into operation data tree: LY_ENOT", libyang::Error); + REQUIRE_THROWS_WITH_AS(ctx.parseOp("asd", libyang::DataFormat::XML, libyang::OperationType::NotificationYang), + "Can't parse a standalone rpc/action/notification into operation data tree: LY_EVALID", libyang::Error); + /* libyang::setLogOptions(libyang::LogOptions::Log | libyang::LogOptions::Store); */ REQUIRE_THROWS_WITH_AS(ctx.parseOp(R"( { diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp index c3892550..dc279493 100644 --- a/tests/example_schema.hpp +++ b/tests/example_schema.hpp @@ -100,6 +100,12 @@ module example-schema { leaf name { type string; } + + notification event { + leaf description { + type string; + } + } } leaf bossPerson {