/*
 *
  ***** BEGIN LICENSE BLOCK *****
 
  Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
  Copyright (C) 2017-2019 Olof Hagsand
  Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)

  This file is part of CLIXON.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  Alternatively, the contents of this file may be used under the terms of
  the GNU General Public License Version 3 or later (the "GPL"),
  in which case the provisions of the GPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of the GPL, and not to allow others to
  use your version of this file under the terms of Apache License version 2, 
  indicate your decision by deleting the provisions above and replace them with
  the  notice and other provisions required by the GPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the Apache License version 2 or the GPL.

  ***** END LICENSE BLOCK *****

 * Netconf library functions. See RFC6241
 * Functions to generate a netconf error message come in two forms: xml-tree and 
 * cbuf. XML tree is preferred.
 */

#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <sys/param.h>

/* cligen */
#include <cligen/cligen.h>

/* clixon */
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_string.h"
#include "clixon_err.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_log.h"
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_data.h"
#include "clixon_xml_bind.h"
#include "clixon_xml_map.h"
#include "clixon_xml_io.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_yang_module.h"
#include "clixon_yang_parse_lib.h"
#include "clixon_plugin.h"
#include "clixon_netconf_lib.h"
#include "clixon_netconf_input.h"

/* Mapping between RFC6243 withdefaults strings <--> ints
 */
static const map_str2int wdmap[] = {
    {"report-all",        WITHDEFAULTS_REPORT_ALL},
    {"trim",              WITHDEFAULTS_TRIM},
    {"explicit",          WITHDEFAULTS_EXPLICIT},
    {"report-all-tagged", WITHDEFAULTS_REPORT_ALL_TAGGED}
};

/*! Map from with-defaults ints to strings
 * @param[in] int  Integer representation of withdefaults values
 * @retval    str  String representation of withdefaults values
 */
char *
withdefaults_int2str(int keyword)
{
    return (char*)clicon_int2str(wdmap, keyword);
}

/*! Map from with-defaults strings to ints
 * @param[in] str  String representation of withdefaults values
 * @retval    int  Integer representation of withdefaults values
 */
int
withdefaults_str2int(char *str)
{
    return clicon_str2int(wdmap, str);
}

/*! Create Netconf in-use error XML tree according to RFC 6241 Appendix A
 *
 * The request requires a resource that already is in use.
 * @param[out] cb       CLIgen buf. Error XML is written in this buffer
 * @param[in]  type     Error type: "application" or "protocol"
 * @param[in]  message  Error message (will be XML encoded)
 */
int
netconf_in_use(cbuf *cb,
               char *type,
               char *message)
{
    int   retval = -1;
    char *encstr = NULL;
    
    if (cprintf(cb, "<rpc-reply xmlns=\"%s\"><rpc-error>"
                "<error-type>%s</error-type>"
                "<error-tag>in-use</error-tag>"
                "<error-severity>error</error-severity>",
                NETCONF_BASE_NAMESPACE,
                type) <0)
        goto err;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
            goto err;
    }
    if (cprintf(cb, "</rpc-error></rpc-reply>") <0)
        goto err;
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
 err:
    clicon_err(OE_XML, errno, "cprintf");
    goto done;
}

/*! Create Netconf invalid-value error XML tree according to RFC 6241 Appendix A
 *
 * The request specifies an unacceptable value for one or more parameters.
 * @param[out] xret    Error XML tree. Free with xml_free after use
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  message Error message (will be XML encoded)
 */
int
netconf_invalid_value_xml(cxobj **xret,
                          char   *type,
                          char   *message)
{
    int    retval =-1;
    cxobj *xerr = NULL;
    char  *encstr = NULL;
    
    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL,
                            "<error-type>%s</error-type>"
                            "<error-tag>invalid-value</error-tag>"
                            "<error-severity>error</error-severity>", type) < 0)
        goto done;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL,
                                "<error-message>%s</error-message>", encstr) < 0)
            goto done;
    }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf invalid-value error XML tree according to RFC 6241 Appendix A
 *
 * The request specifies an unacceptable value for one or more parameters.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  message Error message (will be XML encoded)
 */
int
netconf_invalid_value(cbuf *cb,
                      char *type,
                      char *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_invalid_value_xml(&xret, type, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf too-big error XML tree according to RFC 6241 Appendix A
 *
 * The request or response (that would be generated) is 
 * too large for the implementation to handle.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "transport", "rpc", "application", "protocol"
 * @param[in]  message Error message (will be XML encoded)
 */
int
netconf_too_big(cbuf *cb,
                char *type,
                char *message)
{
    int     retval = -1;
    char *encstr = NULL;

    if (cprintf(cb, "<rpc-reply xmlns=\"%s\"><rpc-error>"
                "<error-type>%s</error-type>"
                "<error-tag>too-big</error-tag>"
                "<error-severity>error</error-severity>",
                NETCONF_BASE_NAMESPACE,
                type) <0)
        goto err;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
            goto err;
    }
    if (cprintf(cb, "</rpc-error></rpc-reply>") <0)
        goto err;
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
 err:
    clicon_err(OE_XML, errno, "cprintf");
    goto done;
}

/*! Create Netconf missing-attribute error XML tree according to RFC 6241 App A
 *
 * An expected attribute is missing.
 * @param[out] xret    Error XML tree. Free with xml_free after use
 * @param[in]  type    Error type: "rpc", "application" or "protocol"
 * @param[in]  attr    bad-attribute and/or bad-element xml
 * @param[in]  message Error message (will be XML encoded)
 */
int
netconf_missing_attribute_xml(cxobj **xret,
                              char   *type,
                              char   *attr,
                              char   *message)
{
    int   retval = -1;
    cxobj *xerr = NULL;
    char  *encstr = NULL;

    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>%s</error-type>"
                            "<error-tag>missing-attribute</error-tag>"
                            "<error-info><bad-attribute>%s</bad-attribute></error-info>"
                            "<error-severity>error</error-severity>", type, attr) < 0)
        goto done;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-message>%s</error-message>",
                                encstr) < 0)
            goto done;
    }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf missing-attribute error XML tree according to RFC 6241 App A
 *
 * An expected attribute is missing.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "rpc", "application" or "protocol"
 * @param[in]  attr    bad-attribute 
 * @param[in]  message Error message (will be XML encoded) or NULL
 */
int
netconf_missing_attribute(cbuf *cb,
                          char *type,
                          char *attr,
                          char *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_missing_attribute_xml(&xret, type, attr, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf bad-attribute error XML tree according to RFC 6241 App A
 *
 * An attribute value is not correct; e.g., wrong type,
 * out of range, pattern mismatch.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "rpc", "application" or "protocol"
 * @param[in]  info    Attribute name
 * @param[in]  message Error message (will be XML encoded)
 */
int
netconf_bad_attribute(cbuf *cb,
                      char *type,
                      char *info,
                      char *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_bad_attribute_xml(&xret, type, info, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf bad-attribute error XML tree according to RFC 6241 App A
 *
 * An attribute value is not correct; e.g., wrong type,
 * out of range, pattern mismatch.
 * @param[out] xret    Error XML tree. Free with xml_free after use
 * @param[in]  type    Error type: "rpc", "application" or "protocol"
 * @param[in]  info    Attribute name
 * @param[in]  message Error message (will be XML encoded)
 */
int
netconf_bad_attribute_xml(cxobj **xret,
                          char   *type,
                          char   *info,
                          char   *message)
{
    int   retval = -1;
    cxobj *xerr = NULL;
    char  *encstr = NULL;

    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>%s</error-type>"
                            "<error-tag>bad-attribute</error-tag>"
                            "<error-info><bad-attribute>%s</bad-attribute></error-info>"
                            "<error-severity>error</error-severity>", type, info) < 0)
        goto done;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-message>%s</error-message>",
                                encstr) < 0)
            goto done;
    }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf unknown-attribute error XML tree according to RFC 6241 App A
 *
 * An unexpected attribute is present.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "rpc", "application" or "protocol"
 * @param[in]  info    bad-attribute or bad-element xml
 * @param[in]  message Error message
 */
