/*
 *
  ***** BEGIN LICENSE BLOCK *****
 
  Copyright (C) 2022 Olof Hagsand and Kristofer Hallin
  Sponsored by Siklu Communications LTD

  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 *****
 */

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

#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <syslog.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>
#include <assert.h>

/* net-snmp */
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

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

/* clicon */
#include <clixon/clixon.h>

#include "snmp_lib.h"
#include "snmp_register.h"
#include "snmp_handler.h"

/*! Common code for handling incoming SNMP request
 * 
 * Get clixon handle from snmp request, print debug data
 * @param[in]  handler      Registered MIB handler structure
 * @param[in]  nhreg        Root registration info.
 * @param[in]  reqinfo      Agent transaction request structure
 * @param[in]  request     The netsnmp request info structure.
 * @param[in]  tablehandler 0:scalar, 1:table for debug
 * @param[out] shp          Clixon snmp handle
 * @retval     0            OK
 * @retval    -1            Error
 */
static int
snmp_common_handler(netsnmp_mib_handler          *handler,
		    netsnmp_handler_registration *nhreg,
		    netsnmp_agent_request_info   *reqinfo,
		    netsnmp_request_info         *request,
		    int                           tablehandler,
		    clixon_snmp_handle          **shp)
{
    int                    retval = -1;
    netsnmp_variable_list *requestvb; /* sub of request */
    cbuf                  *cb = NULL;

    if (request == NULL || shp == NULL){
	clicon_err(OE_XML, EINVAL, "request or shp is null");
	goto done;
    }
    requestvb = request->requestvb;
    if ((*shp = (clixon_snmp_handle*)handler->myvoid) == NULL){
	clicon_err(OE_XML, 0, "No myvoid handler");
	goto done;
    }
    if ((cb = cbuf_new()) == NULL){
	clicon_err(OE_UNIX, errno, "cbuf_new");
	goto done;
    }
    oid_cbuf(cb, (*shp)->sh_oid, (*shp)->sh_oidlen);    
    if (oid_eq(requestvb->name, requestvb->name_length,
	       (*shp)->sh_oid, (*shp)->sh_oidlen) == 0){ /* equal */
	clicon_debug(1, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__,
		     cbuf_get(cb),
		     snmp_msg_int2str(reqinfo->mode),
		     request->inclusive, tablehandler?"table":"scalar");
    }
    else{ /* not equal */
	cprintf(cb, " (");
	oid_cbuf(cb, requestvb->name, requestvb->name_length);
	cprintf(cb, ")");
	// nhreg->rootoid same as shp
	clicon_debug(1, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__,
		     cbuf_get(cb),
		     snmp_msg_int2str(reqinfo->mode),
		     request->inclusive, tablehandler?"table":"scalar");
    }
    retval = 0;
 done:
    if (cb)
	cbuf_free(cb);
    return retval;
}

/*!
 * @param[in]  reqinfo      Agent transaction request structure
 * @param[in]  request     The netsnmp request info structure.
 * @retval     0            OK
 * @retval    -1            Error
 */
static int
snmp_scalar_return(cxobj                      *xs,
		   yang_stmt                  *ys,
		   oid                        *oidc,
		   size_t                      oidclen,
		   netsnmp_agent_request_info *reqinfo,
		   netsnmp_request_info       *request)
{
    int                    retval = -1;
    int                    asn1type;
    char                  *xmlstr = NULL;	
    char                  *defaultval = NULL;
    u_char                *snmpval = NULL;
    size_t                 snmplen = 0;
    char                  *reason = NULL;
    netsnmp_variable_list *requestvb = request->requestvb;
    int                    ret;

    /* SMI default value, How is this different from yang defaults?
     */
    if (yang_extension_value(ys, "defval", IETF_YANG_SMIV2_NS, NULL, &defaultval) < 0)
	goto done;
    if (xs != NULL){
	if ((ret = type_xml2snmp_pre(xml_body(xs), ys, &xmlstr)) < 0)
	    goto done;
	if (ret == 0){
	    if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){
		clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
		goto done;
	    }
	    goto ok;
	}
    }
    else if (defaultval != NULL){
	if ((xmlstr = strdup(defaultval)) == NULL){
	    clicon_err(OE_UNIX, errno, "strdup");
	    goto done;
	}
    }
    else{
	if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
	    clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
	    goto done;
	}
	goto ok;
    }
    if (type_yang2asn1(ys, &asn1type, 1) < 0)
	goto done;
    if ((ret = type_xml2snmp(xmlstr, &asn1type, &snmpval, &snmplen, &reason)) < 0)
	goto done;
    if (ret == 0){
	clicon_debug(1, "%s %s", __FUNCTION__, reason);
	if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
	    clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
	    goto done;
	}
	goto ok;
    }
    /* see snmplib/snmp_client. somewhat indirect
     */
    if ((ret = snmp_set_var_typed_value(requestvb, asn1type, snmpval, snmplen)) != SNMPERR_SUCCESS){
	clicon_err(OE_SNMP, ret, "snmp_set_var_typed_value");
	goto done;
    }
    if ((ret = snmp_set_var_objid(requestvb, oidc, oidclen)) != SNMPERR_SUCCESS){
	clicon_err(OE_SNMP, ret, "snmp_set_var_objid");
	goto done;
    }
 ok:
    retval = 0;
 done:
    if (xmlstr)
	free(xmlstr);
    if (snmpval)
	free(snmpval);
    if (reason)
	free(reason);
    return retval;
}

