client messages FEATURE support for subscribed-notification RPCs
diff --git a/src/messages_client.c b/src/messages_client.c
index cb0a17c..064ccfe 100644
--- a/src/messages_client.c
+++ b/src/messages_client.c
@@ -3,7 +3,7 @@
  * \author Radek Krejci <rkrejci@cesnet.cz>
  * \brief libnetconf2 - NETCONF messages functions
  *
- * Copyright (c) 2015 CESNET, z.s.p.o.
+ * Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -132,7 +132,7 @@
 
 API struct nc_rpc *
 nc_rpc_edit(NC_DATASTORE target, NC_RPC_EDIT_DFLTOP default_op, NC_RPC_EDIT_TESTOPT test_opt,
-            NC_RPC_EDIT_ERROPT error_opt, const char *edit_content, NC_PARAMTYPE paramtype)
+        NC_RPC_EDIT_ERROPT error_opt, const char *edit_content, NC_PARAMTYPE paramtype)
 {
     struct nc_rpc_edit *rpc;
 
@@ -172,7 +172,7 @@
 
 API struct nc_rpc *
 nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source, const char *url_or_config_src,
-            NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype)
+        NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype)
 {
     struct nc_rpc_copy *rpc;
 
@@ -338,7 +338,7 @@
 
 API struct nc_rpc *
 nc_rpc_commit(int confirmed, uint32_t confirm_timeout, const char *persist, const char *persist_id,
-              NC_PARAMTYPE paramtype)
+        NC_PARAMTYPE paramtype)
 {
     struct nc_rpc_commit *rpc;
 
@@ -476,7 +476,7 @@
 
 API struct nc_rpc *
 nc_rpc_subscribe(const char *stream_name, const char *filter, const char *start_time, const char *stop_time,
-                 NC_PARAMTYPE paramtype)
+        NC_PARAMTYPE paramtype)
 {
     struct nc_rpc_subscribe *rpc;
 
@@ -519,8 +519,8 @@
 
 API struct nc_rpc *
 nc_rpc_getdata(const char *datastore, const char *filter, const char *config_filter, char **origin_filter,
-               int origin_filter_count, int negated_origin_filter, uint16_t max_depth, int with_origin,
-               NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype)
+        int origin_filter_count, int negated_origin_filter, uint16_t max_depth, int with_origin, NC_WD_MODE wd_mode,
+        NC_PARAMTYPE paramtype)
 {
     struct nc_rpc_getdata *rpc = NULL;
     int i;
@@ -627,6 +627,141 @@
     return (struct nc_rpc *)rpc;
 }
 