int
netconf_unknown_attribute(cbuf *cb,
                          char *type,
                          char *info,
                          char *message)
{
    int   retval = -1;
    char *encstr = NULL;

    if (cprintf(cb, "<rpc-reply xmlns=\"%s\"><rpc-error>"
                "<error-type>%s</error-type>"
                "<error-tag>unknown-attribute</error-tag>"
                "<error-info>%s</error-info>"
                "<error-severity>error</error-severity>",
                NETCONF_BASE_NAMESPACE, type, info) <0)
        goto err;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
            goto err;
    }
    if (cprintf(cb, "</rpc-error></rpc-reply>") <0)
        goto err;
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
 err:
    clicon_err(OE_XML, errno, "cprintf");
    goto done;
}

/*! Common Netconf element XML tree according to RFC 6241 App A
 *
 * @param[out] xret    Error XML tree. Free with xml_free after use
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  tag     Error tag
 * @param[in]  element bad-element xml
 * @param[in]  message Error message (will be XML encoded)
 */
static int
netconf_common_xml(cxobj **xret,
                   char   *type,
                   char   *tag,
                   char   *infotag,
                   char   *element,
                   char   *message)
{
    int    retval =-1;
    cxobj *xerr;
    char  *encstr = NULL;
    
    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
        if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>%s</error-type>"
                            "<error-tag>%s</error-tag>"
                            "<error-info><%s>%s</%s></error-info>"
                            "<error-severity>error</error-severity>",
                            type, tag, infotag, element, infotag) < 0)
        goto done;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-message>%s</error-message>",
                                encstr) < 0)
            goto done;
    }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}    

/*! Create Netconf missing-element error XML tree according to RFC 6241 App A
 *
 * An expected element is missing.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  info    bad-element xml
 * @param[in]  message Error message
 */
int
netconf_missing_element(cbuf      *cb, 
                        char      *type,
                        char      *element,
                        char      *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_common_xml(&xret, type, "missing-element",
                           "bad-element", element, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf missing-element error XML tree according to RFC 6241 App A
 * @param[out] xret    Error XML tree. Free with xml_free after use
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  element bad-element xml
 * @param[in]  message Error message
 */
int
netconf_missing_element_xml(cxobj **xret,
                            char   *type,
                            char   *element,
                            char   *message)
{
    return netconf_common_xml(xret, type, "missing-element",
                              "bad-element", element, message);
}

/*! Create Netconf bad-element error XML tree according to RFC 6241 App A
 *
 * An element value is not correct; e.g., wrong type, out of range, 
 * pattern mismatch.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  elemnt  Bad element name
 * @param[in]  message Error message
 */
int
netconf_bad_element(cbuf *cb,
                    char *type,
                    char *element,
                    char *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_common_xml(&xret, type, "bad-element",
                           "bad-element",element, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

int
netconf_bad_element_xml(cxobj **xret,
                        char   *type,
                        char   *element,
                        char   *message)
{
    return netconf_common_xml(xret, type, "bad-element", "bad-element", element, message);
}

/*! Create Netconf unknown-element error XML tree according to RFC 6241 App A
 *
 * An unexpected element is present.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  element Bad element name
 * @param[in]  message Error message
 */
int
netconf_unknown_element(cbuf *cb,
                        char *type,
                        char *element,
                        char *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_common_xml(&xret, type, "unknown-element",
                           "bad-element", element, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf unknown-element error XML tree according to RFC 6241 App A
 *
 * An unexpected element is present.
 * @param[out] xret    XML buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  element Bad element name
 * @param[in]  message Error message
 */
int
netconf_unknown_element_xml(cxobj **xret,
                            char   *type,
                            char   *element,
                            char   *message)
{
    return netconf_common_xml(xret, type, "unknown-element",
                              "bad-element", element, message);
}

/*! Create Netconf unknown-namespace error XML tree according to RFC 6241 App A
 *
 * An unexpected namespace is present.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  info    bad-element or bad-namespace xml
 * @param[in]  message Error message
 */
int
netconf_unknown_namespace(cbuf *cb,
                          char *type,
                          char *ns,
                          char *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_common_xml(&xret, type, "unknown-namespace",
                           "bad-namespace", ns, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

int
netconf_unknown_namespace_xml(cxobj **xret,
                              char   *type,
                              char   *ns,
                              char   *message)
{
    return netconf_common_xml(xret, type, "unknown-namespace",
                              "bad-namespace", ns, message);
}

/*! Create Netconf access-denied error cbuf according to RFC 6241 App A
 *
 * Access to the requested protocol operation or data model is denied because 
 * authorization failed.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  message Error message
 * @see netconf_access_denied_xml  Same but returns XML tree
 */
int
netconf_access_denied(cbuf *cb,
                      char *type,
                      char *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_access_denied_xml(&xret, type, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf access-denied error XML tree according to RFC 6241 App A
 *
 * Access to the requested protocol operation or data model is denied because
 * authorization failed.
 * @param[out] xret    Error XML tree. Free with xml_free after use
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  message Error message  (will be XML encoded)
 * @code
 *  cxobj *xret = NULL;
 *  if (netconf_access_denied_xml(&xret, "protocol", "Unauthorized") < 0)
 *    err;
 *  xml_free(xret);
 * @endcode
 * @see netconf_access_denied  Same but returns cligen buffer
 */
int
netconf_access_denied_xml(cxobj **xret,
                          char   *type,
                          char   *message)
{
    int    retval =-1;
    cxobj *xerr;
    char  *encstr = NULL;

    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>%s</error-type>"
                            "<error-tag>access-denied</error-tag>"
                            "<error-severity>error</error-severity>", type) < 0)
        goto done;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-message>%s</error-message>",
                                encstr) < 0)
            goto done;
    }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf lock-denied error XML tree according to RFC 6241 App A
 *
 * Access to the requested lock is denied because the lock is currently held
 * by another entity.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  info    session-id xml
 * @param[in]  message Error message
 */
int
netconf_lock_denied(cbuf *cb,
                    char *info,
                    char *message)
{
    int   retval = -1;
    char *encstr = NULL;

    if (cprintf(cb, "<rpc-reply xmlns=\"%s\"><rpc-error>"
                "<error-type>protocol</error-type>"
                "<error-tag>lock-denied</error-tag>"
                "<error-info>%s</error-info>"
                "<error-severity>error</error-severity>",
                NETCONF_BASE_NAMESPACE, info) <0)
        goto err;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
            goto err;
    }
    if (cprintf(cb, "</rpc-error></rpc-reply>") <0)
        goto err;
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
 err:
    clicon_err(OE_XML, errno, "cprintf");
    goto done;
}

/*! Create Netconf resource-denied error XML tree according to RFC 6241 App A
 *
 * Request could not be completed because of insufficient resources.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "transport, "rpc", "application", "protocol"
 * @param[in]  message Error message
 */
int
netconf_resource_denied(cbuf *cb,
                        char *type,
                        char *message)
{
    int   retval = -1;
    char *encstr = NULL;
    
    if (cprintf(cb, "<rpc-reply xmlns=\"%s\"><rpc-error>"
                "<error-type>%s</error-type>"
                "<error-tag>resource-denied</error-tag>"
                "<error-severity>error</error-severity>",
                NETCONF_BASE_NAMESPACE, type) <0)
        goto err;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
            goto err;
    }
    if (cprintf(cb, "</rpc-error></rpc-reply>") <0)
        goto err;
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
 err:
    clicon_err(OE_XML, errno, "cprintf");
    goto done;
}

/*! Create Netconf rollback-failed error XML tree according to RFC 6241 App A
 *
 * Request to roll back some configuration change (via rollback-on-error or 
 * <discard-changes> operations) was not completed for some reason.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  message Error message
 */
int
netconf_rollback_failed(cbuf *cb,
                        char *type,
                        char *message)
{
    int   retval = -1;
    char *encstr = NULL;

    if (cprintf(cb, "<rpc-reply xmlns=\"%s\"><rpc-error>"
                "<error-type>%s</error-type>"
                "<error-tag>rollback-failed</error-tag>"
                "<error-severity>error</error-severity>",
                NETCONF_BASE_NAMESPACE, type) <0)
        goto err;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
            goto err;
    }
    if (cprintf(cb, "</rpc-error></rpc-reply>") <0)
        goto err;
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
 err:
    clicon_err(OE_XML, errno, "cprintf");
    goto done;
}

/*! Create Netconf data-exists error XML tree according to RFC 6241 Appendix A
 *
 * Request could not be completed because the relevant
 * data model content already exists.  For example,
 * a "create" operation was attempted on data that already exists.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  message Error message
 */
int
netconf_data_exists(cbuf      *cb, 
                    char      *message)
{
    int   retval = -1;
    char *encstr = NULL;

    if (cprintf(cb, "<rpc-reply xmlns=\"%s\"><rpc-error>"
                "<error-type>application</error-type>"
                "<error-tag>data-exists</error-tag>"
                "<error-severity>error</error-severity>",
                NETCONF_BASE_NAMESPACE) <0)
        goto err;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
            goto err;
    }
    if (cprintf(cb, "</rpc-error></rpc-reply>") <0)
        goto err;
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
 err:
    clicon_err(OE_XML, errno, "cprintf");
    goto done;
}

/*! Create Netconf data-missing error XML tree according to RFC 6241 App A
 *
 * Request could not be completed because the relevant data model content 
 * does not exist.  For example, a "delete" operation was attempted on
 * data that does not exist.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  message Error message
 */
int
netconf_data_missing(cbuf *cb,
                     char *message)
{
    int   retval = -1;
    cxobj *xret = NULL;

    if (netconf_data_missing_xml(&xret, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf data-missing error XML tree according to RFC 6241 App A / RFC 7950 15.6(choice)
 *
 * Request could not be completed because the relevant data model content 
 * does not exist.  For example, a "delete" operation was attempted on
 * data that does not exist.
 * @param[out] xret    Error XML tree. Free with xml_free after use
 * @param[in]  message Error message
 */
int
netconf_data_missing_xml(cxobj **xret,
                         char   *message)
{
    int   retval = -1;
    char *encstr = NULL;
    cxobj *xerr;

    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, 
                            "<error-type>application</error-type>"
                            "<error-tag>data-missing</error-tag>") < 0)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, 
                            "<error-severity>error</error-severity>") < 0)
        goto done;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL,
                                "<error-message>%s</error-message>", encstr) < 0)
            goto done;
    }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf data-missing / missing-choice as defeind in RFC 7950 15.6
 *
 * If a NETCONF operation would result in configuration data where no
 * nodes exists in a mandatory choice, the following error MUST be
 * returned:
 * @param[out] xret       Error XML tree. Free with xml_free after use
 * @param[in]  x          Element with missing choice
 * @param[in]  name       Name of missing mandatory choice   
 * @param[in]  message    Error message
 */