/*! Scalar handler, set a value to clixon 
 * get xpath: see yang2api_path_fmt / api_path2xpath
 * @param[in]  h          Clixon handle
 * @param[in]  ys         Yang node
 * @param[in]  cvk        Vector of index/Key variables, if any
 * @param[in]  defaultval Default value
 * @param[in]  reqinfo    Agent transaction request structure
 * @param[in]  request   The netsnmp request info structure.
 * @retval     0          OK
 * @retval     -1         Error
 */
static int
snmp_scalar_get(clicon_handle               h,
		yang_stmt                  *ys,
		cvec                       *cvk,
		char                       *defaultval,
		netsnmp_agent_request_info *reqinfo,
		netsnmp_request_info       *request)
{
    int     retval = -1;
    cvec   *nsc = NULL;
    char   *xpath = NULL;
    cxobj  *xt = NULL;
    cxobj  *xerr;
    cxobj  *x = NULL;
    char   *xmlstr = NULL;
    u_char *snmpval = NULL;
    size_t  snmplen = 0;
    int     ret;
    int     asn1type;
    char   *reason = NULL;
    netsnmp_variable_list *requestvb = request->requestvb;
    cxobj  *xcache = NULL;

    clicon_debug(1, "%s", __FUNCTION__);
    /* Prepare backend call by constructing namespace context */
    if (xml_nsctx_yang(ys, &nsc) < 0)
	goto done;
    /* Create xpath from yang */
    if (snmp_yang2xpath(ys, cvk, &xpath) < 0)
	goto done;
    if (type_yang2asn1(ys, &asn1type, 1) < 0)
	goto done;
    /* First try cache */
    clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
    if (xcache==NULL || (x = xpath_first(xcache, nsc, "%s", xpath)) == NULL){
	/* If not found do the backend call */
	if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, NULL, &xt) < 0)
	    goto done;
	/* Detect error XXX Error handling could improve */
	if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
	    clixon_netconf_error(xerr, "clicon_rpc_get", NULL);
	    goto done;
	}
	x = xpath_first(xt, nsc, "%s", xpath);
    }
    /* 
     * The xml to snmp value conversion is done in two steps:
     * 1. From XML to SNMP string, there is a special case for enumeration, and for default value
     * 2. From SNMP string to SNMP binary value which invloves parsing
     */
    if (x != NULL){
	assert(xml_spec(x) == ys);
	if ((ret = type_xml2snmp_pre(xml_body(x), ys, &xmlstr)) < 0)
	    goto done;
	if (ret == 0){
	    if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){
		clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
		goto done;
	    }
	    goto ok;
	}
    }
    else if (defaultval != NULL){
	if ((xmlstr = strdup(defaultval)) == NULL){
	    clicon_err(OE_UNIX, errno, "strdup");
	    goto done;
	}
    }
    else{
	if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
	    clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
	    goto done;
	}
	goto ok;
    }
    if ((ret = type_xml2snmp(xmlstr, &asn1type, &snmpval, &snmplen, &reason)) < 0)
	goto done;
    if (ret == 0){
	clicon_debug(1, "%s %s", __FUNCTION__, reason);
	if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
	    clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
	    goto done;
	}
	goto ok;
    }
    /* see snmplib/snmp_client. somewhat indirect
     */
    if ((ret = snmp_set_var_typed_value(requestvb, asn1type, snmpval, snmplen)) != SNMPERR_SUCCESS){
	clicon_err(OE_SNMP, ret, "snmp_set_var_typed_value");
	goto done;
    }
 ok:
    retval = 0;
 done:
    if (reason)
	free(reason);
    if (xmlstr)
	free(xmlstr);
    if (snmpval)
	free(snmpval);
    if (xt)
	xml_free(xt);
    if (xpath)
	free(xpath);
    if (nsc)
	xml_nsctx_free(nsc);
    return retval;
}

/*! Yang 2 xml via api-path lib functions
 */
int
snmp_yang2xml(cxobj     *xtop,
	      yang_stmt *ys,
	      cvec      *cvk,
	      cxobj    **xbot)
{
    int   retval = -1;
    cvec *cvk1 = NULL;
    char *api_path = NULL;
    char *api_path_fmt = NULL;
    int   i;
    int   ret;
    yang_stmt *yspec;
    
    yspec = ys_spec(ys);
    if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0)
	goto done;
    /* Need to prepend an element to fit api_path_fmt2api_path cvv parameter */
    if ((cvk1 = cvec_new(1)) == NULL){
	clicon_err(OE_UNIX, errno, "cvec_new");
	goto done;
    }
    for (i=0; i<cvec_len(cvk); i++)
	cvec_append_var(cvk1, cvec_i(cvk,i));
    if (api_path_fmt2api_path(api_path_fmt, cvk1, &api_path, NULL) < 0)
	goto done;
    if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, xbot, NULL, NULL)) < 0)
	goto done;
    if (ret == 0){
	clicon_err(OE_XML, 0, "api_path2xml %s invalid", api_path);
	goto done;
    }
    retval = 0;
 done:
    if (api_path_fmt)
	free(api_path_fmt);
    if (api_path)
	free(api_path);
    if (cvk1)
	cvec_free(cvk1);
    return retval;
}

/*! Scalar handler, get a value from clixon 
 * @param[in]  h          Clixon handle
 * @param[in]  ys         Yang node
 * @param[in]  cvk        Vector of index/Key variables, if any
 * @param[in]  valstr0    Pre-set value string, ignore requestvb
 * @param[in]  reqinfo    Agent transaction request structure
 * @param[in]  request   The netsnmp request info structure.
 * @retval     0          OK
 * @retval     -1         Error
 * @note contains special logic for rowstatus handling
 */