+API struct nc_rpc *
+nc_rpc_establishsub(const char *filter, const char *stream_name, const char *start_time,
+        const char *stop_time, const char *encoding, NC_PARAMTYPE paramtype)
+{
+    struct nc_rpc_establishsub *rpc;
+
+    if (!stream_name) {
+        ERRARG("stream_name");
+        return NULL;
+    }
+
+    if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
+        ERR("Filter is not an XML subtree, an XPath expression, not a filter reference (invalid first char '%c').", filter[0]);
+        return NULL;
+    }
+
+    rpc = malloc(sizeof *rpc);
+    if (!rpc) {
+        ERRMEM;
+        return NULL;
+    }
+
+    rpc->type = NC_RPC_ESTABLISHSUB;
+    if (filter && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->filter = strdup(filter);
+    } else {
+        rpc->filter = (char *)filter;
+    }
+    if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
+        rpc->stream = strdup(stream_name);
+    } else {
+        rpc->stream = (char *)stream_name;
+    }
+    if (start_time && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->start = strdup(start_time);
+    } else {
+        rpc->start = (char *)start_time;
+    }
+    if (stop_time && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->stop = strdup(stop_time);
+    } else {
+        rpc->stop = (char *)stop_time;
+    }
+    if (encoding && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->encoding = strdup(encoding);
+    } else {
+        rpc->encoding = (char *)encoding;
+    }
+    rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
+
+    return (struct nc_rpc *)rpc;
+}
+
+API struct nc_rpc *
+nc_rpc_modifysub(uint32_t id, const char *filter, const char *stop_time, NC_PARAMTYPE paramtype)
+{
+    struct nc_rpc_modifysub *rpc;
+
+    if (!id) {
+        ERRARG("id");
+        return NULL;
+    }
+
+    if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
+        ERR("Filter is not an XML subtree, an XPath expression, not a filter reference (invalid first char '%c').", filter[0]);
+        return NULL;
+    }
+
+    rpc = malloc(sizeof *rpc);
+    if (!rpc) {
+        ERRMEM;
+        return NULL;
+    }
+
+    rpc->type = NC_RPC_MODIFYSUB;
+    rpc->id = id;
+    if (filter && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->filter = strdup(filter);
+    } else {
+        rpc->filter = (char *)filter;
+    }
+    if (stop_time && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
+        rpc->stop = strdup(stop_time);
+    } else {
+        rpc->stop = (char *)stop_time;
+    }
+    rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
+
+    return (struct nc_rpc *)rpc;
+}
+
+API struct nc_rpc *
+nc_rpc_deletesub(uint32_t id)
+{
+    struct nc_rpc_deletesub *rpc;
+
+    if (!id) {
+        ERRARG("id");
+        return NULL;
+    }
+
+    rpc = malloc(sizeof *rpc);
+    if (!rpc) {
+        ERRMEM;
+        return NULL;
+    }
+
+    rpc->type = NC_RPC_DELETESUB;
+    rpc->id = id;
+
+    return (struct nc_rpc *)rpc;
+}
+
+API struct nc_rpc *
+nc_rpc_killsub(uint32_t id)
+{
+    struct nc_rpc_killsub *rpc;
+
+    if (!id) {
+        ERRARG("id");
+        return NULL;
+    }
+
+    rpc = malloc(sizeof *rpc);
+    if (!rpc) {
+        ERRMEM;
+        return NULL;
+    }
+
+    rpc->type = NC_RPC_KILLSUB;
+    rpc->id = id;
+
+    return (struct nc_rpc *)rpc;
+}
+
 API void
 nc_rpc_free(struct nc_rpc *rpc)
 {
@@ -643,6 +778,8 @@
     struct nc_rpc_subscribe *rpc_subscribe;
     struct nc_rpc_getdata *rpc_getdata;
     struct nc_rpc_editdata *rpc_editdata;
+    struct nc_rpc_establishsub *rpc_establishsub;
+    struct nc_rpc_modifysub *rpc_modifysub;
     int i;
 
     if (!rpc) {
@@ -746,7 +883,30 @@
             free(rpc_editdata->edit_cont);
         }
         break;
-    default:
+    case NC_RPC_ESTABLISHSUB:
+        rpc_establishsub = (struct nc_rpc_establishsub *)rpc;
+        if (rpc_establishsub->free) {
+            free(rpc_establishsub->filter);
+            free(rpc_establishsub->stream);
+            free(rpc_establishsub->start);
+            free(rpc_establishsub->stop);
+            free(rpc_establishsub->encoding);
+        }
+        break;
+    case NC_RPC_MODIFYSUB:
+        rpc_modifysub = (struct nc_rpc_modifysub *)rpc;
+        if (rpc_modifysub->free) {
+            free(rpc_modifysub->filter);
+            free(rpc_modifysub->stop);
+        }
+        break;
+    case NC_RPC_UNKNOWN:
+    case NC_RPC_LOCK:
+    case NC_RPC_UNLOCK:
+    case NC_RPC_KILL:
+    case NC_RPC_DISCARD:
+    case NC_RPC_DELETESUB:
+    case NC_RPC_KILLSUB:
         /* nothing special needed */
         break;
     }
diff --git a/src/messages_client.h b/src/messages_client.h
index 2a7281e..0504bc3 100644
--- a/src/messages_client.h
+++ b/src/messages_client.h
@@ -64,6 +64,12 @@
     /* ietf-netconf-nmda */
     NC_RPC_GETDATA,     /**< \<get-data\> RPC. */
     NC_RPC_EDITDATA,    /**< \<edit-data\> RPC. */