int
netconf_missing_choice_xml(cxobj **xret,
                           cxobj  *x,
                           char   *name,
                           char   *message)
{
    int    retval = -1;
    char  *encstr = NULL;
    cxobj *xerr;
    char  *path = NULL;
    char  *encpath = NULL;

    if (xret == NULL || name == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret or name is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    /* error-path:     Path to the element with the missing choice. */
    if (xml2xpath(x, NULL, 0, 0, &path) < 0)
        goto done;
    if (xml_chardata_encode(&encpath, "%s", path) < 0)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, 
                            "<error-type>application</error-type>"
                            "<error-tag>data-missing</error-tag>"
                            "<error-app-tag>missing-choice</error-app-tag>"
                            "<error-path>%s</error-path>"
                            "<error-info><missing-choice xmlns=\"%s\">%s</missing-choice></error-info>"
                            "<error-severity>error</error-severity>",
                            encpath,
                            YANG_XML_NAMESPACE,
                            name) < 0)
        goto done;
    if (message){
        if (xml_chardata_encode(&encstr, "%s", message) < 0)
            goto done;
        if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL,
                                "<error-message>%s</error-message>", encstr) < 0)
            goto done;
    }
    retval = 0;
 done:
    if (path)
        free(path);
    if (encstr)
        free(encstr);
    if (encpath)
        free(encpath);
    return retval;
}

/*! Create Netconf operation-not-supported error XML according to RFC 6241 App A
 *
 * Request could not be completed because the requested operation is not
 * supported by this implementation.
 * @param[out] xret    Error XML tree 
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  message Error message
 * @code
 *  cxobj *xret = NULL;
 *  if (netconf_operation_not_supported_xml(&xret, "protocol", "Unauthorized") < 0)
 *    err;
 *  xml_free(xret);
 * @endcode
 * @see netconf_operation_not_supported  Same but returns cligen buffer
 */
int
netconf_operation_not_supported_xml(cxobj **xret,
                                    char   *type,
                                    char   *message)
{
    int   retval =-1;
    cxobj *xerr;
    char  *encstr = NULL;
    
    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>%s</error-type>"
                            "<error-tag>operation-not-supported</error-tag>"
                            "<error-severity>error</error-severity>",
                            type) < 0)
        goto done;
    if (message){
         if (xml_chardata_encode(&encstr, "%s", message) < 0)
             goto done;
         if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-message>%s</error-message>",
                                 encstr) < 0)
        goto done;
    }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf operation-not-supported error XML according to RFC 6241 App A
 *
 * Request could not be completed because the requested operation is not
 * supported by this implementation.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "application" or "protocol"
 * @param[in]  message Error message
 */