static int
snmp_scalar_set(clicon_handle               h,
		yang_stmt                  *ys,
		cvec                       *cvk,
		char                       *valstr0,
		netsnmp_agent_request_info *reqinfo,
		netsnmp_request_info       *request)
{
    int        retval = -1;
    cxobj     *xtop = NULL;
    cxobj     *xbot = NULL;
    cxobj     *xb;
    int        ret;
    char      *valstr = NULL;
    cbuf      *cb = NULL;
    netsnmp_variable_list *requestvb = request->requestvb;
    int        asn1_type;
    enum operation_type op = OP_MERGE;

    clicon_debug(1, "%s", __FUNCTION__);
    if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
	goto done;
    if (snmp_yang2xml(xtop, ys, cvk, &xbot) < 0)
	goto done;
    if ((xb = xml_new("body", xbot, CX_BODY)) == NULL)
	goto done; 
    /* Extended */
    if (type_yang2asn1(ys, &asn1_type, 1) < 0)
	goto done;
    if (valstr0){
	if (xml_value_set(xb, valstr0) < 0)
	    goto done;
    }
    else {
	if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, request, &valstr)) < 0)
	    goto done;
	if (ret == 0)
	    goto ok;
	if (xml_value_set(xb, valstr) < 0)
	    goto done;
    }
    if ((cb = cbuf_new()) == NULL){
	clicon_err(OE_UNIX, errno, "cbuf_new");
	goto done;
    }
    if (clixon_xml2cbuf(cb, xtop, 0, 0, -1, 0) < 0)
	goto done;
    if (clicon_rpc_edit_config(h, "candidate", op, cbuf_get(cb)) < 0)
	goto done;
 ok:
    retval = 0;
 done:
    if (cb)
	cbuf_free(cb);
    if (xtop)
	xml_free(xtop);
    if (valstr)
	free(valstr);
    return retval;
}

/* Make cache row operation: move to backend, remove altogether
 *
 * Remove row from cache, then make merge or delete operation on backend.
 * @param[in]  h     Clixon handle
 * @param[in]  yp    Yang statement of list / parent of leaf
 * @param[in]  cvk   Vector of index/Key variables
 * @param[in]  opstr Operation on row: merge or delete
 * @param[in]  rpc   If 0: do not make backend RPC, 1: do make backend RPC
 * @retval     0     OK
 * @retval    -1     Error
 */
static int
snmp_cache_row_op(clicon_handle h,
		  yang_stmt    *yp,
		  cvec         *cvk,
		  char         *opstr,
		  int           rpc)
{
    int    retval = -1;
    char  *xpath;
    cxobj *xrow = NULL;
    cvec  *nsc = NULL;
    cbuf  *cb = NULL;
    cxobj *xtop = NULL;
    cxobj *xbot = NULL;
    cxobj *xa;
    cxobj *xcache;
    
    if (xml_nsctx_yang(yp, &nsc) < 0)
	goto done;
    /* Create xpath from yang */
    if (snmp_yang2xpath(yp, cvk, &xpath) < 0)
	goto done;
    clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
    if (xcache && (xrow = xpath_first(xcache, nsc, "%s", xpath)) != NULL){
	if (xml_rm(xrow) < 0)
	    goto done;
	if (rpc){
	    if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
		goto done;
	    if (snmp_yang2xml(xtop, yang_parent_get(yp), cvk, &xbot) < 0)
		goto done;
	    if ((xa = xml_new("operation", xrow, CX_ATTR)) == NULL)
		goto done;
	    if (xml_value_set(xa, opstr) < 0)
		goto done;
	    if (xml_namespace_change(xa, NETCONF_BASE_NAMESPACE, NETCONF_BASE_PREFIX) < 0)
		goto done;
	    if (xml_addsub(xbot, xrow) < 0)
		goto done;
	    if ((cb = cbuf_new()) == NULL){
		clicon_err(OE_UNIX, errno, "cbuf_new");
		goto done;
	    }	
	    if (clixon_xml2cbuf(cb, xtop, 0, 0, -1, 0) < 0)
		goto done;
	    if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
		goto done;
	}
	else
	    xml_free(xrow);
    }
    retval = 0;
 done:
    if (nsc)
	xml_nsctx_free(nsc);
    if (xtop)
	xml_free(xtop);
    if (cb)
	cbuf_free(cb);
    if (xpath)
	free(xpath);
    return retval;
}

/*! Set internal cache instead of RPC to backend
 *
 * Always applies to rowstatus settings, as well as rows with notInService(2) status
 * @param[in]  h         Clixon handle
 * @param[in]  yp        Yang statement of list / parent of leaf
 * @param[in]  cvk       Vector of index/Key variables
 * @param[in]  rowstatus Existing value of rowstatus
 * @param[in]  reqinfo   Agent transaction request structure
 * @param[in]  request  The netsnmp request info structure.
 * @retval     0         OK
 * @retval    -1         Error
 */
