blob: 7ce29390ebeab09877db8a4fa5e3e466e14165e0 [file] [log] [blame]
Michal Vasko7bcb48e2016-01-15 10:28:54 +01001/**
2 * \file messages.c
3 * \author Radek Krejci <rkrejci@cesnet.cz>
4 * \brief libnetconf2 - NETCONF messages functions
5 *
6 * Copyright (c) 2015 CESNET, z.s.p.o.
7 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +01008 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
Michal Vasko7bcb48e2016-01-15 10:28:54 +010013 */
14
15#include <ctype.h>
16#include <stdlib.h>
17#include <string.h>
18#include <stdarg.h>
19
20#include <libyang/libyang.h>
21
22#include "libnetconf.h"
Michal Vasko7bcb48e2016-01-15 10:28:54 +010023
24const char *rpcedit_dfltop2str[] = {NULL, "merge", "replace", "none"};
25const char *rpcedit_testopt2str[] = {NULL, "test-then-set", "set", "test-only"};
26const char *rpcedit_erropt2str[] = {NULL, "stop-on-error", "continue-on-error", "rollback-on-error"};
27
28API NC_RPC_TYPE
29nc_rpc_get_type(const struct nc_rpc *rpc)
30{
Michal Vasko7f1c78b2016-01-19 09:52:14 +010031 if (!rpc) {
32 ERRARG;
33 return 0;
34 }
35
Michal Vasko7bcb48e2016-01-15 10:28:54 +010036 return rpc->type;
37}
38
39API struct nc_rpc *
40nc_rpc_generic(const struct lyd_node *data, NC_PARAMTYPE paramtype)
41{
42 struct nc_rpc_generic *rpc;
43
Michal Vasko7f1c78b2016-01-19 09:52:14 +010044 if (!data) {
45 ERRARG;
46 return NULL;
47 }
48
Michal Vasko7bcb48e2016-01-15 10:28:54 +010049 if (data->next || (data->prev != data)) {
50 ERR("Generic RPC must have a single root node.");
51 return NULL;
52 }
53
54 rpc = malloc(sizeof *rpc);
55 if (!rpc) {
56 ERRMEM;
57 return NULL;
58 }
59
60 rpc->type = NC_RPC_GENERIC;
61 rpc->has_data = 1;
62 if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
63 rpc->content.data = lyd_dup(data, 1);
64 } else {
65 rpc->content.data = (struct lyd_node *)data;
66 }
67 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
68
69 return (struct nc_rpc *)rpc;
70}
71
72API struct nc_rpc *
73nc_rpc_generic_xml(const char *xml_str, NC_PARAMTYPE paramtype)
74{
75 struct nc_rpc_generic *rpc;
76
Michal Vasko7f1c78b2016-01-19 09:52:14 +010077 if (!xml_str) {
78 ERRARG;
79 return NULL;
80 }
81
Michal Vasko7bcb48e2016-01-15 10:28:54 +010082 rpc = malloc(sizeof *rpc);
83 if (!rpc) {
84 ERRMEM;
85 return NULL;
86 }
87
88 rpc->type = NC_RPC_GENERIC;
89 rpc->has_data = 0;
90 if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
91 rpc->content.xml_str = strdup(xml_str);
92 } else {
93 rpc->content.xml_str = (char *)xml_str;
94 }
95 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
96
97 return (struct nc_rpc *)rpc;
98}
99
100API struct nc_rpc *
101nc_rpc_getconfig(NC_DATASTORE source, const char *filter, NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype)
102{
103 struct nc_rpc_getconfig *rpc;
104
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100105 if (!source) {
106 ERRARG;
107 return NULL;
108 }
109
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100110 if (filter && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
111 ERR("Filter must either be an XML subtree or an XPath expression.");
112 return NULL;
113 }
114
115 rpc = malloc(sizeof *rpc);
116 if (!rpc) {
117 ERRMEM;
118 return NULL;
119 }
120
121 rpc->type = NC_RPC_GETCONFIG;
122 rpc->source = source;
123 if (filter && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
124 rpc->filter = strdup(filter);
125 } else {
126 rpc->filter = (char *)filter;
127 }
128 rpc->wd_mode = wd_mode;
129 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
130
131 return (struct nc_rpc *)rpc;
132}
133
134API struct nc_rpc *
135nc_rpc_edit(NC_DATASTORE target, NC_RPC_EDIT_DFLTOP default_op, NC_RPC_EDIT_TESTOPT test_opt,
136 NC_RPC_EDIT_ERROPT error_opt, const char *edit_content, NC_PARAMTYPE paramtype)
137{
138 struct nc_rpc_edit *rpc;
139
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100140 if (!target || !edit_content) {
141 ERRARG;
142 return NULL;
143 }
144
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100145 if ((edit_content[0] != '<') && !isalpha(edit_content[0])) {
146 ERR("<edit-config> content must either be a URL or a config (XML).");
147 return NULL;
148 }
149
150 rpc = malloc(sizeof *rpc);
151 if (!rpc) {
152 ERRMEM;
153 return NULL;
154 }
155
156 rpc->type = NC_RPC_EDIT;
157 rpc->target = target;
158 rpc->default_op = default_op;
159 rpc->test_opt = test_opt;
160 rpc->error_opt = error_opt;
161 if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
162 rpc->edit_cont = strdup(edit_content);
163 } else {
164 rpc->edit_cont = (char *)edit_content;
165 }
166 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
167
168 return (struct nc_rpc *)rpc;
169}
170
171API struct nc_rpc *
172nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source, const char *url_or_config_src,
173 NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype)
174{
175 struct nc_rpc_copy *rpc;
176
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100177 if (!target || !source) {
178 ERRARG;
179 return NULL;
180 }
181
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100182 if (url_or_config_src && (url_or_config_src[0] != '<') && !isalpha(url_or_config_src[0])) {
183 ERR("<copy-config> source is neither a URL nor a config (XML).");
184 return NULL;
185 }
186
187 rpc = malloc(sizeof *rpc);
188 if (!rpc) {
189 ERRMEM;
190 return NULL;
191 }
192
193 rpc->type = NC_RPC_COPY;
194 rpc->target = target;
195 if (url_trg && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
196 rpc->url_trg = strdup(url_trg);
197 } else {
198 rpc->url_trg = (char *)url_trg;
199 }
200 rpc->source = source;
201 if (url_or_config_src && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
202 rpc->url_config_src = strdup(url_or_config_src);
203 } else {
204 rpc->url_config_src = (char *)url_or_config_src;
205 }
206 rpc->wd_mode = wd_mode;
207 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
208
209 return (struct nc_rpc *)rpc;
210}
211
212API struct nc_rpc *
213nc_rpc_delete(NC_DATASTORE target, const char *url, NC_PARAMTYPE paramtype)
214{
215 struct nc_rpc_delete *rpc;
216
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100217 if (!target) {
218 ERRARG;
219 return NULL;
220 }
221
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100222 rpc = malloc(sizeof *rpc);
223 if (!rpc) {
224 ERRMEM;
225 return NULL;
226 }
227
228 rpc->type = NC_RPC_DELETE;
229 rpc->target = target;
230 if (url && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
231 rpc->url = strdup(url);
232 } else {
233 rpc->url = (char *)url;
234 }
235 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
236
237 return (struct nc_rpc *)rpc;
238}
239
240API struct nc_rpc *
241nc_rpc_lock(NC_DATASTORE target)
242{
243 struct nc_rpc_lock *rpc;
244
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100245 if (!target) {
246 ERRARG;
247 return NULL;
248 }
249
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100250 rpc = malloc(sizeof *rpc);
251 if (!rpc) {
252 ERRMEM;
253 return NULL;
254 }
255
256 rpc->type = NC_RPC_LOCK;
257 rpc->target = target;
258
259 return (struct nc_rpc *)rpc;
260}
261
262API struct nc_rpc *
263nc_rpc_unlock(NC_DATASTORE target)
264{
265 struct nc_rpc_lock *rpc;
266
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100267 if (!target) {
268 ERRARG;
269 return NULL;
270 }
271
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100272 rpc = malloc(sizeof *rpc);
273 if (!rpc) {
274 ERRMEM;
275 return NULL;
276 }
277
278 rpc->type = NC_RPC_UNLOCK;
279 rpc->target = target;
280
281 return (struct nc_rpc *)rpc;
282}
283
284API struct nc_rpc *
285nc_rpc_get(const char *filter, NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype)
286{
287 struct nc_rpc_get *rpc;
288
289 if (filter && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
290 ERR("Filter must either be an XML subtree or an XPath expression.");
291 return NULL;
292 }
293
294 rpc = malloc(sizeof *rpc);
295 if (!rpc) {
296 ERRMEM;
297 return NULL;
298 }
299
300 rpc->type = NC_RPC_GET;
301 if (filter && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
302 rpc->filter = strdup(filter);
303 } else {
304 rpc->filter = (char *)filter;
305 }
306 rpc->wd_mode = wd_mode;
307 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
308
309 return (struct nc_rpc *)rpc;
310}
311
312API struct nc_rpc *
313nc_rpc_kill(uint32_t session_id)
314{
315 struct nc_rpc_kill *rpc;
316
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100317 if (!session_id) {
318 ERRARG;
319 return NULL;
320 }
321
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100322 rpc = malloc(sizeof *rpc);
323 if (!rpc) {
324 ERRMEM;
325 return NULL;
326 }
327
328 rpc->type = NC_RPC_KILL;
329 rpc->sid = session_id;
330
331 return (struct nc_rpc *)rpc;
332}
333
334API struct nc_rpc *
335nc_rpc_commit(int confirmed, uint32_t confirm_timeout, const char *persist, const char *persist_id,
336 NC_PARAMTYPE paramtype)
337{
338 struct nc_rpc_commit *rpc;
339
340 rpc = malloc(sizeof *rpc);
341 if (!rpc) {
342 ERRMEM;
343 return NULL;
344 }
345
346 rpc->type = NC_RPC_COMMIT;
347 rpc->confirmed = confirmed;
348 rpc->confirm_timeout = confirm_timeout;
349 if (persist && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
350 rpc->persist = strdup(persist);
351 } else {
352 rpc->persist = (char *)persist;
353 }
354 if (persist_id && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
355 rpc->persist_id = strdup(persist_id);
356 } else {
357 rpc->persist_id = (char *)persist_id;
358 }
359 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
360
361 return (struct nc_rpc *)rpc;
362}
363
364API struct nc_rpc *
365nc_rpc_discard(void)
366{
367 struct nc_rpc *rpc;
368
369 rpc = malloc(sizeof *rpc);
370 if (!rpc) {
371 ERRMEM;
372 return NULL;
373 }
374
375 rpc->type = NC_RPC_DISCARD;
376
377 return rpc;
378}
379
380API struct nc_rpc *
381nc_rpc_cancel(const char *persist_id, NC_PARAMTYPE paramtype)
382{
383 struct nc_rpc_cancel *rpc;
384
385 rpc = malloc(sizeof *rpc);
386 if (!rpc) {
387 ERRMEM;
388 return NULL;
389 }
390
391 rpc->type = NC_RPC_CANCEL;
392 if (persist_id && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
393 rpc->persist_id = strdup(persist_id);
394 } else {
395 rpc->persist_id = (char *)persist_id;
396 }
397 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
398
399 return (struct nc_rpc *)rpc;
400}
401
402API struct nc_rpc *
403nc_rpc_validate(NC_DATASTORE source, const char *url_or_config, NC_PARAMTYPE paramtype)
404{
405 struct nc_rpc_validate *rpc;
406
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100407 if (!source) {
408 ERRARG;
409 return NULL;
410 }
411
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100412 if (url_or_config && (url_or_config[0] != '<') && !isalpha(url_or_config[0])) {
413 ERR("<validate> source is neither a URL nor a config (XML).");
414 return NULL;
415 }
416
417 rpc = malloc(sizeof *rpc);
418 if (!rpc) {
419 ERRMEM;
420 return NULL;
421 }
422
423 rpc->type = NC_RPC_VALIDATE;
424 rpc->source = source;
425 if (url_or_config && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
426 rpc->url_config_src = strdup(url_or_config);
427 } else {
428 rpc->url_config_src = (char *)url_or_config;
429 }
430 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
431
432 return (struct nc_rpc *)rpc;
433}
434
435API struct nc_rpc *
436nc_rpc_getschema(const char *identifier, const char *version, const char *format, NC_PARAMTYPE paramtype)
437{
438 struct nc_rpc_getschema *rpc;
439
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100440 if (!identifier) {
441 ERRARG;
442 return NULL;
443 }
444
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100445 rpc = malloc(sizeof *rpc);
446 if (!rpc) {
447 ERRMEM;
448 return NULL;
449 }
450
451 rpc->type = NC_RPC_GETSCHEMA;
452 if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
453 rpc->identifier = strdup(identifier);
454 } else {
455 rpc->identifier = (char *)identifier;
456 }
457 if (version && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
458 rpc->version = strdup(version);
459 } else {
460 rpc->version = (char *)version;
461 }
462 if (format && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
463 rpc->format = strdup(format);
464 } else {
465 rpc->format = (char *)format;
466 }
467 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
468
469 return (struct nc_rpc *)rpc;
470}
471
472API struct nc_rpc *
473nc_rpc_subscribe(const char *stream_name, const char *filter, const char *start_time, const char *stop_time,
474 NC_PARAMTYPE paramtype)
475{
476 struct nc_rpc_subscribe *rpc;
477
478 if (filter && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
479 ERR("Filter must either be an XML subtree or an XPath expression.");
480 return NULL;
481 }
482
483 rpc = malloc(sizeof *rpc);
484 if (!rpc) {
485 ERRMEM;
486 return NULL;
487 }
488
489 rpc->type = NC_RPC_SUBSCRIBE;
490 if (stream_name && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
491 rpc->stream = strdup(stream_name);
492 } else {
493 rpc->stream = (char *)stream_name;
494 }
495 if (filter && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
496 rpc->filter = strdup(filter);
497 } else {
498 rpc->filter = (char *)filter;
499 }
500 if (start_time && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
501 rpc->start = strdup(start_time);
502 } else {
503 rpc->start = (char *)start_time;
504 }
505 if (stop_time && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
506 rpc->stop = strdup(stop_time);
507 } else {
508 rpc->stop = (char *)stop_time;
509 }
510 rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
511
512 return (struct nc_rpc *)rpc;
513}
514
515API void
516nc_rpc_free(struct nc_rpc *rpc)
517{
518 struct nc_rpc_generic *rpc_generic;
519 struct nc_rpc_getconfig *rpc_getconfig;
520 struct nc_rpc_edit *rpc_edit;
521 struct nc_rpc_copy *rpc_copy;
522 struct nc_rpc_delete *rpc_delete;
523 struct nc_rpc_get *rpc_get;
524 struct nc_rpc_commit *rpc_commit;
525 struct nc_rpc_cancel *rpc_cancel;
526 struct nc_rpc_validate *rpc_validate;
527 struct nc_rpc_getschema *rpc_getschema;
528 struct nc_rpc_subscribe *rpc_subscribe;
529
530 if (!rpc) {
531 return;
532 }
533
534 switch (rpc->type) {
535 case NC_RPC_GENERIC:
536 rpc_generic = (struct nc_rpc_generic *)rpc;
537 if (rpc_generic->free) {
538 if (rpc_generic->has_data) {
539 lyd_free(rpc_generic->content.data);
540 } else {
541 free(rpc_generic->content.xml_str);
542 }
543 }
544 break;
545 case NC_RPC_GETCONFIG:
546 rpc_getconfig = (struct nc_rpc_getconfig *)rpc;
547 if (rpc_getconfig->free) {
548 free(rpc_getconfig->filter);
549 }
550 break;
551 case NC_RPC_EDIT:
552 rpc_edit = (struct nc_rpc_edit *)rpc;
553 if (rpc_edit->free) {
554 free(rpc_edit->edit_cont);
555 }
556 break;
557 case NC_RPC_COPY:
558 rpc_copy = (struct nc_rpc_copy *)rpc;
559 if (rpc_copy->free) {
560 free(rpc_copy->url_config_src);
561 }
562 break;
563 case NC_RPC_DELETE:
564 rpc_delete = (struct nc_rpc_delete *)rpc;
565 if (rpc_delete->free) {
566 free(rpc_delete->url);
567 }
568 break;
569 case NC_RPC_GET:
570 rpc_get = (struct nc_rpc_get *)rpc;
571 if (rpc_get->free) {
572 free(rpc_get->filter);
573 }
574 break;
575 case NC_RPC_COMMIT:
576 rpc_commit = (struct nc_rpc_commit *)rpc;
577 if (rpc_commit->free) {
578 free(rpc_commit->persist);
579 free(rpc_commit->persist_id);
580 }
581 break;
582 case NC_RPC_CANCEL:
583 rpc_cancel = (struct nc_rpc_cancel *)rpc;
584 if (rpc_cancel->free) {
585 free(rpc_cancel->persist_id);
586 }
587 break;
588 case NC_RPC_VALIDATE:
589 rpc_validate = (struct nc_rpc_validate *)rpc;
590 if (rpc_validate->free) {
591 free(rpc_validate->url_config_src);
592 }
593 break;
594 case NC_RPC_GETSCHEMA:
595 rpc_getschema = (struct nc_rpc_getschema *)rpc;
596 if (rpc_getschema->free) {
597 free(rpc_getschema->identifier);
598 free(rpc_getschema->version);
599 free(rpc_getschema->format);
600 }
601 break;
602 case NC_RPC_SUBSCRIBE:
603 rpc_subscribe = (struct nc_rpc_subscribe *)rpc;
604 if (rpc_subscribe->free) {
605 free(rpc_subscribe->stream);
606 free(rpc_subscribe->filter);
607 free(rpc_subscribe->start);
608 free(rpc_subscribe->stop);
609 }
610 break;
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100611 default:
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100612 /* nothing special needed */
613 break;
614 }
615
616 free(rpc);
617}
618
619API void
620nc_reply_free(struct nc_reply *reply)
621{
Michal Vasko1a38c862016-01-15 15:50:07 +0100622 struct nc_client_reply_error *error;
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100623 struct nc_reply_data *data;
624 uint32_t i, j;
625
626 if (!reply) {
627 return;
628 }
629
630 switch (reply->type) {
631 case NC_RPL_DATA:
632 data = (struct nc_reply_data *)reply;
633 lyd_free_withsiblings(data->data);
634 break;
635
636 case NC_RPL_OK:
637 /* nothing to free */
638 break;
639
640 case NC_RPL_ERROR:
Michal Vasko1a38c862016-01-15 15:50:07 +0100641 error = (struct nc_client_reply_error *)reply;
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100642 for (i = 0; i < error->count; ++i) {
643 lydict_remove(error->ctx, error->err[i].type);
644 lydict_remove(error->ctx, error->err[i].tag);
645 lydict_remove(error->ctx, error->err[i].severity);
646 lydict_remove(error->ctx, error->err[i].apptag);
647 lydict_remove(error->ctx, error->err[i].path);
648 lydict_remove(error->ctx, error->err[i].message);
649 lydict_remove(error->ctx, error->err[i].message_lang);
650 lydict_remove(error->ctx, error->err[i].sid);
651 for (j = 0; j < error->err[i].attr_count; ++j) {
652 lydict_remove(error->ctx, error->err[i].attr[j]);
653 }
654 free(error->err[i].attr);
655 for (j = 0; j < error->err[i].elem_count; ++j) {
656 lydict_remove(error->ctx, error->err[i].elem[j]);
657 }
658 free(error->err[i].elem);
659 for (j = 0; j < error->err[i].ns_count; ++j) {
660 lydict_remove(error->ctx, error->err[i].ns[j]);
661 }
662 free(error->err[i].ns);
663 for (j = 0; j < error->err[i].other_count; ++j) {
664 lyxml_free(error->ctx, error->err[i].other[j]);
665 }
666 free(error->err[i].other);
667 }
668 free(error->err);
669 break;
670
671 case NC_RPL_NOTIF:
672 nc_notif_free((struct nc_notif *)reply);
Michal Vasko11d142a2016-01-19 15:58:24 +0100673 return;
Michal Vasko7bcb48e2016-01-15 10:28:54 +0100674 }
675
676 free(reply);
677}
Michal Vasko495c9462016-01-15 11:27:43 +0100678
679API void
680nc_notif_free(struct nc_notif *notif)
681{
682 if (!notif) {
683 return;
684 }
685
686 lydict_remove(notif->tree->schema->module->ctx, notif->datetime);
687 lyd_free(notif->tree);
688 free(notif);
689}