int
netconf_operation_not_supported(cbuf *cb,
                                char *type,
                                char *message)
{
    int   retval = -1;
    cxobj *xret = NULL;

    if (netconf_operation_not_supported_xml(&xret, type, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf operation-failed error XML tree according to RFC 6241 App A
 *
 * Request could not be completed because the requested operation failed for
 * some reason not covered by any other error condition.
 * @param[out] cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]  type    Error type: "rpc", "application" or "protocol"
 * @param[in]  message Error message (will be XML encoded)
 * @see netconf_operation_failed_xml  Same but returns XML tree
 */
int
netconf_operation_failed(cbuf  *cb,
                         char  *type,
                         char  *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_operation_failed_xml(&xret, type, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf operation-failed error XML tree according to RFC 6241 App A
 *
 * Request could not be completed because the requested operation failed for
 * some reason not covered by any other error condition.
 * @param[out] xret    Error XML tree 
 * @param[in]  type    Error type: "rpc", "application" or "protocol"
 * @param[in]  message Error message (will be XML encoded)
 * @code
 *  cxobj *xret = NULL;
 *  if (netconf_operation_failed_xml(&xret, "protocol", "Unauthorized") < 0)
 *    err;
 *  xml_free(xret);
 * @endcode
 * @see netconf_operation_failed  Same but returns cligen buffer
 */
int
netconf_operation_failed_xml(cxobj **xret,
                             char  *type,
                             char  *message)
{
    int   retval =-1;
    cxobj *xerr;
    char  *encstr = NULL;
    
    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>%s</error-type>"
                            "<error-tag>operation-failed</error-tag>"
                            "<error-severity>error</error-severity>",
                            type) < 0)
        goto done;
    if (message){
         if (xml_chardata_encode(&encstr, "%s", message) < 0)
             goto done;
         if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-message>%s</error-message>",
                                 encstr) < 0)
        goto done;
    }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf malformed-message error cbuf according to RFC 6241 App A
 *
 * A message could not be handled because it failed to be parsed correctly.  
 * For example, the message is not well-formed XML or it uses an
 * invalid character set.
 * @param[out]  cb      CLIgen buf. Error XML is written in this buffer
 * @param[in]   message Error message
 * @note New in :base:1.1
 * @see netconf_malformed_message_xml  Same but returns XML tree
 */
int
netconf_malformed_message(cbuf  *cb,
                          char  *message)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_malformed_message_xml(&xret, message) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf malformed-message error XML tree according to RFC 6241 App A
 *
 * A message could not be handled because it failed to be parsed correctly.  
 * For example, the message is not well-formed XML or it uses an
 * invalid character set.
 * @param[out] xret    Error XML tree 
 * @param[in]  message Error message (will be XML encoded)
 * @note New in :base:1.1
 * @code
 *  cxobj *xret = NULL;
 *  if (netconf_malformed_message_xml(&xret, "Unauthorized") < 0)
 *    err;
 *  xml_free(xret);
 * @endcode
 * @see netconf_malformed_message  Same but returns cligen buffer
 */
int
netconf_malformed_message_xml(cxobj **xret,
                              char   *message)
{
    int    retval =-1;
    cxobj *xerr;
    char  *encstr = NULL;

    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>rpc</error-type>"
                            "<error-tag>malformed-message</error-tag>"
                            "<error-severity>error</error-severity>") < 0)
        goto done;
     if (message){
         if (xml_chardata_encode(&encstr, "%s", message) < 0)
             goto done;
         if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-message>%s</error-message>",
                                 encstr) < 0)
             goto done;
     }
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf data-not-unique error message according to RFC 7950 15.1
 *
 * A NETCONF operation would result in configuration data where a
 * "unique" constraint is invalidated.
 * @param[out]  xret   Error XML tree. Free with xml_free after use
 * @param[in]   x      List element containing duplicate
 * @param[in]   cvk    List of components in x that are non-unique
 * @see RFC7950 Sec 15.1
 * @see netconf_data_not_unique_xml  Same but returns XML tree
 */
int
netconf_data_not_unique(cbuf  *cb,
                        cxobj  *x,
                        cvec   *cvk)
{
    int    retval = -1;
    cxobj *xret = NULL;

    if (netconf_data_not_unique_xml(&xret, x, cvk) < 0)
        goto done;
    if (clixon_xml2cbuf(cb, xret, 0, 0, NULL, -1, 0) < 0)
        goto done;
    retval = 0;
 done:
    if (xret)
        xml_free(xret);
    return retval;
}

/*! Create Netconf data-not-unique error message according to RFC 7950 15.1
 *
 * A NETCONF operation would result in configuration data where a
 * "unique" constraint is invalidated.
 * @param[out]  xret   Error XML tree. Free with xml_free after use
 * @param[in]   x      List element containing duplicate
 * @param[in]   cvk    List of components in x that are non-unique
 * @see RFC7950 Sec 15.1
 */
int
netconf_data_not_unique_xml(cxobj **xret,
                            cxobj  *x,
                            cvec   *cvk)
{
    int     retval = -1;
    cg_var *cvi = NULL; 
    cxobj  *xerr;
    cxobj  *xinfo;
    char   *path = NULL;
    char  *encpath = NULL;

    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL,
                            "<error-type>application</error-type>"
                            "<error-tag>operation-failed</error-tag>"
                            "<error-app-tag>data-not-unique</error-app-tag>"
                            "<error-severity>error</error-severity>") < 0)
        goto done;
    /* error-info: <non-unique> Contains an instance identifier that  points to a leaf
     * that invalidates the "unique" constraint.  This element is present once for each
     * non-unique leaf. */
    if (cvec_len(cvk)){
        if ((xinfo = xml_new("error-info", xerr, CX_ELMNT)) == NULL)
            goto done;
        if (xml2xpath(x, NULL, 0, 0, &path) < 0)
            goto done;
        if (xml_chardata_encode(&encpath, "%s", path) < 0)
            goto done;
        while ((cvi = cvec_each(cvk, cvi)) != NULL){
            if (clixon_xml_parse_va(YB_NONE, NULL, &xinfo, NULL,
                                    "<non-unique xmlns=\"%s\">%s/%s</non-unique>",
                                    YANG_XML_NAMESPACE,
                                    encpath,
                                    cv_string_get(cvi)) < 0)
                goto done;
        }
    }
    retval = 0;
 done:
    if (path)
        free(path);
    if (encpath)
        free(encpath);
    return retval;
}

/*! Create Netconf too-many/few-elements err msg according to RFC 7950 15.2/15.3
 *
 * A NETCONF operation would result in configuration data where a
 * list or a leaf-list would have too many entries, the following error
 * @param[out]  xret     Error XML tree. Free with xml_free after use
 * @param[in]   xp       XML parent node (for error)
 * @param[in]   name     Name of list (for error)
 * @param[in]   max      If set, return too-many, otherwise too-few
 * @see RFC7950 Sec 15.1
 */
int
netconf_minmax_elements_xml(cxobj **xret,
                            cxobj  *xp,
                            char   *name,
                            int     max)
{
    int    retval = -1;
    cxobj *xerr;
    char  *path = NULL;
    char  *encpath = NULL;
    
    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
            goto done;
    }
    else if (xml_name_set(*xret, "rpc-reply") < 0)
        goto done;
    if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
        goto done;
    if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
        goto done;
    if (xml_parent(xp)){ /* Dont include root, eg <config> */
        if (xml2xpath(xp, NULL, 0, 0, &path) < 0)
           goto done;
        if (xml_chardata_encode(&encpath, "%s", path) < 0)
            goto done;
    }
    if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>protocol</error-type>"
                            "<error-tag>operation-failed</error-tag>"
                            "<error-app-tag>too-%s-elements</error-app-tag>"
                            "<error-severity>error</error-severity>"
                            "<error-path>%s/%s</error-path>",
                            max?"many":"few",
                            encpath?encpath:"",
                            name) < 0)
        goto done;
    retval = 0;
 done:
    if (path)
        free(path);
    if (encpath)
        free(encpath);
    return retval;
}

/*! Help function: merge - check yang - if error make netconf errmsg 
 * @param[in]     x       XML tree
 * @param[in]     yspec   Yang spec
 * @param[in,out] xret    Existing XML tree, merge x into this
 * @retval        1       OK
 * @retval        0       Statedata callback failed, error in xret?
 * @retval       -1       Error (fatal)
 */