static int
snmp_cache_set(clicon_handle               h,
	       yang_stmt                  *ys,
	       cvec                       *cvk,
	       int                         rowstatus,
	       netsnmp_agent_request_info *reqinfo,
	       netsnmp_request_info       *request)
{
    int        retval = -1;
    cxobj     *xtop = NULL;
    cxobj     *xbot = NULL;
    cxobj     *xb;
    int        ret;
    char      *valstr = NULL;
    netsnmp_variable_list *requestvb = request->requestvb;
    int        asn1_type;
    yang_stmt *yspec;
    int        isrowstatus = 0;
    cxobj     *xcache = NULL;
    
    clicon_debug(1, "%s", __FUNCTION__);
    if ((yspec = clicon_dbspec_yang(h)) == NULL){
	clicon_err(OE_FATAL, 0, "No DB_SPEC");
	goto done;
    }
    if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
	goto done;
    if (snmp_yang2xml(xtop, ys, cvk, &xbot) < 0)
	goto done;
    if ((xb = xml_new("body", xbot, CX_BODY)) == NULL)
	goto done; 
    /* Extended */
    if (type_yang2asn1(ys, &asn1_type, 1) < 0)
	goto done;
    if (asn1_type == CLIXON_ASN_ROWSTATUS)
	isrowstatus++;
    if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, request, &valstr)) < 0)
	goto done;
    if (ret == 0)
	goto ok;
    if (isrowstatus){
	/* Special case translation of rowstatus values: 
	 * createAndGo -> active, createAndWait -> notInService
	 */
	if (strcmp(valstr, "createAndGo") == 0){
	    free(valstr);
	    if ((valstr = strdup("active")) == NULL){
		clicon_err(OE_UNIX, errno, "strdup");
		goto done;
	    }
	}
	else if (strcmp(valstr, "createAndWait") == 0){
	    free(valstr);
	    if ((valstr = strdup("notInService")) == NULL){
		clicon_err(OE_UNIX, errno, "strdup");
		goto done;
	    }
	}
	else if (strcmp(valstr, "active") == 0){
	    if (rowstatus == 5) /* createAndWait */
		if (snmp_cache_row_op(h, yang_parent_get(ys), cvk, "merge", 1) < 0)
		    goto done;
	}
	else if (strcmp(valstr, "destroy") == 0){ 
	    clicon_debug(1, "%s %d", __FUNCTION__, rowstatus);
	    /* Dont send delete to backend if notInService(2)  */
	    if (snmp_cache_row_op(h, yang_parent_get(ys), cvk, "delete", rowstatus!=2) < 0)
		goto done;
	}
    }
    if (xml_value_set(xb, valstr) < 0)
	goto done;
    clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
    if (xcache == NULL){
	if ((xcache = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
	    goto done;
	clicon_ptr_set(h, "snmp-rowstatus-tree", xcache);
    }
    if (!isrowstatus || strcmp(valstr, "destroy") != 0)
	if (xml_merge(xcache, xtop, yspec, NULL) < 0)
	    goto done;
    /* Special case: push active to clixon */
    if (isrowstatus && strcmp(valstr, "active") == 0){
	if (snmp_scalar_set(h, ys,
			    cvk,
			    valstr,
			    reqinfo,
			    request) < 0)
	    goto done;
    }
 ok:
    retval = 0;
 done:
    if (xtop)
	xml_free(xtop);
    if (valstr)
	free(valstr);
    return retval;
}

/*! Specialized get-config to get value of row-status, if any
 *
 * @param[in]  h          Clixon handle
 * @param[in]  ys         Yang node
 * @param[in]  cvk        Vector of index/Key variables, if any
 * @param[out] rowstatus  Enmu rowstatus: 0 invalid, 1 active, etc
 * @retval     0          OK
 * @retval     -1         Error
 */
static int
snmp_table_rowstatus_get(clicon_handle h,
			 yang_stmt    *ys,
			 yang_stmt    *yrestype,
			 cvec         *cvk,
			 int32_t      *rowstatus)
{
    int     retval = -1;
    cvec   *nsc = NULL;
    cxobj  *xt = NULL;
    char   *xpath = NULL;
    cxobj  *xr;
    int     ret;
    char   *body;
    char   *intstr;
    char   *reason = NULL;
    
    clicon_debug(1, "%s", __FUNCTION__);
    /* Prepare backend call by constructing namespace context */
    if (xml_nsctx_yang(ys, &nsc) < 0)
	goto done;
    /* Create xpath from yang */
    if (snmp_yang2xpath(ys, cvk, &xpath) < 0)
	goto done;
    clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xt);
    if (xt && (xr = xpath_first(xt, nsc, "%s", xpath)) != NULL &&
	(body = xml_body(xr)) != NULL) {
	if ((ret = yang_enum2valstr(yrestype, body, &intstr)) < 0)
	    goto done;
	if (ret == 0){
	    clicon_debug(1, "%s %s invalid or not found", __FUNCTION__, body);
	    *rowstatus = 0;
	}
	else {
	    if ((ret = parse_int32(intstr, rowstatus, &reason)) < 0)
		goto done;
	    if (ret == 0){
		clicon_debug(1, "%s parse_int32: %s", __FUNCTION__, reason);
		*rowstatus = 0;
	    }
	}
    }
    else
	*rowstatus = 0;
    retval = 0;
 done:
    if (xpath)
	free(xpath);
    if (nsc)
	xml_nsctx_free(nsc);
    if (reason)
	free(reason);
    return retval;
}

/*! Single SNMP Scalar operation handler
 *
 * @param[in]  handler   Registered MIB handler structure
 * @param[in]  nhreg     Root registration info.
 * @param[in]  reqinfo   Agent transaction request structure
 * @param[in]  request   The netsnmp request info structure.
 * @retval     0         OK
 * @retval    -1         Error
 */
