diff --git a/lib/hook.h b/lib/hook.h index 19e0f1fbfce7..58aa2009b961 100644 --- a/lib/hook.h +++ b/lib/hook.h @@ -163,6 +163,8 @@ extern void _hook_unregister(struct hook *hook, void *funcptr, void *arg, _hook_unregister(&_hook_##hookname, \ _hook_typecheck_arg_##hookname(func), arg, true) +#define hook_have_hooks(hookname) (_hook_##hookname.entries != NULL) + /* invoke hooks * this is private (static) to the file that has the DEFINE_HOOK statement */ diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c index 286555c56484..5896db1e5859 100644 --- a/lib/mgmt_be_client.c +++ b/lib/mgmt_be_client.c @@ -311,13 +311,15 @@ static int be_client_send_error(struct mgmt_be_client *client, uint64_t txn_id, return ret; } -void mgmt_be_send_notification(struct lyd_node *tree) +static int mgmt_be_send_notification(void *__be_client, const char *xpath, + const struct lyd_node *tree) { struct mgmt_be_client *client = __be_client; struct mgmt_msg_notify_data *msg = NULL; LYD_FORMAT format = LYD_JSON; uint8_t **darrp; LY_ERR err; + int ret = 0; assert(tree); @@ -331,6 +333,8 @@ void mgmt_be_send_notification(struct lyd_node *tree) msg->code = MGMT_MSG_CODE_NOTIFY; msg->result_type = format; + mgmt_msg_native_xpath_encode(msg, xpath); + darrp = mgmt_msg_native_get_darrp(msg); err = yang_print_tree_append(darrp, tree, format, (LYD_PRINT_SHRINK | LYD_PRINT_WD_EXPLICIT | @@ -339,6 +343,7 @@ void mgmt_be_send_notification(struct lyd_node *tree) flog_err(EC_LIB_LIBYANG, "%s: error creating notification data: %s", __func__, ly_strerrcode(err)); + ret = 1; goto done; } @@ -346,53 +351,7 @@ void mgmt_be_send_notification(struct lyd_node *tree) mgmt_msg_native_get_msg_len(msg), false); done: mgmt_msg_native_free_msg(msg); - lyd_free_all(tree); -} - -/* - * Convert old style NB notification data into new MGMTD YANG tree and send. - */ -static int mgmt_be_notification_send(void *arg, const char *xpath, - struct list *args) -{ - struct lyd_node *root = NULL; - struct lyd_node *dnode; - struct yang_data *data; - struct listnode *ln; - LY_ERR err; - - debug_be_client("%s: sending notification: %s", __func__, xpath); - - /* - * Convert yang data args list to a libyang data tree - */ - for (ALL_LIST_ELEMENTS_RO(args, ln, data)) { - err = lyd_new_path(root, ly_native_ctx, data->xpath, - data->value, LYD_NEW_PATH_UPDATE, &dnode); - if (err != LY_SUCCESS) { -lyerr: - flog_err(EC_LIB_LIBYANG, - "%s: error creating notification data: %s", - __func__, ly_strerrcode(err)); - if (root) - lyd_free_all(root); - return 1; - } - if (!root) { - root = dnode; - while (root->parent) - root = lyd_parent(root); - } - } - - if (!root) { - err = lyd_new_path(NULL, ly_native_ctx, xpath, "", 0, &root); - if (err) - goto lyerr; - } - - mgmt_be_send_notification(root); - return 0; + return ret; } static int mgmt_be_send_txn_reply(struct mgmt_be_client *client_ctx, @@ -964,27 +923,40 @@ static void be_client_handle_notify(struct mgmt_be_client *client, void *msgbuf, { struct mgmt_msg_notify_data *notif_msg = msgbuf; struct nb_node *nb_node; - char notif[XPATH_MAXLEN]; struct lyd_node *dnode; + const char *data; + const char *notif; LY_ERR err; debug_be_client("Received notification for client %s", client->name); - err = yang_parse_notification(notif_msg->result_type, - (char *)notif_msg->result, &dnode); - if (err) + notif = mgmt_msg_native_xpath_data_decode(notif_msg, msg_len, data); + if (!notif || !data) { + log_err_be_client("Corrupt notify msg"); return; - - lysc_path(dnode->schema, LYSC_PATH_DATA, notif, sizeof(notif)); + } nb_node = nb_node_find(notif); - if (!nb_node || !nb_node->cbs.notify) { - debug_be_client("No notification callback for %s", notif); - goto cleanup; + if (!nb_node) { + log_err_be_client("No schema found for notification: %s", notif); + return; + } + + if (!nb_node->cbs.notify) { + debug_be_client("No notification callback for: %s", notif); + return; + } + + err = yang_parse_notification(notif, notif_msg->result_type, data, + &dnode); + if (err) { + log_err_be_client("Can't parse notification data for: %s", + notif); + return; } nb_callback_notify(nb_node, notif, dnode); -cleanup: + lyd_free_all(dnode); } @@ -1202,7 +1174,7 @@ struct mgmt_be_client *mgmt_be_client_create(const char *client_name, "BE-client", debug_check_be_client()); /* Hook to receive notifications */ - hook_register_arg(nb_notification_send, mgmt_be_notification_send, + hook_register_arg(nb_notification_tree_send, mgmt_be_send_notification, client); debug_be_client("Initialized client '%s'", client_name); diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h index 361899fc1da0..cd8b23752671 100644 --- a/lib/mgmt_be_client.h +++ b/lib/mgmt_be_client.h @@ -143,15 +143,6 @@ extern int mgmt_be_send_subscr_req(struct mgmt_be_client *client_ctx, int n_config_xpaths, char **config_xpaths, int n_oper_xpaths, char **oper_xpaths); -/** - * mgmt_be_notification_send() - send a YANG notification to FE clients. - * @tree: libyang tree for the notification. The tree will be freed by - * this function. - * - */ -extern void mgmt_be_send_notification(struct lyd_node *tree); - - /* * Destroy backend client and cleanup everything. */ diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c index 9f98a241fe46..bfdecedc4e1e 100644 --- a/lib/mgmt_fe_client.c +++ b/lib/mgmt_fe_client.c @@ -504,7 +504,8 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, struct mgmt_msg_notify_data *notify_msg; struct mgmt_msg_tree_data *tree_msg; struct mgmt_msg_error *err_msg; - char *notify_data = NULL; + const char *data = NULL; + size_t dlen; debug_fe_client("Got native message for session-id %" PRIu64, msg->refer_id); @@ -563,20 +564,17 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, return; } - if (notify_msg->result_type != LYD_LYB && - !MGMT_MSG_VALIDATE_NUL_TERM(notify_msg, msg_len)) { + data = mgmt_msg_native_data_decode(notify_msg, msg_len); + if (!data) { log_err_fe_client("Corrupt error msg recv"); return; } - if (notify_msg->result_type == LYD_JSON) - notify_data = (char *)notify_msg->result; - else - notify_data = - yang_convert_lyd_format(notify_msg->result, - msg_len, - notify_msg->result_type, - LYD_JSON, true); - if (!notify_data) { + dlen = mgmt_msg_native_data_len_decode(notify_msg, msg_len); + if (notify_msg->result_type != LYD_JSON) + data = yang_convert_lyd_format(data, dlen, + notify_msg->result_type, + LYD_JSON, true); + if (!data) { log_err_fe_client("Can't convert format %d to JSON", notify_msg->result_type); return; @@ -588,11 +586,10 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, session->client->cbs .async_notification(client, client->user_data, session->client_id, - session->user_ctx, - notify_data); + session->user_ctx, data); } if (notify_msg->result_type != LYD_JSON) - darr_free(notify_data); + darr_free(data); break; default: log_err_fe_client("unknown native message session-id %" PRIu64 diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h index 7273170a132c..53bb81be2832 100644 --- a/lib/mgmt_msg_native.h +++ b/lib/mgmt_msg_native.h @@ -77,6 +77,11 @@ extern "C" { * mgmt_msg_native_get_msg_len() - Get the total length of the msg. * mgmt_msg_native_send_msg() - Send the message. * + * mgmt_msg_native_xpath_encode() - Encode xpath in xpath, data format message. + * mgmt_msg_native_xpath_data_decode() - Decode xpath, data format message. + * mgmt_msg_native_xpath_decode() - Get the xpath, from xpath, data format message. + * mgmt_msg_native_data_decode() - Get the secondary data from xpath, data message. + * mgmt_msg_native_data_len_decode() - Get length of secondary data. * * ------------------------------------- * [Advanced Use] Dynamic Array Messages @@ -299,18 +304,18 @@ _Static_assert(sizeof(struct mgmt_msg_get_data) == * struct mgmt_msg_notify_data - Message carrying notification data. * * @result_type: ``LYD_FORMAT`` for format of the @result value. - * @result: The tree data in @result_type format. - * + * @data: The xpath string of the notification followed by the tree data in + * @result_type format. */ struct mgmt_msg_notify_data { struct mgmt_msg_header; uint8_t result_type; uint8_t resv2[7]; - alignas(8) uint8_t result[]; + alignas(8) char data[]; }; _Static_assert(sizeof(struct mgmt_msg_notify_data) == - offsetof(struct mgmt_msg_notify_data, result), + offsetof(struct mgmt_msg_notify_data, data), "Size mismatch"); /* @@ -404,7 +409,12 @@ extern int vmgmt_msg_native_send_error(struct msg_conn *conn, * Return: a pointer to the newly appended data. */ #define mgmt_msg_native_append(msg, data, len) \ - memcpy(darr_append(*mgmt_msg_native_get_darrp(msg), len), data, len) + ({ \ + uint8_t **darrp = mgmt_msg_native_get_darrp(msg); \ + uint8_t *p = darr_append_n(*darrp, len); \ + memcpy(p, data, len); \ + p; \ + }) /** * mgmt_msg_native_send_msg(msg, short_circuit_ok) - Send a native msg. @@ -458,6 +468,116 @@ extern int vmgmt_msg_native_send_error(struct msg_conn *conn, */ #define mgmt_msg_native_get_darrp(msg) ((uint8_t **)&(msg)) +/* ------------------------- */ +/* Encode and Decode Helpers */ +/* ------------------------- */ + +/** + * mgmt_msg_native_xpath_encode() - encode an xpath in a xpath, data message. + * @msg: Pointer to the native message. + * @xpath: The xpath string to encode. + * + * This function starts the encoding of a message that can be decoded with + * `mgmt_msg_native_xpath_data_decode()`. The variable length data is comprised + * of a NUL terminated string followed by some data of any format. This starts + * the first half of the encoding, after which one can simply append the + * secondary data to the message. + */ +#define mgmt_msg_native_xpath_encode(msg, xpath) \ + do { \ + size_t __slen = strlen(xpath) + 1; \ + mgmt_msg_native_append(msg, xpath, __slen); \ + (msg)->vsplit = __slen; \ + } while (0) + +/** + * mgmt_msg_native_xpath_data_decode() - decode an xpath, data format message. + * @msg: Pointer to the native message. + * @msglen: Length of the message. + * @data: [OUT] Pointer to the data section of the variable data + * + * This function decodes a message that was encoded with + * `mgmt_msg_native_xpath_encode()`. The variable length data is comprised of a + * NUL terminated string followed by some data of any format. + * + * Return: + * The xpath string or NULL if there was an error decoding (i.e., the + * message is corrupt). + */ +#define mgmt_msg_native_xpath_data_decode(msg, msglen, data) \ + ({ \ + size_t __len = (msglen) - sizeof(*msg); \ + const char *__s = NULL; \ + if (msg->vsplit && msg->vsplit <= __len && \ + msg->data[msg->vsplit - 1] == 0) { \ + (data) = msg->data + msg->vsplit; \ + __s = msg->data; \ + } \ + __s; \ + }) + +/** + * mgmt_msg_native_xpath_decode() - return the xpath from xpath, data message. + * @msg: Pointer to the native message. + * @msglen: Length of the message. + * + * This function decodes the xpath from a message that was encoded with + * `mgmt_msg_native_xpath_encode()`. The variable length data is comprised of a + * NUL terminated string followed by some data of any format. + * + * Return: + * The xpath string or NULL if there was an error decoding (i.e., the + * message is corrupt). + */ +#define mgmt_msg_native_xpath_decode(msg, msglen) \ + ({ \ + size_t __len = (msglen) - sizeof(*msg); \ + const char *__s = msg->data; \ + if (!msg->vsplit || msg->vsplit > __len || \ + __s[msg->vsplit - 1] != 0) \ + __s = NULL; \ + __s; \ + }) + +/** + * mgmt_msg_native_data_decode() - return the data from xpath, data message. + * @msg: Pointer to the native message. + * @msglen: Length of the message. + * + * This function decodes the secondary data from a message that was encoded with + * `mgmt_msg_native_xpath_encode()`. The variable length data is comprised of a + * NUL terminated string followed by some data of any format. + * + * Return: + * The secondary data or NULL if there was an error decoding (i.e., the + * message is corrupt). + */ +#define mgmt_msg_native_data_decode(msg, msglen) \ + ({ \ + size_t __len = (msglen) - sizeof(*msg); \ + const char *__data = msg->data + msg->vsplit; \ + if (!msg->vsplit || msg->vsplit > __len || __data[-1] != 0) \ + __data = NULL; \ + __data; \ + }) + +/** + * mgmt_msg_native_data_len_decode() - len of data in xpath, data format message. + * @msg: Pointer to the native message. + * @msglen: Length of the message. + * + * This function returns the length of the secondary variable data from a + * message that was encoded with `mgmt_msg_native_xpath_encode()`. The variable + * length data is comprised of a NUL terminated string followed by some data of + * any format. + * + * Return: + * The length of the secondary variable data. The message is assumed to be + * validated as not corrupt already. + */ +#define mgmt_msg_native_data_len_decode(msg, msglen) \ + ((msglen) - sizeof(*msg) - msg->vsplit) + #ifdef __cplusplus } #endif diff --git a/lib/northbound.c b/lib/northbound.c index d74773194c71..a3d91e56afb3 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -2076,20 +2076,68 @@ DEFINE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments), int nb_notification_send(const char *xpath, struct list *arguments) { + struct lyd_node *root = NULL; + struct lyd_node *dnode; + struct yang_data *data; + struct listnode *ln; + LY_ERR err; int ret; DEBUGD(&nb_dbg_notif, "northbound notification: %s", xpath); + /* + * Call old hook functions + */ ret = hook_call(nb_notification_send, xpath, arguments); + + if (!hook_have_hooks(nb_notification_tree_send)) + goto done; + /* + * Convert yang data arguments list to a libyang data tree for new hook + * functions. + */ + for (ALL_LIST_ELEMENTS_RO(arguments, ln, data)) { + err = lyd_new_path(root, ly_native_ctx, data->xpath, + data->value, LYD_NEW_PATH_UPDATE, &dnode); + if (err != LY_SUCCESS) + goto lyerr; + if (!root) { + root = dnode; + while (root->parent) + root = lyd_parent(root); + } + } + + if (!root) { + err = lyd_new_path(NULL, ly_native_ctx, xpath, "", 0, &root); + if (err) { +lyerr: + flog_err(EC_LIB_LIBYANG, + "%s: error creating notification data: %s", + __func__, ly_strerrcode(err)); + ret += 1; + goto done; + } + } + + /* + * Call new hook functions + */ + ret += nb_notification_tree_send(xpath, root); + +done: + if (root) + lyd_free_all(root); if (arguments) list_delete(&arguments); return ret; } -DEFINE_HOOK(nb_notification_tree_send, (struct lyd_node *tree), (tree)); +DEFINE_HOOK(nb_notification_tree_send, + (const char *xpath, const struct lyd_node *tree), (xpath, tree)); -int nb_notification_tree_send(struct lyd_node *tree) +int nb_notification_tree_send(const char *xpath, const struct lyd_node *tree) { int ret; @@ -2098,8 +2146,7 @@ int nb_notification_tree_send(struct lyd_node *tree) DEBUGD(&nb_dbg_notif, "northbound tree notification: %s", tree->schema->name); - ret = hook_call(nb_notification_tree_send, tree); - lyd_free_all(tree); + ret = hook_call(nb_notification_tree_send, xpath, tree); return ret; } diff --git a/lib/northbound.h b/lib/northbound.h index e9f2db9b6ef3..5be111cf0a12 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -803,6 +803,8 @@ typedef enum nb_error (*nb_oper_data_finish_cb)(const struct lyd_node *tree, /* Hooks. */ DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments), (xpath, arguments)); +DECLARE_HOOK(nb_notification_tree_send, + (const char *xpath, const struct lyd_node *tree), (xpath, tree)); DECLARE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty)); DECLARE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set)); @@ -1491,14 +1493,17 @@ extern int nb_notification_send(const char *xpath, struct list *arguments); * Send a YANG notification from a backend . This is a no-op unless th * 'nb_notification_tree_send' hook was registered by a northbound plugin. * + * xpath + * XPath of the YANG notification. + * * tree - * The libyang tree for the notification. The tree will be freed by - * this call. + * The libyang tree for the notification. * * Returns: * NB_OK on success, NB_ERR otherwise. */ -extern int nb_notification_tree_send(struct lyd_node *tree); +extern int nb_notification_tree_send(const char *xpath, + const struct lyd_node *tree); /* * Associate a user pointer to a configuration node. diff --git a/lib/yang.c b/lib/yang.c index 3ce24a318d43..03044fc29e9b 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -714,12 +714,12 @@ static void ly_log_cb(LY_LOG_LEVEL level, const char *msg, const char *path) zlog(priority, "libyang: %s", msg); } -LY_ERR yang_parse_notification(LYD_FORMAT format, const char *data, - struct lyd_node **notif) +LY_ERR yang_parse_notification(const char *xpath, LYD_FORMAT format, + const char *data, struct lyd_node **notif) { - struct lyd_node *tree, *dnode; + struct lyd_node *tree; + struct ly_set *set = NULL; struct ly_in *in = NULL; - bool found = false; LY_ERR err; err = ly_in_new_memory(data, &in); @@ -736,26 +736,20 @@ LY_ERR yang_parse_notification(LYD_FORMAT format, const char *data, return err; } - /* - * Notification can be a child of some data node, so traverse the tree - * until we find the notification. - */ - LYD_TREE_DFS_BEGIN (tree, dnode) { - if (dnode->schema->nodetype == LYS_NOTIF) { - found = true; - break; - } - LYD_TREE_DFS_END(tree, dnode); + err = lyd_find_xpath3(NULL, tree, xpath, NULL, &set); + if (err) { + zlog_err("Failed to parse notification: %s", ly_last_errmsg()); + lyd_free_all(tree); + return err; } - - if (!found) { - zlog_err("Notification not found in the parsed tree"); + if (set->count == 0) { + zlog_err("Notification not found in the parsed tree: %s", xpath); + ly_set_free(set, NULL); lyd_free_all(tree); return LY_ENOTFOUND; } - - *notif = dnode; - + *notif = set->dnodes[0]; + ly_set_free(set, NULL); return LY_SUCCESS; } @@ -790,9 +784,9 @@ uint8_t *yang_print_tree(const struct lyd_node *root, LYD_FORMAT format, return darr; } -char *yang_convert_lyd_format(const uint8_t *data, size_t data_len, - LYD_FORMAT in_format, - LYD_FORMAT out_format, bool shrink) +char *yang_convert_lyd_format(const char *data, size_t data_len, + LYD_FORMAT in_format, LYD_FORMAT out_format, + bool shrink) { struct lyd_node *tree = NULL; uint32_t options = LYD_PRINT_WD_EXPLICIT | LYD_PRINT_WITHSIBLINGS; diff --git a/lib/yang.h b/lib/yang.h index 9c221445cd2e..65f6a73e0b50 100644 --- a/lib/yang.h +++ b/lib/yang.h @@ -611,6 +611,7 @@ extern void yang_debugging_set(bool enable); * Parse a YANG notification. * * Args: + * xpath: xpath of notification. * format: LYD_FORMAT of input data. * data: input data. * notif: pointer to the libyang data tree to store the parsed notification. @@ -618,8 +619,8 @@ extern void yang_debugging_set(bool enable); * the pointer to the notification node is still returned, but it's * part of the full data tree with all its parents. */ -extern LY_ERR yang_parse_notification(LYD_FORMAT format, const char *data, - struct lyd_node **notif); +extern LY_ERR yang_parse_notification(const char *xpath, LYD_FORMAT format, + const char *data, struct lyd_node **notif); /* * "Print" the yang tree in `root` into dynamic sized array. @@ -647,7 +648,7 @@ extern uint8_t *yang_print_tree(const struct lyd_node *root, LYD_FORMAT format, * Return: * A darr based string or NULL for error. */ -extern char *yang_convert_lyd_format(const uint8_t *data, size_t msg_len, +extern char *yang_convert_lyd_format(const char *data, size_t msg_len, LYD_FORMAT in_format, LYD_FORMAT out_format, bool shrink); diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c index d85d87b4b689..b311bf469878 100644 --- a/mgmtd/mgmt_be_adapter.c +++ b/mgmtd/mgmt_be_adapter.c @@ -592,25 +592,29 @@ static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg, { struct mgmt_be_client_adapter *adapter; struct mgmt_be_xpath_map *map; - char notif[XPATH_MAXLEN]; - struct lyd_node *dnode; - LY_ERR err; - uint id; + struct nb_node *nb_node; + const char *notif; + uint id, len; if (!darr_len(be_notif_xpath_map)) return; - err = yang_parse_notification(msg->result_type, (char *)msg->result, - &dnode); - if (err) + notif = mgmt_msg_native_xpath_decode(msg, msglen); + if (!notif) { + __log_err("Corrupt notify msg"); return; + } - lysc_path(dnode->schema, LYSC_PATH_DATA, notif, sizeof(notif)); - - lyd_free_all(dnode); + nb_node = nb_node_find(notif); + if (!nb_node) { + __log_err("No schema found for notification: %s", notif); + return; + } darr_foreach_p (be_notif_xpath_map, map) { - if (strncmp(map->xpath_prefix, notif, strlen(map->xpath_prefix))) + len = strlen(map->xpath_prefix); + if (strncmp(map->xpath_prefix, nb_node->xpath, len) && + strncmp(map->xpath_prefix, notif, len)) continue; FOREACH_BE_CLIENT_BITS (id, map->clients) { diff --git a/tests/topotests/lib/fe_client.py b/tests/topotests/lib/fe_client.py index ec643bb0bf60..07059ccf3a86 100755 --- a/tests/topotests/lib/fe_client.py +++ b/tests/topotests/lib/fe_client.py @@ -321,12 +321,18 @@ def recv_notify(self, xpaths=None): while True: logging.debug("Waiting for Notify Message") mhdr, mfixed, mdata = self.recv_native_msg() - assert mdata[-1] == 0 - result = mdata[:-1].decode("utf-8") if mhdr[HDR_FIELD_CODE] == MSG_CODE_NOTIFY: logging.debug("Received Notify Message: %s: %s", mfixed, mdata) else: raise Exception(f"Received NON-NOTIFY Message: {mfixed}: {mdata}") + + vsplit = mhdr[HDR_FIELD_VSPLIT] + assert mdata[vsplit - 1] == 0 + xpath = mdata[: vsplit - 1].decode("utf-8") + + assert mdata[-1] == 0 + result = mdata[vsplit:-1].decode("utf-8") + if not xpaths: return result js = json.loads(result)