int
netconf_trymerge(cxobj       *x,
                 yang_stmt   *yspec,
                 cxobj      **xret)
{
    int    retval = -1;
    char  *reason = NULL;
    cxobj *xc;
    
    if (xret == NULL){
        clicon_err(OE_NETCONF, EINVAL, "xret is NULL");
        goto done;      
    }
    if (*xret == NULL){
        if ((*xret = xml_dup(x)) == NULL)
            goto done;
        goto ok;
    }
    if (xml_merge(*xret, x, yspec, &reason) < 0)
        goto done;
    if (reason){
        while ((xc = xml_child_i(*xret, 0)) != NULL)
            xml_purge(xc);          
        if (netconf_operation_failed_xml(xret, "rpc", reason)< 0)
            goto done;
        goto fail;
    }
 ok:
    retval = 1;
 done:
    if (reason)
        free(reason);
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Load ietf netconf yang module and set enabled features
 *
 * This function should be called after options loaded but before yang modules.
 * (a yang module may import ietf-netconf and then features must be set)
 * @param[in] h  Clixon handle
 * @retval    0  OK
 * @retval   -1  Error
 * The features added are (in order) (numbers are section# in RFC6241):
 *   candidate (8.3)
 *   validate (8.6)
 *   startup (8.7)
 *   xpath (8.9)
 * @see netconf_module_load  that is called later
 */
int
netconf_module_features(clicon_handle h)
{
    int        retval = -1;
    cxobj     *xc;

    if ((xc = clicon_conf_xml(h)) == NULL){
        clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded");
        goto done; 
    }
    /* Enable features (hardcoded here) */
    if (clixon_xml_parse_string("<CLICON_FEATURE>ietf-netconf:candidate</CLICON_FEATURE>",
                                YB_PARENT, NULL, &xc, NULL) < 0)
        goto done;
    if (clixon_xml_parse_string("<CLICON_FEATURE>ietf-netconf:validate</CLICON_FEATURE>",
                                YB_PARENT,  NULL, &xc, NULL) < 0)
        goto done;
    if (clixon_xml_parse_string("<CLICON_FEATURE>ietf-netconf:xpath</CLICON_FEATURE>",
                                YB_PARENT, NULL, &xc, NULL) < 0)
        goto done;
    retval = 0;
 done:
    return retval;
}

/*! Load generic yang specs, ie ietf netconf yang module and set enabled features
 * @param[in] h  Clixon handle
 * @retval    0  OK
 * @retval   -1  Error
 * @see netconf_module_feature  should be called before any yang modules
 */
int
netconf_module_load(clicon_handle h)
{
    int              retval = -1;
    yang_stmt       *yspec;
    
    yspec = clicon_dbspec_yang(h);
    /* Load yang spec */
    if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
        goto done;
    /* Load yang Netconf stream discovery 
     * Actually add by default since create-subscription is in this module
     */
    if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277"))
        if (yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
            goto done;
    /* Load yang Restconf stream discovery */
    if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
        yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
        goto done;
    /* YANG module revision change management */
    if (clicon_option_bool(h, "CLICON_XML_CHANGELOG"))
        if (yang_spec_parse_module(h, "clixon-xml-changelog", NULL, yspec)< 0)
            goto done;
    /* Load restconf yang to data. Note clixon-restconf.yang is always part of clixon-config */
    if (clicon_option_bool(h, "CLICON_BACKEND_RESTCONF_PROCESS"))
        if (yang_spec_parse_module(h, "clixon-restconf", NULL, yspec)< 0)
            goto done;
    /* XXX: Both the following settings are because clicon-handle is not part of all API
     * functions
     * Treat unknown XML as anydata */
    if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1)
        xml_bind_yang_unknown_anydata(1);
    /* Make message-id attribute optional */
    if (clicon_option_bool(h, "CLICON_NETCONF_MESSAGE_ID_OPTIONAL") == 1)
        xml_bind_netconf_message_id_optional(1);
    /* Load ietf list pagination */
    if (yang_spec_parse_module(h, "ietf-list-pagination", NULL, yspec)< 0)
        goto done;
    /* Load ietf list pagination netconf */
    if (yang_spec_parse_module(h, "ietf-list-pagination-nc", NULL, yspec)< 0)
        goto done;
    /* RFC6243 With-defaults Capability for NETCONF */
    if (yang_spec_parse_module(h, "ietf-netconf-with-defaults", NULL, yspec)< 0)
        goto done;
    /* RFC6022 YANG Module for NETCONF Monitoring */
    if (yang_spec_parse_module(h, "ietf-netconf-monitoring", NULL, yspec)< 0)
        goto done;
    /* Framing: If hello protocol skipped, set framing direct, ie fix chunked framing if NETCONF-1.1
     * But start with default: RFC 4741 EOM ]]>]]>
     * For now this only applies to external protocol
     */
    clicon_data_int_set(h, NETCONF_FRAMING_TYPE, NETCONF_SSH_EOM);
    if (clicon_option_bool(h, "CLICON_NETCONF_HELLO_OPTIONAL")){
        if (clicon_option_int(h, "CLICON_NETCONF_BASE_CAPABILITY") > 0) /* RFC 6241 */
            clicon_data_int_set(h, NETCONF_FRAMING_TYPE, NETCONF_SSH_CHUNKED);
    }
    retval = 0;
 done:
    return retval;
}

/*! Find some sub-child in netconf/xm request.
 * Actually, find a child with a certain name and return its body
 * @param[in]  xn
 * @param[in]  name
 * @retval     db    Name of database
 * @retval     NULL  Not found
 * The following code returns "source"
 * @code
 *   cxobj *xt = NULL;
 *   char  *db;
 *   clixon_xml_parse_string("<x><target>source</target></x>", YB_NONE, NULL, &xt, NULL);
 *   db = netconf_db_find(xt, "target");
 * @endcode
 */
char*
netconf_db_find(cxobj *xn, 
                char  *name)
{
    cxobj *xs; /* source */
    cxobj *xi;
    char  *db = NULL;

    /* XXX should use prefix cf edit_config */
    if ((xs = xml_find(xn, name)) == NULL)
        goto done;
    if ((xi = xml_child_i(xs, 0)) == NULL)
        goto done;
    db = xml_name(xi);
 done:
    return db;
}

/*! Generate netconf error msg to cbuf to use in string printout or logs
 * @param[in]     xerr    Netconf error message on the level: <rpc-error>
 * @param[in,out] cberr   Translation from netconf err to cbuf.
 * @retval     0       OK, with cberr set
 * @retval    -1       Error
 * @code
 *     cbuf *cb = NULL;
 *     if ((cb = cbuf_new()) ==NULL){
 *        err;
 *     if (netconf_err2cb(xerr, cb) < 0)
 *        err;
 *     printf("%s", cbuf_get(cb));
 *     cbuf_free(cb);
 * @endcode
 * @see clixon_netconf_error_fn
 * XXX does not support prefixes properly
 */
int
netconf_err2cb(cxobj *xerr,
               cbuf  *cberr)
{
    int    retval = -1;
    cxobj *x;

    if ((x=xpath_first(xerr, NULL, "//error-type"))!=NULL)
        cprintf(cberr, "%s ", xml_body(x));
    if ((x=xpath_first(xerr, NULL, "//error-tag"))!=NULL)
        cprintf(cberr, "%s ", xml_body(x));
    if ((x=xpath_first(xerr, NULL, "//error-message"))!=NULL)
        cprintf(cberr, "%s ", xml_body(x));
    if ((x=xpath_first(xerr, NULL, "//error-info")) != NULL &&
        xml_child_nr(x) > 0){
        if (clixon_xml2cbuf(cberr, xml_child_i(x, 0), 0, 0, NULL, -1, 0) < 0)
            goto done;
    }
    if ((x=xpath_first(xerr, NULL, "//error-app-tag"))!=NULL)
        cprintf(cberr, ": %s ", xml_body(x));
    if ((x=xpath_first(xerr, NULL, "//error-path"))!=NULL)
        cprintf(cberr, ": %s ", xml_body(x));
    retval = 0;
 done:
    return retval;
}

/* See RFC 8040 4.8.1
 * @see netconf_content_str2int
 */
static const map_str2int netconf_content_map[] = {
    {"config",     CONTENT_CONFIG},
    {"nonconfig",  CONTENT_NONCONFIG},
    {"all",        CONTENT_ALL},
    {NULL,        -1}
};

const netconf_content
netconf_content_str2int(char *str)
{
    return clicon_str2int(netconf_content_map, str);
}

const char *
netconf_content_int2str(netconf_content nr)
{
    return clicon_int2str(netconf_content_map, nr);
}

/*! List capabilities
 *
 * RFC5277 NETCONF Event Notifications
 *  urn:ietf:params:netconf:capability:notification:1.0 is advertised during the capability exchange
 *  
 * RFC6022 YANG Module for NETCONF Monitoring
 *     MUST advertise the capability URI "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"
 * RFC7895 Yang module library defines how to announce module features (not hello capabilities)
 * RFC7950 YANG 1.1 says (5.6.4);
 *    MUST announce the modules it implements by implementing the YANG module 
 *    "ietf-yang-library" (RFC7895) and listing all implemented modules in the
 *    "/modules-state/module" list.
 *    MUST advertise urn:ietf:params:netconf:capability:yang-library:1.0?
 *    revision=<date>&module-set-id=<id> in the <hello> message.
 *
 * Question: should the NETCONF in RFC6241 sections 8.2-8.9 be announced both 
 * as features and as capabilities in the <hello> message according to RFC6241?
 *   urn:ietf:params:netconf:capability:candidate:1.0 (8.3)
 *   urn:ietf:params:netconf:capability:validate:1.1 (8.6)
 *   urn:ietf:params:netconf:capability:startup:1.0 (8.7)
 *   urn:ietf:params:netconf:capability:xpath:1.0 (8.9)
 *   urn:ietf:params:netconf:capability:notification:1.0 (RFC5277)
 */
int
netconf_capabilites(clicon_handle h,
                    cbuf         *cb)
{
    int        retval = -1;
    char      *encstr = NULL;
    char      *ietf_yang_library_revision;
    yang_stmt *yspec = clicon_dbspec_yang(h);
    char      *module_set_id;

    module_set_id = clicon_option_str(h, "CLICON_MODULE_SET_ID");
    cprintf(cb, "<capabilities>");
    if (clicon_option_int(h, "CLICON_NETCONF_BASE_CAPABILITY") > 0){
        /* Each peer MUST send at least the base NETCONF capability, "urn:ietf:params:netconf:base:1.1" 
         * RFC 6241 Sec 8.1
         */
        cprintf(cb, "<capability>%s</capability>", NETCONF_BASE_CAPABILITY_1_1);
    }
    /* A peer MAY include capabilities for previous NETCONF versions, to indicate
       that it supports multiple protocol versions. */
    cprintf(cb, "<capability>%s</capability>", NETCONF_BASE_CAPABILITY_1_0);

    /* Check if RFC7895 loaded and revision found */
    if ((ietf_yang_library_revision = yang_modules_revision(h)) != NULL){
        if (xml_chardata_encode(&encstr, "urn:ietf:params:netconf:capability:yang-library:1.0?revision=%s&module-set-id=%s",
                                ietf_yang_library_revision,
                                module_set_id) < 0)
            goto done;
        cprintf(cb, "<capability>%s</capability>", encstr);
        if (encstr){
            free(encstr);
            encstr = NULL;
        }
    }
    /* RFC6241 Sec 8.3.  Candidate Configuration Capability */
    cprintf(cb, "<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>");
    /* RFC6241 Sec 8.6.  Validate Capability */
    cprintf(cb, "<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>");
    /* rfc 6241 Sec 8.7 Distinct Startup Capability */
    if (if_feature(yspec, "ietf-netconf", "startup"))
        cprintf(cb, "<capability>urn:ietf:params:netconf:capability:startup:1.0</capability>");
    /* RFC6241 Sec 8.9.  XPath Capability */
    cprintf(cb, "<capability>urn:ietf:params:netconf:capability:xpath:1.0</capability>");
    /* rfc6243 with-defaults capability modes */
    cprintf(cb, "<capability>");
    xml_chardata_cbuf_append(cb, "urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all,trim,report-all-tagged");
    cprintf(cb, "</capability>");
    /* RFC5277 Notification Capability */
    cprintf(cb, "<capability>%s</capability>", NETCONF_NOTIFICATION_CAPABILITY);
    /* RFC6022 YANG Module for NETCONF Monitoring 
     * This seems non-standard but necessary for most existing boxes and software
     */
    if (clicon_option_bool(h, "CLICON_NETCONF_MONITORING"))
        cprintf(cb, "<capability>%s</capability>", NETCONF_MONITORING_NAMESPACE);

    /* It is somewhat arbitrary why some features/capabilities are hardocded and why some are not
     * rfc 6241 Sec 8.4 confirmed-commit capabilities */
    if (if_feature(yspec, "ietf-netconf", "confirmed-commit"))
        cprintf(cb, "<capability>urn:ietf:params:netconf:capability:confirmed-commit:1.1</capability>");
    cprintf(cb, "</capabilities>");
    retval = 0;
 done:
    if (encstr)
        free(encstr);
    return retval;
}

/*! Create Netconf server hello. Single cap and defer individual to querying modules
 * @param[in]  h           Clicon handle
 * @param[in]  cb          Msg buffer
 * @param[in]  session_id  Id of client session
 * Lots of dependencies here. regarding the hello protocol.
 * RFC6241 NETCONF Protocol says: (8.1)
 *    MUST send a <hello> element containing a list of that peer's capabilities
 *    MUST send at least the base NETCONF capability, urn:ietf:params:netconf:base:1.1
 *    MAY include capabilities for previous NETCONF versions
 *    A server MUST include a <session-id>  
 *    A client MUST NOT include a <session-id> 
 *    A server receiving <session-id> MUST terminate the NETCONF session.  
 *    A client not receiving <session-id> MUST terminate w/o sending<close-session>
 * the example shows urn:ietf:params:netconf:capability:startup:1.0 
 *
 * @note the hello message is created bythe netconf application, not the 
 *  backend, and backend may implement more modules - please consider if using
 *  library routines for detecting capabilities here. In contrast, yang module 
 * list (RFC7895) is processed by the backend.
 * @note If you add new, remember to encode bodies if needed, see xml_chardata_encode()
 * @note
 * @see yang_modules_state_get
 * @see netconf_module_load
 */
int
netconf_hello_server(clicon_handle h,
                     cbuf         *cb,
                     uint32_t      session_id)
{
    int   retval = -1;

    cprintf(cb, "<hello xmlns=\"%s\">", NETCONF_BASE_NAMESPACE);
    if (netconf_capabilites(h, cb) < 0)
        goto done;
    if (session_id) 
        cprintf(cb, "<session-id>%lu</session-id>", (long unsigned int)session_id);
    cprintf(cb, "</hello>");
    retval = 0;
 done:
    return retval;
}

/*! Generate and print textual error log from Netconf error message
 *
 * Get a text error message from netconf error message and generate error on the form:
 *   <msg>: "<arg>": <netconf-error>   or   <msg>: <netconf-error>
 * @param[in]  fn      Inline function name (when called from clicon_err() macro)
 * @param[in]  line    Inline file line number (when called from clicon_err() macro)
 * @param[in]  xerr    Netconf error xml tree on the form: <rpc-error> 
 * @param[in]  format  Format string 
 * @param[in]  arg     String argument to format (optional)
 * @see netconf_err2cb
 */
int
clixon_netconf_error_fn(const char *fn, 
                        const int   line,
                        cxobj       *xerr,
                        const char  *msg,
                        const char  *arg)
{
    int    retval = -1;
    cbuf  *cb = NULL;

    if ((cb = cbuf_new()) ==NULL){
        clicon_err(OE_XML, errno, "cbuf_new");
        goto done;
    }
    if (msg){
        cprintf(cb, "%s", msg);
        if (arg)
            cprintf(cb, " \"%s\" ", arg);
        
        cprintf(cb, ": ");
    }
    if (netconf_err2cb(xerr, cb) < 0)
        goto done;
#if 0 /* More verbose output for debugging */
    clicon_log(LOG_ERR, "%s: %d: %s", fn, line, cbuf_get(cb));
#else
    clicon_log(LOG_ERR, "%s", cbuf_get(cb));
#endif
    retval = 0;
 done:
    if (cb)
        cbuf_free(cb);
    return retval;
}

/*! Add internal error info to existing netconf error message by rewriting
 *
 * If a eg sanity check detects errors in internal messages passing, such as from a plugin or
 * in backend communication, an error is generated. However, it does not make sense to send this
 * error message as-is to the requestor. Instead this function transforms the error to a more
 * generic "operation-failed" error and adds info in its error message to say it is an internal error.
 * If a requestor receives such an error, it will be clear that the error is internal.
 * @param[in]  xerr    Netconf error xml tree on the form: <rpc-error> 
 * @param[in]  msg     Error message
 * @param[in]  arg     Extra error message (consider stdarg?)
 * @retval     0       OK
 * @retval     -1      Error
 */
int
clixon_netconf_internal_error(cxobj *xerr,
                              char  *msg,
                              char  *arg)
{
    int    retval = -1;
    cxobj *xr;
    cxobj *xb;

    if ((xr = xpath_first(xerr, NULL, "//error-tag")) != NULL &&
        (xb = xml_body_get(xr))){
        if (xml_value_set(xb, "operation-failed") < 0)
            goto done;
    }
    if ((xr = xpath_first(xerr, NULL, "//error-message")) != NULL &&
        (xb = xml_body_get(xr))){
        if (xml_value_append(xb, msg) < 0)
            goto done;
        if (arg &&xml_value_append(xb, arg) < 0)
            goto done;
    }
    retval = 0;
 done:
    return retval;
}

/*! Parse string into uint32 and return netconf bad-element msg on error
 *
 * @param[in]     name       Name of attribute (for error msg)
 * @param[in]     valstr     Value string to be parsed
 * @param[in]     defaultstr If given, default string which is accepted and sets value to 0
 * @param[in,out] cbret      Output buffer for internal RPC message if invalid
 * @param[out]    value      Value if valid
 * @retval        1          OK
 * @retval        0          Invalid, cbret set
 * @retval       -1          Error
 * @code
 *   char    *name = "a";
 *   char    *valstr = "322";
 *   char    *defaultstr = "unbounded";
 *   cbuf    *cbret = cbuf_new();
 *   uint32_t value = 0;
 *
 *   if ((ret = netconf_parse_uint32(name, valstr, defaultstr, cbret, &value) < 0)
 *     goto err;
 *   if (ret == 0)
 *      // cbret contains netconf errmsg
 *   else
 *      // value contains uin32
 * @endcode
 */
int
netconf_parse_uint32(char     *name,
                     char     *valstr,
                     char     *defaultstr,
                     uint32_t  defaultvalue,
                     cbuf     *cbret,
                     uint32_t *value)
{
    int    retval = -1;
    int    ret;
    char  *reason = NULL;

    if (valstr == NULL){
        clicon_err(OE_NETCONF, EINVAL, "valstr is NULL");
        goto done;
    }
    if (defaultstr && strcmp(valstr, defaultstr) == 0)
        *value = defaultvalue;
    else {
        if ((ret = parse_uint32(valstr, value, &reason)) < 0){
            clicon_err(OE_XML, errno, "parse_uint32");
            goto done;
        }
        if (ret == 0){
            if (netconf_bad_element(cbret, "application",
                                    name, "Unrecognized value") < 0)
                goto done;
            goto fail;
        }
    }
    retval = 1;
 done:
    if (reason)
        free(reason);
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Parse string into uint32 and return netconf bad-element msg on error xml variant
 * @see netconf_parse_uint32_xml
 */
int
netconf_parse_uint32_xml(char     *name,
                         char     *valstr,
                         char     *defaultstr,
                         uint32_t  defaultvalue,
                         cxobj   **xerr,
                         uint32_t *value)
{
    int    retval = -1;
    int    ret;
    char  *reason = NULL;

    if (valstr == NULL){
        clicon_err(OE_NETCONF, EINVAL, "valstr is NULL");
        goto done;
    }
    if (defaultstr && strcmp(valstr, defaultstr) == 0)
        *value = defaultvalue;
    else {
        if ((ret = parse_uint32(valstr, value, &reason)) < 0){
            clicon_err(OE_XML, errno, "parse_uint32");
            goto done;
        }
        if (ret == 0){
            if (netconf_bad_element_xml(xerr, "application",
                                        name, "Unrecognized value") < 0)
                goto done;
            goto fail;
        }
    }
    retval = 1;
 done:
    if (reason)
        free(reason);
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Get next netconf rpc message-id 
 *
 * @param[in]  h      Clixon handle
 * @retval     msgid  Next message id
 */
int
netconf_message_id_next(clicon_handle h)
{
    int msgid;
    
    if ((msgid=clicon_option_int(h, "netconf-message-id")) < 0){
        clicon_option_str_set(h, "netconf-message-id", NETCONF_MESSAGE_ID_DEFAULT);
        msgid = clicon_option_int(h, "netconf-message-id");
    }
    else {
        msgid++;
        msgid %= 0x7ffffff; /* Wrap at some number */
        clicon_option_int_set(h, "netconf-message-id", msgid);
    }
    return msgid;
}

/*! Add netconf xml postamble of message. I.e, xml after the body of the message.
 *
 * @param[in]      framing Netconf framing
 * @param[in,out]  cb  Netconf packet (cligen buffer)
 * XXX: copies body
 */
int
netconf_framing_preamble(netconf_framing_type framing,
                         cbuf                *cb)
{
    int   retval = -1;
    char *body = NULL;

    switch (framing){
    case NETCONF_SSH_EOM:
        break;
    case NETCONF_SSH_CHUNKED:
        if ((body = strdup(cbuf_get(cb))) == NULL){
            clicon_err(OE_UNIX, errno, "strdup");
            goto done;
        }
        cbuf_reset(cb);
        cprintf(cb, "\n#%zu\n", strlen(body));     /* Add RFC6242 chunked-end */
        cbuf_append_str(cb, body);
        break;
    }
    retval = 0;
 done:
    if (body)
        free(body);
    return retval;
}

/*! Add netconf xml postamble of message. I.e, xml after the body of the message.
 *
 * @param[in]      framing Netconf framing
 * @param[in,out]  cb  Netconf packet (cligen buffer)
 */
int
netconf_framing_postamble(netconf_framing_type framing,
                          cbuf                *cb)
{
    switch (framing){
    case NETCONF_SSH_EOM:
        cprintf(cb, "]]>]]>");     /* Add RFC4742 end-of-message marker */
        break;
    case NETCONF_SSH_CHUNKED:
        cprintf(cb, "\n##\n");     /* Add RFC6242 chunked-end */
        break;
    }
    return 0;
}

/*! Send netconf message from cbuf on socket
 * @param[in]   s    
 * @param[in]   cb      Cligen buffer that contains the XML message
 * @param[in]   msg     Only for debug
 * @retval      0       OK
 * @retval     -1       Error
 * @see netconf_output_encap  for function with encapsulation
 */
int 
netconf_output(int   s,
               cbuf *cb, 
               char *msg)
{
    int   retval = -1;
    char *buf = cbuf_get(cb);
    int   len = cbuf_len(cb);

    clicon_debug(CLIXON_DBG_MSG, "Send ext: %s", cbuf_get(cb));
#if 0 // Extra sanity check for debugging
    {
        cxobj *xt = NULL;
        if (clixon_xml_parse_string(buf, YB_NONE, NULL, &xt, NULL) == 0){
            if (clixon_xml2file(stderr, xml_child_i(xt, 0), 0, 0, NULL, fprintf, 0, 0) < 0)
                goto done;
            fprintf(stderr, "\n");
            xml_free(xt);
        }
    }
#endif
    if (write(s, buf, len) < 0){
        if (errno == EPIPE)
            clicon_debug(1, "%s write err SIGPIPE", __FUNCTION__);
        else
            clicon_log(LOG_ERR, "%s: write: %s", __FUNCTION__, strerror(errno));
        goto done;
    }
    retval = 0;
  done:
    return retval;
}
            
/*! Encapsulate and send outgoing netconf packet as cbuf on socket
 *
 * @param[in]   framing Framing type, ie EOM(1.0) or chunked (1.1)
 * @param[in]   cb      Cligen buffer that contains the XML message
 * @retval      0       OK
 * @retval     -1       Error
 * @note Assumes "cb" contains valid XML
 * @see netconf_output  without encapsulation
 * @see netconf_hello_msg where framing is set
 */
int 
netconf_output_encap(netconf_framing_type framing,
                     cbuf                *cb)
{
    int  retval = -1;
    
    if (netconf_framing_preamble(framing, cb) < 0)
        goto done;
    if (netconf_framing_postamble(framing, cb) < 0)
        goto done;
    retval = 0;
 done:
    return retval;
}

/*! Track chunked framing defined in RFC6242
 *
 * The function works by calling sequentially each received char and using 
 * two state variables: state and size
 * The function traverses the states 0-7, where state 4 also uses countdown of size
 * The function returns one of four results: framing/control; chunk-data; end-of-frame; or error
 * Anything not conforming is returned as error.
 * Possibly one should accept whitespaces between frames?
 * RFC6242 Sec 4.2
 * <Chunked-Message> = <chunk>+ end-of-chunks
 * <chunk>           = LF HASH <chunk-size> LF <chunk-data>
 *   Example:  \n#4\n
 *             data
 * <end-of-chunks>   = LF HASH HASH LF
 *   Example:  \n##\n
 * <chunk-size>      = [1-9][0-9]*
 *
 * State: 0 No frame (begin/ended)
 *        1 \n received
 *        2 # received
 *        3 read [1-9]
 *        4 read chunk-size read: chunk-data
 * @param[in]     ch     New input character
 * @param[in,out] state  State machine state
 * @param[in,out] size   Remaining expecting chunk bytes.
 * @retval        2      End-of-frame
 * @retval        1      Chunk-data      
 * @retval        0      Framing char, not data
 * @retval       -1      Error
 * Example:
   C:  \n#4\n
   C:  <rpc
   C:  \n#18\n
   C:   message-id="102"\n
   C:  \n#79\n
   C:       xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">\n
   C:    <close-session/>\n
   C:  </rpc>
   C:  \n##\n
 */
int
netconf_input_chunked_framing(char    ch, 
                              int    *state,
                              size_t *size)
{
    int retval = 0;

    clicon_debug(CLIXON_DBG_DETAIL, "%s ch:%c(%d) state:%d size:%zu", __FUNCTION__, ch, ch, *state, *size);
    switch (*state){
    case 0:
        if (ch == '\n'){
            (*state)++;
            break;
        }
        clicon_err(OE_NETCONF, 0, "NETCONF framing error chunk-start: expected \\n but received %c (state:%d)", ch, *state);
        goto err;
        break;
    case 1:
    case 5:
        if (ch == '#'){
            (*state)++;
            break;
        }
        clicon_err(OE_NETCONF, 0, "NETCONF framing error: expected # but received %c (state:%d)", ch, *state);
        goto err;
        break;
    case 2:
        if (ch == '#'){
            (*state) = 0;
            retval = 2; /* end of frame (empty data) */
            break;
        }
        else if (ch >= '1' && ch <= '9'){ /* first num */
            (*state)++;
            *size = ch-'0';
            break;
        }
        clicon_err(OE_NETCONF, 0, "NETCONF framing error chunk-start: expected 1-9 or # but received %c (state:%d)", ch, *state);
        goto err;
        break;
    case 3:
        if (ch >= '0' && ch <= '9'){ /* other nums */
            *size = (*size)*10 + ch-'0';
            break;
        }
        else if (ch == '\n'){ 
            (*state)++;
            break;
        }
        clicon_err(OE_NETCONF, 0, "NETCONF framing error chunk-size: expected 0-9 or \\n but received %c (state:%d)", ch, *state);
        goto err;
        break;
    case 4:
        if (*size > 0){ /* chunk-data */
            (*size)--;
            retval = 1; /* chunk-data */
            break;
        }
        else if (*size == 0 && ch == '\n'){
            (*state)++;
            break;
        }
        clicon_err(OE_NETCONF, 0, "NETCONF framing error chunk-end: expected \\n but received %c (state:%d)", ch, *state);
        goto err;
        break;
    case 6:
        if (ch == '#'){
            (*state)++;
            break;
        }
        else if (ch >= '1' && ch <= '9'){ /* first num */
            *state=3;
            *size = ch-'0';
            break;
        }
        clicon_err(OE_NETCONF, 0, "NETCONF framing error: expected # but received %c (state:%d)", ch, *state);
        goto err;
        break;
    case 7:
        if (ch == '\n'){
            (*state) = 0;
            retval = 2; /* end of frame */
            break;
        }
        clicon_err(OE_NETCONF, 0, "NETCONF framing error chunk-end: expected \\n but received %c (state:%d)", ch, *state);
        goto err;
        break;
    default:
        clicon_err(OE_NETCONF, 0, "NETCONF framing error %c , invalid state:%d", ch, *state);
        goto err;
        break;
    }
 done:
    return retval;
 err:
    *state = 0;
    retval = -1; /* Error */
    goto done;
}