static int
clixon_snmp_scalar_handler1(netsnmp_mib_handler          *handler,
			    netsnmp_handler_registration *nhreg,
			    netsnmp_agent_request_info   *reqinfo,
			    netsnmp_request_info         *request)
{
    int                    retval = -1;
    clixon_snmp_handle    *sh = NULL;
    int                    asn1_type;
    netsnmp_variable_list *requestvb = request->requestvb;
    int                    ret;

    clicon_debug(2, "%s", __FUNCTION__);
    if (snmp_common_handler(handler, nhreg, reqinfo, request, 0, &sh) < 0)
	goto done;
    /* see net-snmp/agent/snmp_agent.h / net-snmp/library/snmp.h */
    switch (reqinfo->mode) {
    case MODE_GET:          /* 160 */
	if (snmp_scalar_get(sh->sh_h, sh->sh_ys, sh->sh_cvk_orig,
			    sh->sh_default, reqinfo, request) < 0)
	    goto done;
        break;
    case MODE_GETNEXT:      /* 161 */
	assert(0); // Not seen?
	break;
    case MODE_SET_RESERVE1: /* 0 */
	/* Translate from YANG ys leaf type to SNMP asn1.1 type ids (not value), also cvtype */
	if (type_yang2asn1(sh->sh_ys, &asn1_type, 0) < 0)
	    goto done;
        if (requestvb->type != asn1_type){
	    clicon_debug(1, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type);
	    if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
		clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
		goto ok;
	    }
	}
        break;
    case MODE_SET_RESERVE2: /* 1 */
        break;
    case MODE_SET_ACTION:   /* 2 */
	if (snmp_scalar_set(sh->sh_h, sh->sh_ys, NULL, NULL, reqinfo, request) < 0)
	    goto done;
        break;
    case MODE_SET_COMMIT:   /* 3 */
	if (clicon_rpc_commit(sh->sh_h) < 0)
	    goto done;	
	break;
    case MODE_SET_FREE:     /* 4 */
        break;
    case MODE_SET_UNDO:     /* 5 */
	if (clicon_rpc_discard_changes(sh->sh_h) < 0)
	    goto done;	
        break;
    }
 ok:
    retval = SNMP_ERR_NOERROR;
 done:
    return retval;
}

/*! Top level scalar request handler, loop over individual request
 *
 * @param[in]  handler      Registered MIB handler structure
 * @param[in]  nhreg        Root registration info.
 * @param[in]  reqinfo      Agent transaction request structure
 * @param[in]  requests     The netsnmp request info structure.
 * @see clixon_snmp_table_handler
 */
int
clixon_snmp_scalar_handler(netsnmp_mib_handler          *handler,
			   netsnmp_handler_registration *nhreg,
			   netsnmp_agent_request_info   *reqinfo,
			   netsnmp_request_info         *requests)
{
    int                   retval = -1;
    netsnmp_request_info *req;
    int                   ret;

    clicon_debug(1, "%s", __FUNCTION__);
    for (req = requests; req; req = req->next){
	ret = clixon_snmp_scalar_handler1(handler, nhreg, reqinfo, req);
	if (ret != SNMP_ERR_NOERROR){
	    retval = ret;
	    goto done;
	    break;
	}
    }
    retval = SNMP_ERR_NOERROR;
 done:
    return retval;
}

/*! Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name 
 *
 * Get yang of leaf from first part of OID
 * Create xpath with right keys from later part of OID
 * Query clixon if object exists, if so return value
 * @param[in]  h        Clixon handle
 * @param[in]  yt       Yang of table (of list type)
 * @param[in]  oids     OID of ultimate scalar value
 * @param[in]  oidslen  OID length of scalar
 * @param[in]  reqinfo  Agent transaction request structure
 * @param[in]  request The netsnmp request info structure.
 * @retval     -1       Error
 * @retval     0        Object not found
 * @retval     1        OK
 */