+
+    /* ietf-subscribed-notifications */
+    NC_RPC_ESTABLISHSUB,    /**< \<establish-subscription\> RPC. */
+    NC_RPC_MODIFYSUB,       /**< \<modify-subscription\> RPC. */
+    NC_RPC_DELETESUB,       /**< \<delete-subscription\> RPC. */
+    NC_RPC_KILLSUB,         /**< \<kill-subscription\> RPC. */
 } NC_RPC_TYPE;
 
 /**
@@ -139,7 +145,13 @@
 };
 
 /**
+ * @struct nc_rpc
  * @brief NETCONF client RPC object
+ *
+ * Note that any stored parameters are not checked for validity because it is performed later,
+ * while sending the RPC via a specific NETCONF session (::nc_send_rpc()) since the NETCONF
+ * capabilities of the session are needed for such a check. An RPC object can be sent via any
+ * NETCONF session which supports all the needed NETCONF capabilities for the RPC.
  */
 struct nc_rpc;
 
@@ -166,11 +178,7 @@
 /**
  * @brief Create a generic NETCONF RPC or action from an XML string
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] xml_str NETCONF RPC data as an XML string.
  * @param[in] paramtype How to further manage data parameters.
@@ -181,11 +189,7 @@
 /**
  * @brief Create NETCONF RPC \<get-config\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] source Source datastore being queried.
  * @param[in] filter Optional filter data, an XML subtree or XPath expression (with JSON prefixes).
@@ -194,16 +198,12 @@
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
 struct nc_rpc *nc_rpc_getconfig(NC_DATASTORE source, const char *filter, NC_WD_MODE wd_mode,
-                                NC_PARAMTYPE paramtype);
+        NC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<edit-config\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] target Target datastore being edited.
  * @param[in] default_op Optional default operation.
@@ -214,16 +214,12 @@
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
 struct nc_rpc *nc_rpc_edit(NC_DATASTORE target, NC_RPC_EDIT_DFLTOP default_op, NC_RPC_EDIT_TESTOPT test_opt,
-                           NC_RPC_EDIT_ERROPT error_opt, const char *edit_content, NC_PARAMTYPE paramtype);
+        NC_RPC_EDIT_ERROPT error_opt, const char *edit_content, NC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<copy-config\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] target Target datastore.
  * @param[in] url_trg Used instead \p target if the target is an URL.
@@ -234,16 +230,12 @@
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
 struct nc_rpc *nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source,
-                           const char *url_or_config_src, NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype);
+        const char *url_or_config_src, NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<delete-config\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] target Target datastore to delete.
  * @param[in] url Used instead \p target if the target is an URL.
@@ -255,11 +247,7 @@
 /**
  * @brief Create NETCONF RPC \<lock\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] target Target datastore of the operation.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
@@ -269,11 +257,7 @@
 /**
  * @brief Create NETCONF RPC \<unlock\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] target Target datastore of the operation.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
@@ -283,11 +267,7 @@
 /**
  * @brief Create NETCONF RPC \<get\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] filter Optional filter data, an XML subtree or XPath expression (with JSON prefixes).
  * @param[in] wd_mode Optional with-defaults capability mode.
@@ -299,11 +279,7 @@
 /**
  * @brief Create NETCONF RPC \<kill-session\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] session_id Session ID of the session to kill.
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
@@ -313,11 +289,7 @@
 /**
  * @brief Create NETCONF RPC \<commit\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] confirmed Whether the commit is to be confirmed.
  * @param[in] confirm_timeout Optional confirm timeout.
@@ -327,16 +299,12 @@
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
 struct nc_rpc *nc_rpc_commit(int confirmed, uint32_t confirm_timeout, const char *persist, const char *persist_id,
-                             NC_PARAMTYPE paramtype);
+        NC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<discard-changes\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
@@ -345,11 +313,7 @@
 /**
  * @brief Create NETCONF RPC \<cancel-commit\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] persist_id Optional identification string of a persistent confirmed commit.
  * @param[in] paramtype How to further manage data parameters.
@@ -360,11 +324,7 @@
 /**
  * @brief Create NETCONF RPC \<validate\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] source Source datastore being validated.
  * @param[in] url_or_config Used instead \p source if the source is an URL or a config.
@@ -376,11 +336,7 @@
 /**
  * @brief Create NETCONF RPC \<get-schema\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] identifier Requested model identifier.
  * @param[in] version Optional model version, either YANG version (1.0/1.1) or revision date.
@@ -393,11 +349,7 @@
 /**
  * @brief Create NETCONF RPC \<create-subscription\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] stream_name Optional name of a NETCONF stream to subscribe to.
  * @param[in] filter Optional filter data, an XML subtree or XPath expression (with JSON prefixes).
@@ -407,16 +359,12 @@
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
 struct nc_rpc *nc_rpc_subscribe(const char *stream_name, const char *filter, const char *start_time,
-                                const char *stop_time, NC_PARAMTYPE paramtype);
+        const char *stop_time, NC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<get-data\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] datastore Source datastore, foreign identity so a module name prefix is required.
  * @param[in] filter Optional filter data, an XML subtree or XPath expression (with JSON prefixes).
@@ -431,17 +379,13 @@
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
 struct nc_rpc *nc_rpc_getdata(const char *datastore, const char *filter, const char *config_filter, char **origin_filter,
-                              int origin_filter_count, int neg_origin_filter, uint16_t max_depth, int with_origin,
-                              NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype);
+        int origin_filter_count, int neg_origin_filter, uint16_t max_depth, int with_origin, NC_WD_MODE wd_mode,
+        NC_PARAMTYPE paramtype);
 
 /**
  * @brief Create NETCONF RPC \<get-data\>
  *
- * Note that functions to create any RPC object do not check validity of the provided
- * parameters. It is checked later while sending the RPC via a specific NETCONF session
- * (#nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
- * check. Created object can be sent via any NETCONF session which supports all the
- * needed NETCONF capabilities for the RPC.
+ * For details, see ::nc_rpc.
  *
  * @param[in] datastore Source datastore, foreign identity so a module name prefix is required.
  * @param[in] default_op Optional default operation.
@@ -450,7 +394,58 @@
  * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
  */
 struct nc_rpc *nc_rpc_editdata(const char *datastore, NC_RPC_EDIT_DFLTOP default_op, const char *edit_content,
-                               NC_PARAMTYPE paramtype);
+        NC_PARAMTYPE paramtype);
+
+/**
+ * @brief Create NETCONF RPC \<establish-subscription\>
+ *
+ * For details, see ::nc_rpc.
+ *
+ * @param[in] filter Optional filter data, an XML subtree, XPath expression (with JSON prefixes),
+ * or filter reference, selected based on the first character.
+ * @param[in] stream_name Name of a NETCONF stream to subscribe to.
+ * @param[in] start_time Optional YANG datetime identifying the start of the subscription.
+ * @param[in] stop_time Optional YANG datetime identifying the end of the subscription.
+ * @param[in] encoding Optional specific encoding to use.
+ * @param[in] paramtype How to further manage data parameters.
+ * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
+ */
+struct nc_rpc *nc_rpc_establishsub(const char *filter, const char *stream_name, const char *start_time,
+        const char *stop_time, const char *encoding, NC_PARAMTYPE paramtype);
+
+/**
+ * @brief Create NETCONF RPC \<modify-subscription\>
+ *
+ * For details, see ::nc_rpc.
+ *
+ * @param[in] id Subscription ID to modify.
+ * @param[in] filter Optional new filter data, an XML subtree, XPath expression (with JSON prefixes),
+ * or filter reference, selected based on the first character.
+ * @param[in] stop_time Optional new YANG datetime identifying the end of the subscription.
+ * @param[in] paramtype How to further manage data parameters.
+ * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
+ */
+struct nc_rpc *nc_rpc_modifysub(uint32_t id, const char *filter, const char *stop_time, NC_PARAMTYPE paramtype);
+
+/**
+ * @brief Create NETCONF RPC \<delete-subscription\>
+ *
+ * For details, see ::nc_rpc.
+ *
+ * @param[in] id Subscription ID to delete.
+ * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
+ */
+struct nc_rpc *nc_rpc_deletesub(uint32_t id);
+
+/**
+ * @brief Create NETCONF RPC \<kill-subscription\>
+ *
+ * For details, see ::nc_rpc.
+ *
+ * @param[in] id Subscription ID to kill.
+ * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
+ */
+struct nc_rpc *nc_rpc_killsub(uint32_t id);
 
 /**
  * @brief Free the NETCONF RPC object.
diff --git a/src/messages_p.h b/src/messages_p.h
index f7b5449..b71f69d 100644
--- a/src/messages_p.h
+++ b/src/messages_p.h
@@ -185,6 +185,34 @@
     char free;
 };
 
+struct nc_rpc_establishsub {
+    NC_RPC_TYPE type;        /**< NC_RPC_ESTABLISHSUB */
+    char *filter;            /**< XML subtree (starts with '<'), an XPath (starts with '/'), or reference (start with alpha) */
+    char *stream;            /**< stream name */
+    char *start;
+    char *stop;
+    char *encoding;
+    char free;
+};
+
+struct nc_rpc_modifysub {
+    NC_RPC_TYPE type;        /**< NC_RPC_MODIFYSUB */
+    uint32_t id;
+    char *filter;            /**< XML subtree (starts with '<'), an XPath (starts with '/'), or reference (start with alpha) */
+    char *stop;
+    char free;
+};
+
+struct nc_rpc_deletesub {
+    NC_RPC_TYPE type;        /**< NC_RPC_DELETESUB */
+    uint32_t id;
+};
+
+struct nc_rpc_killsub {
+    NC_RPC_TYPE type;        /**< NC_RPC_KILLSUB */
+    uint32_t id;
+};
+
 void nc_server_rpc_free(struct nc_server_rpc *rpc);
 
 void nc_client_err_clean(struct nc_err *err, struct ly_ctx *ctx);
