/*
 * Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 *
 * Portions of this file are copyrighted by:
 * Copyright (c) 2016 VMware, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */

#include <net-snmp/net-snmp-config.h>

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

#include <net-snmp/agent/scalar_group.h>

#include <stdlib.h>
#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include <net-snmp/agent/instance.h>
#include <net-snmp/agent/serialize.h>

static void *
clone_scalar_group(void *p)
{
  netsnmp_scalar_group* src = p;
  netsnmp_scalar_group *t = SNMP_MALLOC_TYPEDEF(netsnmp_scalar_group);
  if(t != NULL) {
    t->lbound = src->lbound;
    t->ubound = src->ubound;
  }
  return t;
}

/** @defgroup scalar_group_group scalar_group
 *  Process groups of scalars.
 *  @ingroup leaf
 *  @{
 */
netsnmp_mib_handler *
netsnmp_get_scalar_group_handler(oid first, oid last)
{
    netsnmp_mib_handler  *ret    = NULL;
    netsnmp_scalar_group *sgroup = NULL;

    ret = netsnmp_create_handler("scalar_group",
                                  netsnmp_scalar_group_helper_handler);
    if (ret) {
        sgroup = SNMP_MALLOC_TYPEDEF(netsnmp_scalar_group);
        if (NULL == sgroup) {
            netsnmp_handler_free(ret);
            ret = NULL;
        }
        else {
	    sgroup->lbound = first;
	    sgroup->ubound = last;
            ret->myvoid = (void *)sgroup;
            ret->data_free = free;
            ret->data_clone = clone_scalar_group;
	}
    }
    return ret;
}

int
netsnmp_register_scalar_group(netsnmp_handler_registration *reginfo,
                              oid first, oid last)
{
    netsnmp_mib_handler *h1, *h2;

    h1 = netsnmp_get_instance_handler();
    h2 = netsnmp_get_scalar_group_handler(first, last);

    if (h1 && h2) {
        if (netsnmp_inject_handler(reginfo, h1) == SNMPERR_SUCCESS) {
            h1 = NULL;
            if (netsnmp_inject_handler(reginfo, h2) == SNMPERR_SUCCESS)
                return netsnmp_register_serialize(reginfo);
        }
    }

    snmp_log(LOG_ERR, "register read only scalar group failed\n");
    netsnmp_handler_free(h1);
    netsnmp_handler_free(h2);
    netsnmp_handler_registration_free(reginfo);

    return MIB_REGISTRATION_FAILED;
}