static int
snmp_table_get(clicon_handle               h,
	       yang_stmt                  *yt,
	       oid                        *oids,
	       size_t                      oidslen,
	       netsnmp_agent_request_info *reqinfo,
	       netsnmp_request_info       *request)
{
    int        retval = -1;
    oid        oidt[MAX_OID_LEN] = {0,}; /* Table / list oid */
    size_t     oidtlen = MAX_OID_LEN;
    oid        oidleaf[MAX_OID_LEN] = {0,}; /* Leaf */
    size_t     oidleaflen = MAX_OID_LEN;
    oid       *oidi;
    size_t     oidilen;
    yang_stmt *ys;
    yang_stmt *yk;
    char      *xpath = NULL;
    cvec      *cvk_orig;
    cvec      *cvk_val = NULL;
    int        i;
    cg_var    *cv;
    char      *defaultval = NULL;
    int        ret;

    /* Get OID from table /list  */
    if ((ret = yangext_oid_get(yt, oidt, &oidtlen, NULL)) < 0)
	goto done;
    if (ret == 0)
	goto done;
    /* Get yang of leaf from first part of OID */
    ys = NULL;
    while ((ys = yn_each(yt, ys)) != NULL) {
	if (yang_keyword_get(ys) != Y_LEAF)
	    continue;
	/* reset oid */
	oidleaflen = MAX_OID_LEN;
	if ((ret = yangext_oid_get(ys, oidleaf, &oidleaflen, NULL)) < 0)
	    goto done;
	if (ret == 0)
	    goto done;
	if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */
	    continue;
	if (oids[oidleaflen-1] == oidleaf[oidleaflen-1])
	    break;
    }
    if (ys == NULL){
	/* No leaf with matching OID */
	goto fail;
    }
    /* SMI default value, How is this different from yang defaults?
     */
    if (yang_extension_value(ys, "defval", IETF_YANG_SMIV2_NS, NULL, &defaultval) < 0)
    	goto done;
    
    /* Create xpath with right keys from later part of OID 
     * Inverse of snmp_str2oid
     */
    if ((cvk_orig = yang_cvec_get(yt)) == NULL){
	clicon_err(OE_YANG, 0, "No keys");
	goto done;
    }
    if ((cvk_val = cvec_dup(cvk_orig)) == NULL){
	clicon_err(OE_UNIX, errno, "cvec_dup");
	goto done;
    }
    /* read through keys and create cvk */
    oidilen = oidslen-(oidtlen+1);
    oidi = oids+oidtlen+1;
    /* Add keys */
    for (i=0; i<cvec_len(cvk_val); i++){
	cv = cvec_i(cvk_val, i); 
	if ((yk = yang_find(yt, Y_LEAF, cv_string_get(cv))) == NULL){
	    clicon_err(OE_YANG, 0, "List key %s not found", cv_string_get(cv));
	    goto done;
	}
	if (snmp_oid2str(&oidi, &oidilen, yk, cv) < 0) 
	    goto done;
    }
    if (oidilen != 0){
	clicon_err(OE_YANG, 0, "Expected oidlen 0 but is %zu", oidilen);
	goto fail;
    }
    /* Get scalar value */
    if (snmp_scalar_get(h, ys, cvk_val,
			defaultval,
			reqinfo,
			request) < 0)
	goto done;
    // ok:
    retval = 1;
 done:
    if (cvk_val)
	cvec_free(cvk_val);
    if (xpath)
	free(xpath);
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Set value in table
 * Get yang of leaf from first part of OID
 * Create xpath with right keys from later part of OID
 * Query clixon if object exists, if so return value
 * @param[in]  h        Clixon handle
 * @param[in]  yt       Yang of table (of list type)
 * @param[in]  oids     OID of ultimate scalar value
 * @param[in]  oidslen  OID length of scalar
 * @param[in]  reqinfo  Agent transaction request structure
 * @param[in]  request The netsnmp request info structure.
 * @retval     -1       Error
 * @retval     0        Object not found
 * @retval     1        OK
 */
static int
snmp_table_set(clicon_handle               h,
	       yang_stmt                  *yt,
	       oid                        *oids,
	       size_t                      oidslen,
	       netsnmp_agent_request_info *reqinfo,
	       netsnmp_request_info       *request)
{
    int        retval = -1;
    oid        oidt[MAX_OID_LEN] = {0,}; /* Table / list oid */
    size_t     oidtlen = MAX_OID_LEN;
    oid        oidleaf[MAX_OID_LEN] = {0,}; /* Leaf */
    size_t     oidleaflen = MAX_OID_LEN;
    oid       *oidi;
    size_t     oidilen;
    yang_stmt *yi;
    yang_stmt *ys;
    yang_stmt *yrowst;
    yang_stmt *yk;
    yang_stmt *yrestype = NULL;
    char      *xpath = NULL;
    cvec      *cvk_orig;
    cvec      *cvk_val = NULL;
    int        i;
    cg_var    *cv;
    int        ret;
    int        asn1_type;
    netsnmp_variable_list  *requestvb;
    int        rowstatus = 0;
    char      *origtype;

    /* Get OID from table /list  */
    if ((ret = yangext_oid_get(yt, oidt, &oidtlen, NULL)) < 0)
	goto done;
    if (ret == 0)
	goto done;
    /* Get yang of leaf from first part of OID
     * and also leaf with rowstatus type
     */
    ys = NULL;
    yrowst = NULL;
    yi = NULL;
    while ((yi = yn_each(yt, yi)) != NULL) {
	if (yang_keyword_get(yi) != Y_LEAF)
	    continue;
	/* reset oid */
	oidleaflen = MAX_OID_LEN;
	if ((ret = yangext_oid_get(yi, oidleaf, &oidleaflen, NULL)) < 0)
	    goto done;
	if (ret == 0)
	    goto done;
	if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */
	    continue;
	if (oids[oidleaflen-1] == oidleaf[oidleaflen-1]){
	    ys = yi;
	}
	origtype = NULL;
	if (snmp_yang_type_get(yi, NULL, &origtype, &yrestype, NULL) < 0)
	    goto done;
	if (strcmp(origtype, "RowStatus") == 0){
	    yrowst = yi;
	}
	if (origtype){
	    free(origtype);
	    origtype = NULL;
	}
    }
    if (ys == NULL){
	/* No leaf with matching OID */
	goto fail;
    }
    if (type_yang2asn1(ys, &asn1_type, 0) < 0)
	goto done;
    requestvb = request->requestvb;
    if (requestvb->type != asn1_type){
	clicon_debug(1, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type);
	if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
	    clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
	    goto ok;
	}
    }
    /* Create xpath with right keys from later part of OID 
     * Inverse of snmp_str2oid
     */
    if ((cvk_orig = yang_cvec_get(yt)) == NULL){
	clicon_err(OE_YANG, 0, "No keys");
	goto done;
    }
    if ((cvk_val = cvec_dup(cvk_orig)) == NULL){
	clicon_err(OE_UNIX, errno, "cvec_dup");
	goto done;
    }
    /* read through keys and create cvk */
    oidilen = oidslen-(oidtlen+1);
    oidi = oids+oidtlen+1;
    /* Add keys */
    for (i=0; i<cvec_len(cvk_val); i++){
	cv = cvec_i(cvk_val, i); 
	if ((yk = yang_find(yt, Y_LEAF, cv_string_get(cv))) == NULL){
	    clicon_err(OE_YANG, 0, "List key %s not found", cv_string_get(cv));
	    goto done;
	}
	if (snmp_oid2str(&oidi, &oidilen, yk, cv) < 0) 
	    goto done;
    }
    if (oidilen != 0){
	clicon_err(OE_YANG, 0, "Expected oidlen 0 but is %zu", oidilen);
	goto fail;
    }
    if (yrowst){
	/* Get rowstatus from cache */
	if ((ret = snmp_table_rowstatus_get(h,
					    yrowst,
					    yrestype,
					    cvk_val,
					    &rowstatus)) < 0)
	    goto done;
    }
    else{
	/* If no rowstatus object, default to active */
	rowstatus = 1;
    }
    if (ys == yrowst){
	/* Set a rowstatus value to cache */
	if (snmp_cache_set(h, ys,
			   cvk_val,
			   rowstatus,
			   reqinfo,
			   request) < 0)
	    goto done;
    }
    else{
	switch (rowstatus){
	case 0: /* not found, invalid */
	case 3: /* notReady */
	case 6: /* destroy */
	    if ((ret = netsnmp_request_set_error(request, SNMP_ERR_INCONSISTENTVALUE)) != SNMPERR_SUCCESS){
		clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
		goto ok;
	    }
	    break;
	case 1: /* active */
	case 4: /* createAndGo */
	    /* Set backend */
	    if (snmp_scalar_set(h, ys,
				cvk_val,
				NULL,
				reqinfo,
				request) < 0)
		goto done;
	    break;
	case 2: /* notInService */
	case 5: /* createAndWait */
	    /* Set cache */
	    if (snmp_cache_set(h, ys,
			       cvk_val,
			       rowstatus,
			       reqinfo,
			       request) < 0)
		goto done;
	    break;
	}
    }
 ok:
    retval = 1;
 done:
    if (cvk_val)
	cvec_free(cvk_val);
    if (xpath)
	free(xpath);
    if (origtype)
	free(origtype);
    return retval;
 fail:
    retval = 0;
    goto done;
}