diff --git a/src/session_client.c b/src/session_client.c
index 0606764..cd296ee 100644
--- a/src/session_client.c
+++ b/src/session_client.c
@@ -1846,7 +1846,23 @@
         module_name = "ietf-netconf-nmda";
         rpc_name = "edit-data";
         break;
-    default:
+    case NC_RPC_ESTABLISHSUB:
+        module_name = "ietf-subscribed-notifications";
+        rpc_name = "establish-subscription";
+        break;
+    case NC_RPC_MODIFYSUB:
+        module_name = "ietf-subscribed-notifications";
+        rpc_name = "modify-subscription";
+        break;
+    case NC_RPC_DELETESUB:
+        module_name = "ietf-subscribed-notifications";
+        rpc_name = "delete-subscription";
+        break;
+    case NC_RPC_KILLSUB:
+        module_name = "ietf-subscribed-notifications";
+        rpc_name = "kill-subscription";
+        break;
+    case NC_RPC_UNKNOWN:
         lyrc = LY_EINT;
         break;
     }
@@ -2148,6 +2164,10 @@
     struct nc_rpc_subscribe *rpc_sub;
     struct nc_rpc_getdata *rpc_getd;
     struct nc_rpc_editdata *rpc_editd;
+    struct nc_rpc_establishsub *rpc_estsub;
+    struct nc_rpc_modifysub *rpc_modsub;
+    struct nc_rpc_deletesub *rpc_delsub;
+    struct nc_rpc_killsub *rpc_killsub;
     struct lyd_node *data, *node;
     const struct lys_module *mod = NULL, *ietfncwd;
     LY_ERR lyrc;