int
netsnmp_scalar_group_helper_handler(netsnmp_mib_handler *handler,
                                netsnmp_handler_registration *reginfo,
                                netsnmp_agent_request_info *reqinfo,
                                netsnmp_request_info *requests)
{
    netsnmp_variable_list *var = requests->requestvb;

    netsnmp_scalar_group *sgroup = (netsnmp_scalar_group *)handler->myvoid;
    int             ret, cmp;
    int             namelen;
    oid             subid, root_tmp[MAX_OID_LEN], *root_save;

    DEBUGMSGTL(("helper:scalar_group", "Got request:\n"));
    namelen = SNMP_MIN(requests->requestvb->name_length,
                       reginfo->rootoid_len);
    cmp = snmp_oid_compare(requests->requestvb->name, namelen,
                           reginfo->rootoid, reginfo->rootoid_len);

    DEBUGMSGTL(( "helper:scalar_group", "  cmp=%d, oid:", cmp));
    DEBUGMSGOID(("helper:scalar_group", var->name, var->name_length));
    DEBUGMSG((   "helper:scalar_group", "\n"));

    /*
     * copy root oid to root_tmp, set instance to 0. (subid set later on)
     * save rootoid, since we'll replace it before calling next handler,
     * and need to restore it afterwards.
     */
    memcpy(root_tmp, reginfo->rootoid, reginfo->rootoid_len * sizeof(oid));
    root_tmp[reginfo->rootoid_len + 1] = 0;
    root_save = reginfo->rootoid;

    switch (reqinfo->mode) {
    /*
     * The handling of "exact" requests is basically the same.
     * The only difference between GET and SET requests is the
     *     error/exception to return on failure.
     */
#ifndef NETSNMP_NO_WRITE_SUPPORT
    case MODE_SET_RESERVE1:
    case MODE_SET_RESERVE2:
    case MODE_SET_ACTION:
    case MODE_SET_COMMIT:
    case MODE_SET_UNDO:
    case MODE_SET_FREE:
#endif /* NETSNMP_NO_WRITE_SUPPORT */
    case MODE_GET:
	ret = reqinfo->mode == MODE_GET ? SNMP_NOSUCHOBJECT : SNMP_ERR_NOCREATION;
        if (cmp != 0 ||
            requests->requestvb->name_length <= reginfo->rootoid_len) {
	    /*
	     * Common prefix doesn't match, or only *just* matches 
	     *  the registered root (so can't possibly match a scalar)
	     */
            netsnmp_set_request_error(reqinfo, requests, ret);
            return SNMP_ERR_NOERROR;
        } else {
	    /*
	     * Otherwise,
	     *     extract the object subidentifier from the request, 
	     *     check this is (probably) valid, and then fudge the
	     *     registered 'rootoid' to match, before passing the
	     *     request off to the next handler ('scalar').
	     *
	     * Note that we don't bother checking instance subidentifiers
	     *     here.  That's left to the scalar helper.
	     */
            subid = requests->requestvb->name[reginfo->rootoid_len];
	    if (subid < sgroup->lbound ||
	        subid > sgroup->ubound) {
                netsnmp_set_request_error(reqinfo, requests, ret);
                return SNMP_ERR_NOERROR;
	    }
            root_tmp[reginfo->rootoid_len] = subid;
            reginfo->rootoid_len += 2;
            reginfo->rootoid = root_tmp;
            ret = netsnmp_call_next_handler(handler, reginfo, reqinfo,
                                            requests);
            reginfo->rootoid = root_save;
            reginfo->rootoid_len -= 2;
            return ret;
        }
        break;

    case MODE_GETNEXT:
	/*
	 * If we're being asked for something before (or exactly matches)
	 *     the registered root OID, then start with the first object.
	 * If we're being asked for something that exactly matches an object
	 *    OID, then that's what we pass down.
	 * Otherwise, we pass down the OID of the *next* object....
	 */
        if (cmp < 0 ||
            requests->requestvb->name_length <= reginfo->rootoid_len) {
            subid  = sgroup->lbound;
        } else if (requests->requestvb->name_length == reginfo->rootoid_len+1)
            subid = requests->requestvb->name[reginfo->rootoid_len];
        else
            subid = requests->requestvb->name[reginfo->rootoid_len]+1;

	/*
	 * ... always assuming this is (potentially) valid, of course.
	 */
        if (subid < sgroup->lbound)
            subid = sgroup->lbound;
	else if (subid > sgroup->ubound)
            return SNMP_ERR_NOERROR;
        
        root_tmp[reginfo->rootoid_len] = subid;
        reginfo->rootoid_len += 2;
        reginfo->rootoid = root_tmp;
        ret = netsnmp_call_next_handler(handler, reginfo, reqinfo,
                                            requests);
        /*
         * If we didn't get an answer (due to holes in the group)
	 *   set things up to retry again.
         */
        if (!requests->delegated &&
            (requests->requestvb->type == ASN_NULL ||
             requests->requestvb->type == SNMP_NOSUCHOBJECT ||
             requests->requestvb->type == SNMP_NOSUCHINSTANCE)) {
            snmp_set_var_objid(requests->requestvb,
                               reginfo->rootoid, reginfo->rootoid_len - 1);
            requests->requestvb->name[reginfo->rootoid_len - 2] = ++subid;
            requests->requestvb->type = ASN_PRIV_RETRY;
        }
        reginfo->rootoid = root_save;
        reginfo->rootoid_len -= 2;
        return ret;
    }
    /*
     * got here only if illegal mode found 
     */
    return SNMP_ERR_GENERR;
}

/** @} 
 */