/*! Find "next" object from oids minus key and return that.
 * @param[in]  h        Clixon handle
 * @param[in]  ylist    Yang of table (of list type)
 * @param[in]  oids     OID of ultimate scalar value
 * @param[in]  oidslen  OID length of scalar
 * @param[in]  reqinfo  Agent transaction request structure
 * @param[in]  request The netsnmp request info structure.
 * @retval     1        OK
 * @retval     0        Failed
 * @retval    -1        Error
 * XXX: merge with cache
 */
static int
snmp_table_getnext(clicon_handle               h,
		   yang_stmt                  *ylist,
		   oid                        *oids,
		   size_t                      oidslen,
		   netsnmp_agent_request_info *reqinfo,
		   netsnmp_request_info       *request)
{
    int        retval = -1;
    cvec      *nsc = NULL;
    char      *xpath = NULL;
    cxobj     *xt = NULL;
    cxobj     *xerr;
    cxobj     *xtable;
    cxobj     *xrow;
    cxobj     *xcol;
    yang_stmt *ycol;
    yang_stmt *ys;
    int        ret;
    cvec      *cvk_name;
    oid        oidc[MAX_OID_LEN] = {0,}; /* Table / list oid */
    size_t     oidclen = MAX_OID_LEN;
    oid        oidk[MAX_OID_LEN] = {0,}; /* Key oid */
    size_t     oidklen = MAX_OID_LEN;
    oid        oidnext[MAX_OID_LEN] = {0x7fffffff,}; /* Next oid: start with high value */
    size_t     oidnextlen = MAX_OID_LEN;
    int        found = 0; 
    cxobj     *xnext = NULL;
    yang_stmt *ynext = NULL;
    cbuf      *cb = NULL;

    clicon_debug(1, "%s", __FUNCTION__);
    if ((ys = yang_parent_get(ylist)) == NULL ||
	yang_keyword_get(ys) != Y_CONTAINER){
	clicon_err(OE_YANG, EINVAL, "ylist parent is not list");
	goto done;
    }
    if (xml_nsctx_yang(ys, &nsc) < 0)
        goto done;
    if (snmp_yang2xpath(ys, NULL, &xpath) < 0)
        goto done;
    if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, NULL, &xt) < 0)
        goto done;
    if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
        clixon_netconf_error(xerr, "clicon_rpc_get", NULL);
        goto done;
    }
    if ((xtable = xpath_first(xt, nsc, "%s", xpath)) != NULL) {
	/* Make a clone of key-list, but replace names with values */
	if ((cvk_name = yang_cvec_get(ylist)) == NULL){
	    clicon_err(OE_YANG, 0, "No keys");
	    goto done;
	}
	xrow = NULL;
	while ((xrow = xml_child_each(xtable, xrow, CX_ELMNT)) != NULL) {
	    /* Get key part of OID from XML list entry */
	    if ((ret = snmp_xmlkey2val_oid(xrow, cvk_name, NULL, /*&cvk_oid,*/ oidk, &oidklen)) < 0)
		goto done;
	    if (ret == 0)
		continue; /* skip row, not all indexes */
	    xcol = NULL;
	    while ((xcol = xml_child_each(xrow, xcol, CX_ELMNT)) != NULL) {
		if ((ycol = xml_spec(xcol)) == NULL)
		    continue;
		if (yang_keyword_get(ycol) != Y_LEAF)
		    continue;
		if ((ret = yangext_oid_get(ycol, oidc, &oidclen, NULL)) < 0)
		    goto done;
		/* Append key oid */
		if (oid_append(oidc, &oidclen, oidk, oidklen) < 0)
		    goto done;
		/* Get smallest larger */
		if (oid_eq(oidc, oidclen, oids, oidslen) > 0 &&
		    oid_eq(oidc, oidclen, oidnext, oidnextlen) < 0){
		    memcpy(oidnext, oidc, oidclen*sizeof(*oidnext));
		    oidnextlen = oidclen;
		    xnext = xcol;
		    ynext = ycol;
		    found++;
		}
	    } /* while xcol */
	} /* while xrow */
    }
    if (found){
	if (snmp_scalar_return(xnext, ynext, oidnext, oidnextlen, reqinfo, request) < 0)
	    goto done;
	if ((cb = cbuf_new()) == NULL){
	    clicon_err(OE_UNIX, errno, "cbuf_new");
	    goto done;
	}
	oid_cbuf(cb, oidnext, oidnextlen);
	clicon_debug(1, "%s next: %s", __FUNCTION__, cbuf_get(cb));
    }
    retval = found;
 done:
    if (cb)
	cbuf_free(cb);
    if (xpath)
	free(xpath);
    if (xt)
        xml_free(xt);
    if (nsc)
        xml_nsctx_free(nsc);    
    return retval;
}