@@ -2213,6 +2233,16 @@
             return NC_MSG_ERROR;
         }
         break;
+    case NC_RPC_ESTABLISHSUB:
+    case NC_RPC_MODIFYSUB:
+    case NC_RPC_DELETESUB:
+    case NC_RPC_KILLSUB:
+        mod = ly_ctx_get_module_implemented(session->ctx, "ietf-subscribed-notifications");
+        if (!mod) {
+            ERR("Session %u: missing \"ietf-subscribed-notifications\" schema in the context.", session->id);
+            return NC_MSG_ERROR;
+        }
+        break;
     case NC_RPC_UNKNOWN:
         ERRINT;
         return NC_MSG_ERROR;
@@ -2538,7 +2568,7 @@
 
         lyd_new_inner(NULL, mod, "create-subscription", 0, &data);
         if (rpc_sub->stream) {
-            if (lyd_new_term(data, mod, "stream", rpc_sub->stream, 0, &data)) {
+            if (lyd_new_term(data, mod, "stream", rpc_sub->stream, 0, NULL)) {
                 lyd_free_tree(data);
                 return NC_MSG_ERROR;
             }
@@ -2656,7 +2686,110 @@
         }
         break;
 
-    default:
+    case NC_RPC_ESTABLISHSUB:
+        rpc_estsub = (struct nc_rpc_establishsub *)rpc;
+
+        lyd_new_inner(NULL, mod, "establish-subscription", 0, &data);
+
+        if (rpc_estsub->filter) {
+            if (!rpc_estsub->filter[0] || (rpc_estsub->filter[0] == '<')) {
+                lyd_new_any(data, mod, "stream-subtree-filter", rpc_estsub->filter, 0, LYD_ANYDATA_XML, 0, &node);
+            } else if (rpc_estsub->filter[0] == '/') {
+                lyd_new_term(data, mod, "stream-xpath-filter", rpc_estsub->filter, 0, &node);
+            } else {
+                lyd_new_term(data, mod, "stream-filter-name", rpc_estsub->filter, 0, &node);
+            }
+            if (!node) {
+                lyd_free_tree(data);
+                return NC_MSG_ERROR;
+            }
+        }
+
+        if (lyd_new_term(data, mod, "stream", rpc_estsub->stream, 0, NULL)) {
+            lyd_free_tree(data);
+            return NC_MSG_ERROR;
+        }
+
+        if (rpc_estsub->start) {
+            if (lyd_new_term(data, mod, "replay-start-time", rpc_estsub->start, 0, NULL)) {
+                lyd_free_tree(data);
+                return NC_MSG_ERROR;
+            }
+        }
+
+        if (rpc_estsub->stop) {
+            if (lyd_new_term(data, mod, "stop-time", rpc_estsub->stop, 0, NULL)) {
+                lyd_free_tree(data);
+                return NC_MSG_ERROR;
+            }
+        }
+
+        if (rpc_estsub->encoding) {
+            if (lyd_new_term(data, mod, "encoding", rpc_estsub->encoding, 0, NULL)) {
+                lyd_free_tree(data);
+                return NC_MSG_ERROR;
+            }
+        }
+        break;
+
+    case NC_RPC_MODIFYSUB:
+        rpc_modsub = (struct nc_rpc_modifysub *)rpc;
+
+        lyd_new_inner(NULL, mod, "modify-subscription", 0, &data);
+
+        sprintf(str, "%u", rpc_modsub->id);
+        if (lyd_new_term(data, mod, "id", str, 0, NULL)) {
+            lyd_free_tree(data);
+            return NC_MSG_ERROR;
+        }
+
+        if (rpc_modsub->filter) {
+            if (!rpc_modsub->filter[0] || (rpc_modsub->filter[0] == '<')) {
+                lyd_new_any(data, mod, "stream-subtree-filter", rpc_modsub->filter, 0, LYD_ANYDATA_XML, 0, &node);
+            } else if (rpc_modsub->filter[0] == '/') {
+                lyd_new_term(data, mod, "stream-xpath-filter", rpc_modsub->filter, 0, &node);
+            } else {
+                lyd_new_term(data, mod, "stream-filter-name", rpc_modsub->filter, 0, &node);
+            }
+            if (!node) {
+                lyd_free_tree(data);
+                return NC_MSG_ERROR;
+            }
+        }
+
+        if (rpc_modsub->stop) {
+            if (lyd_new_term(data, mod, "stop-time", rpc_modsub->stop, 0, NULL)) {
+                lyd_free_tree(data);
+                return NC_MSG_ERROR;
+            }
+        }
+        break;
+
+    case NC_RPC_DELETESUB:
+        rpc_delsub = (struct nc_rpc_deletesub *)rpc;
+
+        lyd_new_inner(NULL, mod, "delete-subscription", 0, &data);
+
+        sprintf(str, "%u", rpc_delsub->id);
+        if (lyd_new_term(data, mod, "id", str, 0, NULL)) {
+            lyd_free_tree(data);
+            return NC_MSG_ERROR;
+        }
+        break;
+
+    case NC_RPC_KILLSUB:
+        rpc_killsub = (struct nc_rpc_killsub *)rpc;
+
+        lyd_new_inner(NULL, mod, "kill-subscription", 0, &data);
+
+        sprintf(str, "%u", rpc_killsub->id);
+        if (lyd_new_term(data, mod, "id", str, 0, NULL)) {
+            lyd_free_tree(data);
+            return NC_MSG_ERROR;
+        }
+        break;
+
+    case NC_RPC_UNKNOWN:
         ERRINT;
         return NC_MSG_ERROR;
     }