/*! SNMP table operation handler
 *
 * @param[in]  handler      Registered MIB handler structure
 * @param[in]  nhreg        Root registration info.
 * @param[in]  reqinfo      Agent transaction request structure
 * @param[in]  request     The netsnmp request info structure.
 */
static int
clixon_snmp_table_handler1(netsnmp_mib_handler          *handler,
			   netsnmp_handler_registration *nhreg,
			   netsnmp_agent_request_info   *reqinfo,
			   netsnmp_request_info         *request)
{
    int                     retval = -1;
    clixon_snmp_handle     *sh = NULL;
    cvec                   *nsc = NULL;
    cxobj                  *xt = NULL;
    cbuf                   *cb = NULL;
    int                     ret;
    netsnmp_variable_list  *requestvb;
    
    clicon_debug(2, "%s", __FUNCTION__);
    if ((ret = snmp_common_handler(handler, nhreg, reqinfo, request, 1, &sh)) < 0)
	goto done;
    if (sh->sh_ys == NULL){
	clicon_debug(1, "%s Error table not registered", __FUNCTION__);
	goto ok;
    }
    requestvb = request->requestvb;
    switch(reqinfo->mode){
    case MODE_GET: // 160
	/* Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name 
	 */
	if ((ret = snmp_table_get(sh->sh_h, sh->sh_ys,
				  requestvb->name, requestvb->name_length,
				  reqinfo, request)) < 0)
	    goto done;
	if (ret == 0){
	    if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
		clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
		goto done;
	    }
	    clicon_debug(1, "%s Nosuchinstance", __FUNCTION__);
	}
	break;
    case MODE_GETNEXT: // 161
	/* Register table sub-oid:s of existing entries in clixon */
	if ((ret = snmp_table_getnext(sh->sh_h, sh->sh_ys,
				      requestvb->name, requestvb->name_length,
				      reqinfo, request)) < 0)
	    goto done;
	if (ret == 0){
	    //	    if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){
	    if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){

		clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
		goto done;
	    }
	    clicon_debug(1, "%s No such object", __FUNCTION__);
	}
        break;
    case MODE_SET_RESERVE1: // 0
	// Check types: compare type in requestvb to yang type (or do later)
	break;
    case MODE_SET_RESERVE2: // 1
	break;
    case MODE_SET_ACTION:   // 2
	if ((ret = snmp_table_set(sh->sh_h, sh->sh_ys,
				  requestvb->name, requestvb->name_length,
				  reqinfo, request)) < 0)
	    goto done;
	if (ret == 0){
	    if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
		clicon_err(OE_SNMP, ret, "netsnmp_request_set_error");
		goto done;
	    }
	    clicon_debug(1, "%s Nosuchinstance", __FUNCTION__);
	}
	break;
    case MODE_SET_COMMIT:   // 3
	if (clicon_rpc_commit(sh->sh_h) < 0)
	    goto done;	
	break;
    case MODE_SET_FREE:     // 4
	break;
    case MODE_SET_UNDO  :   // 5
	if (clicon_rpc_discard_changes(sh->sh_h) < 0)
	    goto done;	
        break;
    }
 ok:
    retval = SNMP_ERR_NOERROR;
 done:
    if (xt)
        xml_free(xt);
    if (cb)
        cbuf_free(cb);
    if (nsc)
        xml_nsctx_free(nsc);
    return retval;
}

/*! Top level table request handler, loop over individual requests
 *
 * @param[in]  handler      Registered MIB handler structure
 * @param[in]  nhreg        Root registration info.
 * @param[in]  reqinfo      Agent transaction request structure
 * @param[in]  requests     The netsnmp request info structure.
 * @see clixon_snmp_scalar_handler
 */
int
clixon_snmp_table_handler(netsnmp_mib_handler          *handler,
			  netsnmp_handler_registration *nhreg,
			  netsnmp_agent_request_info   *reqinfo,
			  netsnmp_request_info         *requests)
{
    int                   retval = -1;
    netsnmp_request_info *req;
    int                   ret;

    clicon_debug(1, "%s", __FUNCTION__);
    for (req = requests; req; req = req->next){
	ret = clixon_snmp_table_handler1(handler, nhreg, reqinfo, req);
	if (ret != SNMP_ERR_NOERROR){
	    retval = ret;
	    goto done;
	    break;
	}
    }
    retval = SNMP_ERR_NOERROR;
 done:
    return retval;
}
