/*
 * table.c 
 */

/* 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 © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */
/*
 * Portions of this file are copyrighted by:
 * Copyright (C) 2007 Apple, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 *
 * 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-features.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

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

#ifndef NETSNMP_NO_WRITE_SUPPORT
netsnmp_feature_require(oid_stash);
#endif /* !NETSNMP_NO_WRITE_SUPPORT */

#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include <net-snmp/library/snmp_assert.h>

netsnmp_feature_child_of(table_all, mib_helpers);

netsnmp_feature_child_of(table_build_result, table_all);
netsnmp_feature_child_of(table_get_or_create_row_stash, table_all);
netsnmp_feature_child_of(registration_owns_table_info, table_all);
netsnmp_feature_child_of(table_sparse, table_all);

static void     table_helper_cleanup(netsnmp_agent_request_info *reqinfo,
                                     netsnmp_request_info *request,
                                     int status);
static void     table_data_free_func(void *data);
static int
sparse_table_helper_handler(netsnmp_mib_handler *handler,
                            netsnmp_handler_registration *reginfo,
                            netsnmp_agent_request_info *reqinfo,
                            netsnmp_request_info *requests);

/** @defgroup table table
 *  Helps you implement a table.
 *  @ingroup handler
 *
 *  This handler helps you implement a table by doing some of the
 *  processing for you.
 *  
 *  This handler truly shows the power of the new handler mechanism.
 *  By creating a table handler and injecting it into your calling
 *  chain, or by using the netsnmp_register_table() function to register your
 *  table, you get access to some pre-parsed information.
 *  Specifically, the table handler pulls out the column number and
 *  indexes from the request oid so that you don't have to do the
 *  complex work to do that parsing within your own code.
 *
 *  To do this, the table handler needs to know up front how your
 *  table is structured.  To inform it about this, you fill in a
 *  table_registeration_info structure that is passed to the table
 *  handler.  It contains the asn index types for the table as well as
 *  the minimum and maximum column that should be used.
 *  
 *  @{
 */

/** Given a netsnmp_table_registration_info object, creates a table handler.
 *  You can use this table handler by injecting it into a calling
 *  chain.  When the handler gets called, it'll do processing and
 *  store it's information into the request->parent_data structure.
 *
 *  The table helper handler pulls out the column number and indexes from 
 *  the request oid so that you don't have to do the complex work of
 *  parsing within your own code.
 *
 *  @param tabreq is a pointer to a netsnmp_table_registration_info struct.
 *	The table handler needs to know up front how your table is structured.
 *	A netsnmp_table_registeration_info structure that is 
 *	passed to the table handler should contain the asn index types for the 
 *	table as well as the minimum and maximum column that should be used.
 *
 *  @return Returns a pointer to a netsnmp_mib_handler struct which contains
 *	the handler's name and the access method
 *
 */
netsnmp_mib_handler *
netsnmp_get_table_handler(netsnmp_table_registration_info *tabreq)
{
    netsnmp_mib_handler *ret = NULL;

    if (!tabreq) {
        snmp_log(LOG_INFO, "netsnmp_get_table_handler(NULL) called\n");
        return NULL;
    }

    ret = netsnmp_create_handler(TABLE_HANDLER_NAME, table_helper_handler);
    if (ret) {
        ret->myvoid = (void *) tabreq;
        tabreq->number_indexes = count_varbinds(tabreq->indexes);
    }
    return ret;
}

/** Configures a handler such that table registration information is freed by
 *  netsnmp_handler_free(). Should only be called if handler->myvoid points to
 *  an object of type netsnmp_table_registration_info.
 */
void netsnmp_handler_owns_table_info(netsnmp_mib_handler *handler)
{
    netsnmp_assert(handler);
    netsnmp_assert(handler->myvoid);
    handler->data_clone
	= (void *(*)(void *)) netsnmp_table_registration_info_clone;
    handler->data_free
	= (void (*)(void *)) netsnmp_table_registration_info_free;
}

/** Configures a handler such that table registration information is freed by
 *  netsnmp_handler_free(). Should only be called if reg->handler->myvoid
 *  points to an object of type netsnmp_table_registration_info.
 */
#ifndef NETSNMP_FEATURE_REMOVE_REGISTRATION_OWNS_TABLE_INFO
void netsnmp_registration_owns_table_info(netsnmp_handler_registration *reg)
{
    if (reg)
        netsnmp_handler_owns_table_info(reg->handler);
}
#endif /* NETSNMP_FEATURE_REMOVE_REGISTRATION_OWNS_TABLE_INFO */

/** creates a table handler given the netsnmp_table_registration_info object,
 *  inserts it into the request chain and then calls
 *  netsnmp_register_handler() to register the table into the agent.
 */
int
netsnmp_register_table(netsnmp_handler_registration *reginfo,
                       netsnmp_table_registration_info *tabreq)
{
    netsnmp_mib_handler *handler = netsnmp_get_table_handler(tabreq);
    if (!handler ||
        (netsnmp_inject_handler(reginfo, handler) != SNMPERR_SUCCESS)) {
        snmp_log(LOG_ERR, "could not create table handler\n");
        netsnmp_handler_free(handler);
        netsnmp_handler_registration_free(reginfo);
        return MIB_REGISTRATION_FAILED;
    }

    return netsnmp_register_handler(reginfo);
}

int
netsnmp_unregister_table(netsnmp_handler_registration *reginfo)
{
    /* Locate "this" reginfo */
    /* SNMP_FREE(reginfo->myvoid); */
    return netsnmp_unregister_handler(reginfo);
}

/** Extracts the processed table information from a given request.
 *  Call this from subhandlers on a request to extract the processed
 *  netsnmp_request_info information.  The resulting information includes the
 *  index values and the column number.
 *
 * @param request populated netsnmp request structure
 *
 * @return populated netsnmp_table_request_info structure
 */
NETSNMP_INLINE netsnmp_table_request_info *
netsnmp_extract_table_info(netsnmp_request_info *request)
{
    return (netsnmp_table_request_info *)
        netsnmp_request_get_list_data(request, TABLE_HANDLER_NAME);
}

/** extracts the registered netsnmp_table_registration_info object from a
 *  netsnmp_handler_registration object */
netsnmp_table_registration_info *
netsnmp_find_table_registration_info(netsnmp_handler_registration *reginfo)
{
    return (netsnmp_table_registration_info *)
        netsnmp_find_handler_data_by_name(reginfo, TABLE_HANDLER_NAME);
}

/** implements the table helper handler */
int
table_helper_handler(netsnmp_mib_handler *handler,
                     netsnmp_handler_registration *reginfo,
                     netsnmp_agent_request_info *reqinfo,
                     netsnmp_request_info *requests)
{

    netsnmp_request_info *request;
    netsnmp_table_registration_info *tbl_info;
    int             oid_index_pos;
    unsigned int    oid_column_pos;
    unsigned int    tmp_idx;
    ssize_t 	    tmp_len;
    int             incomplete, out_of_range;
    int             status = SNMP_ERR_NOERROR, need_processing = 0;
    oid            *tmp_name;
    netsnmp_table_request_info *tbl_req_info;
    netsnmp_variable_list *vb;

    if (!reginfo || !handler)
        return SNMPERR_GENERR;

    oid_index_pos  = reginfo->rootoid_len + 2;
    oid_column_pos = reginfo->rootoid_len + 1;
    tbl_info = (netsnmp_table_registration_info *) handler->myvoid;

    if ((!handler->myvoid) || (!tbl_info->indexes)) {
        snmp_log(LOG_ERR, "improperly registered table found\n");
        snmp_log(LOG_ERR, "name: %s, table info: %p, indexes: %p\n",
                 handler->handler_name, handler->myvoid, tbl_info->indexes);

        /*
         * XXX-rks: unregister table? 
         */
        return SNMP_ERR_GENERR;
    }

    DEBUGIF("helper:table:req") {
        DEBUGMSGTL(("helper:table:req",
                    "Got %s (%d) mode request for handler %s: base oid:",
                    se_find_label_in_slist("agent_mode", reqinfo->mode),
                    reqinfo->mode, handler->handler_name));
        DEBUGMSGOID(("helper:table:req", reginfo->rootoid,
                     reginfo->rootoid_len));
        DEBUGMSG(("helper:table:req", "\n"));
    }
    
    /*
     * if the agent request info has a state reference, then this is a 
     * later pass of a set request and we can skip all the lookup stuff.
     *
     * xxx-rks: this might break for handlers which only handle one varbind
     * at a time... those handlers should not save data by their handler_name
     * in the netsnmp_agent_request_info. 
     */
    if (netsnmp_agent_get_list_data(reqinfo, handler->next->handler_name)) {
#ifndef NETSNMP_NO_WRITE_SUPPORT
        if (MODE_IS_SET(reqinfo->mode)) {
            return netsnmp_call_next_handler(handler, reginfo, reqinfo,
                                             requests);
        } else {
#endif /* NETSNMP_NO_WRITE_SUPPORT */
/** XXX-rks: memory leak. add cleanup handler? */
            netsnmp_free_agent_data_sets(reqinfo);
#ifndef NETSNMP_NO_WRITE_SUPPORT
        }
#endif /* NETSNMP_NO_WRITE_SUPPORT */
    }

#ifndef NETSNMP_NO_WRITE_SUPPORT
    if ( MODE_IS_SET(reqinfo->mode) &&
         (reqinfo->mode != MODE_SET_RESERVE1)) {
        /*
         * for later set modes, we can skip all the index parsing,
         * and we always need to let child handlers have a chance
         * to clean up, if they were called in the first place (i.e. have
         * a valid table info pointer).
         */
        if(NULL == netsnmp_extract_table_info(requests)) {
            DEBUGMSGTL(("helper:table","no table info for set - skipping\n"));
        }
        else
            need_processing = 1;
    }
    else {
#endif /* NETSNMP_NO_WRITE_SUPPORT */
        /*
         * for GETS, only continue if we have at least one valid request.
         * for RESERVE1, only continue if we have indexes for all requests.
         */
           
    /*
     * loop through requests
     */

    for (request = requests; request; request = request->next) {
        netsnmp_variable_list *var = request->requestvb;

        DEBUGMSGOID(("verbose:table", var->name, var->name_length));
        DEBUGMSG(("verbose:table", "\n"));

        if (request->processed) {
            DEBUGMSG(("verbose:table", "already processed\n"));
            continue;
        }
        netsnmp_assert(request->status == SNMP_ERR_NOERROR);

        /*
         * this should probably be handled further up 
         */
        if ((reqinfo->mode == MODE_GET) && (var->type != ASN_NULL)) {
            /*
             * valid request if ASN_NULL 
             */
            DEBUGMSGTL(("helper:table",
                        "  GET var type is not ASN_NULL\n"));
            netsnmp_set_request_error(reqinfo, request,
                                      SNMP_ERR_WRONGTYPE);
            continue;
        }

#ifndef NETSNMP_NO_WRITE_SUPPORT
        if (reqinfo->mode == MODE_SET_RESERVE1) {
            DEBUGIF("helper:table:set") {
                u_char         *buf = NULL;
                size_t          buf_len = 0, out_len = 0;
                DEBUGMSGTL(("helper:table:set", " SET_REQUEST for OID: "));
                DEBUGMSGOID(("helper:table:set", var->name, var->name_length));
                out_len = 0;
                if (sprint_realloc_by_type(&buf, &buf_len, &out_len, 1,
                                           var, NULL, NULL, NULL)) {
                    DEBUGMSG(("helper:table:set"," type=%d(%02x), value=%s\n",
                              var->type, var->type, buf));
                } else {
                    if (buf != NULL) {
                        DEBUGMSG(("helper:table:set",
                                  " type=%d(%02x), value=%s [TRUNCATED]\n",
                                  var->type, var->type, buf));
                    } else {
                        DEBUGMSG(("helper:table:set",
                                  " type=%d(%02x), value=[NIL] [TRUNCATED]\n",
                                  var->type, var->type));
                    }
                }
                if (buf != NULL) {
                    free(buf);
                }
            }
        }
#endif /* NETSNMP_NO_WRITE_SUPPORT */

        /*
         * check to make sure its in table range 
         */

        out_of_range = 0;
        /*
         * if our root oid is > var->name and this is not a GETNEXT, 
         * then the oid is out of range. (only compare up to shorter 
         * length) 
         */
        if (reginfo->rootoid_len > var->name_length)
            tmp_len = var->name_length;
        else
            tmp_len = reginfo->rootoid_len;
        if (snmp_oid_compare(reginfo->rootoid, reginfo->rootoid_len,
                             var->name, tmp_len) > 0) {
            if (reqinfo->mode == MODE_GETNEXT) {
                if (var->name != var->name_loc)
                    SNMP_FREE(var->name);
                snmp_set_var_objid(var, reginfo->rootoid,
                                   reginfo->rootoid_len);
            } else {
                DEBUGMSGTL(("helper:table", "  oid is out of range.\n"));
                out_of_range = 1;
            }
        }
        /*
         * if var->name is longer than the root, make sure it is 
         * table.1 (table.ENTRY).  
         */
        else if ((var->name_length > reginfo->rootoid_len) &&
                 (var->name[reginfo->rootoid_len] != 1)) {
            if ((var->name[reginfo->rootoid_len] < 1) &&
                (reqinfo->mode == MODE_GETNEXT)) {
                var->name[reginfo->rootoid_len] = 1;
                var->name_length = reginfo->rootoid_len;
            } else {
                out_of_range = 1;
                DEBUGMSGTL(("helper:table", "  oid is out of range.\n"));
            }
        }
        /*
         * if it is not in range, then mark it in the request list 
         * because we can't process it, and set an error so
         * nobody else wastes time trying to process it either.  
         */
        if (out_of_range) {
            DEBUGMSGTL(("helper:table", "  Not processed: "));
            DEBUGMSGOID(("helper:table", var->name, var->name_length));
            DEBUGMSG(("helper:table", "\n"));

            /*
             *  Reject requests of the form 'myTable.N'   (N != 1)
             */
#ifndef NETSNMP_NO_WRITE_SUPPORT
            if (reqinfo->mode == MODE_SET_RESERVE1)
                table_helper_cleanup(reqinfo, request,
                                     SNMP_ERR_NOTWRITABLE);
            else
#endif /* NETSNMP_NO_WRITE_SUPPORT */
            if (reqinfo->mode == MODE_GET)
                table_helper_cleanup(reqinfo, request,
                                     SNMP_NOSUCHOBJECT);
            else
                request->processed = 1; /* skip if next handler called */
            continue;
        }


        /*
         * Check column ranges; set-up to pull out indexes from OID. 
         */

        incomplete = 0;
        tbl_req_info = netsnmp_extract_table_info(request);
        if (NULL == tbl_req_info) {
            tbl_req_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_request_info);
            if (tbl_req_info == NULL) {
                table_helper_cleanup(reqinfo, request,
                                     SNMP_ERR_GENERR);
                continue;
            }
            tbl_req_info->reg_info = tbl_info;
            tbl_req_info->indexes = snmp_clone_varbind(tbl_info->indexes);
            tbl_req_info->number_indexes = 0;       /* none yet */
            netsnmp_request_add_list_data(request,
                                          netsnmp_create_data_list
                                          (TABLE_HANDLER_NAME,
                                           (void *) tbl_req_info,
                                           table_data_free_func));
        } else {
            DEBUGMSGTL(("helper:table", "  using existing tbl_req_info\n "));
        }

        /*
         * do we have a column?
         */
        if (var->name_length > oid_column_pos) {
            /*
             * oid is long enough to contain COLUMN info
             */
            DEBUGMSGTL(("helper:table:col",
                        "  have at least a column (%" NETSNMP_PRIo "d)\n",
                        var->name[oid_column_pos]));
            if (var->name[oid_column_pos] < tbl_info->min_column) {
                DEBUGMSGTL(("helper:table:col",
                            "    but it's less than min (%d)\n",
                            tbl_info->min_column));
                if (reqinfo->mode == MODE_GETNEXT) {
                    /*
                     * fix column, truncate useless column info 
                     */
                    var->name_length = oid_column_pos;
                    tbl_req_info->colnum = tbl_info->min_column;
                } else
                    out_of_range = 1;
            } else if (var->name[oid_column_pos] > tbl_info->max_column)
                out_of_range = 1;
            else
                tbl_req_info->colnum = var->name[oid_column_pos];

            if (out_of_range) {
                /*
                 * this is out of range...  remove from requests, free
                 * memory 
                 */
                DEBUGMSGTL(("helper:table",
                            "    oid is out of range. Not processed: "));
                DEBUGMSGOID(("helper:table", var->name, var->name_length));
                DEBUGMSG(("helper:table", "\n"));

                /*
                 *  Reject requests of the form 'myEntry.N'   (invalid N)
                 */
#ifndef NETSNMP_NO_WRITE_SUPPORT
                if (reqinfo->mode == MODE_SET_RESERVE1)
                    table_helper_cleanup(reqinfo, request,
                                         SNMP_ERR_NOTWRITABLE);
                else
#endif /* NETSNMP_NO_WRITE_SUPPORT */
                if (reqinfo->mode == MODE_GET)
                    table_helper_cleanup(reqinfo, request,
                                         SNMP_NOSUCHOBJECT);
                else
                    request->processed = 1; /* skip if next handler called */
                continue;
            }
            /*
             * use column verification 
             */
            else if (tbl_info->valid_columns) {
                tbl_req_info->colnum =
                    netsnmp_closest_column(var->name[oid_column_pos],
                                           tbl_info->valid_columns);
                DEBUGMSGTL(("helper:table:col", "    closest column is %d\n",
                            tbl_req_info->colnum));
                /*
                 * xxx-rks: document why the continue...
                 */
                if (tbl_req_info->colnum == 0)
                    continue;
                if (tbl_req_info->colnum != var->name[oid_column_pos]) {
                    DEBUGMSGTL(("helper:table:col",
                                "    which doesn't match req "
                                "%" NETSNMP_PRIo "d - truncating index info\n",
                                var->name[oid_column_pos]));
                    /*
                     * different column! truncate useless index info 
                     */
                    var->name_length = oid_column_pos + 1; /* pos is 0 based */
                }
            }
            /*
             * var->name_length may have changed - check again 
             */
            if ((int)var->name_length <= oid_index_pos) { /* pos is 0 based */
                DEBUGMSGTL(("helper:table", "    not enough for indexes\n"));
                tbl_req_info->index_oid_len = 0; /** none available */
            } else {
                /*
                 * oid is long enough to contain INDEX info
                 */
                tbl_req_info->index_oid_len =
                    var->name_length - oid_index_pos;
                DEBUGMSGTL(("helper:table", "    have %lu bytes of index\n",
                            (unsigned long)tbl_req_info->index_oid_len));
                netsnmp_assert(tbl_req_info->index_oid_len < MAX_OID_LEN);
                memcpy(tbl_req_info->index_oid, &var->name[oid_index_pos],
                       tbl_req_info->index_oid_len * sizeof(oid));
                tmp_name = tbl_req_info->index_oid;
            }
        } else if (reqinfo->mode == MODE_GETNEXT ||
                   reqinfo->mode == MODE_GETBULK) {
            /*
             * oid is NOT long enough to contain column or index info, so start
             * at the minimum column. Set index oid len to 0 because we don't
             * have any index info in the OID.
             */
            DEBUGMSGTL(("helper:table", "  no column/index in request\n"));
            tbl_req_info->index_oid_len = 0;
            tbl_req_info->colnum = tbl_info->min_column;
        } else {
            /*
             * oid is NOT long enough to contain index info,
             * so we can't do anything with it.
             *
             * Reject requests of the form 'myTable' or 'myEntry'
             */
            if (reqinfo->mode == MODE_GET ) {
                table_helper_cleanup(reqinfo, request, SNMP_NOSUCHOBJECT);
#ifndef NETSNMP_NO_WRITE_SUPPORT
            } else if (reqinfo->mode == MODE_SET_RESERVE1 ) {
                table_helper_cleanup(reqinfo, request, SNMP_ERR_NOTWRITABLE);
#endif /* NETSNMP_NO_WRITE_SUPPORT */
            }
            continue;
        }

        /*
         * set up tmp_len to be the number of OIDs we have beyond the column;
         * these should be the index(s) for the table. If the index_oid_len
         * is 0, set tmp_len to -1 so that when we try to parse the index below,
         * we just zero fill everything.
         */
        if (tbl_req_info->index_oid_len == 0) {
            incomplete = 1;
            tmp_len = -1;
        } else
            tmp_len = tbl_req_info->index_oid_len;


        /*
         * for each index type, try to extract the index from var->name
         */
        DEBUGMSGTL(("helper:table", "  looking for %d indexes\n",
                    tbl_info->number_indexes));
        for (tmp_idx = 0, vb = tbl_req_info->indexes;
             tmp_idx < tbl_info->number_indexes;
             ++tmp_idx, vb = vb->next_variable) {
            size_t parsed_oid_len;

            if (incomplete && tmp_len) {
                /*
                 * incomplete/illegal OID, set up dummy 0 to parse 
                 */
                DEBUGMSGTL(("helper:table",
                            "  oid indexes not complete: "));
                DEBUGMSGOID(("helper:table", var->name, var->name_length));
                DEBUGMSG(("helper:table", "\n"));

                /*
                 * no sense in trying anymore if this is a GET/SET. 
                 *
                 * Reject requests of the form 'myObject'   (no instance)
                 */
                tmp_len = 0;
                tmp_name = NULL;
                break;
            }
            /*
             * try and parse current index 
             */
            netsnmp_assert(tmp_len >= 0);
            parsed_oid_len = tmp_len;
            if (parse_one_oid_index(&tmp_name, &parsed_oid_len,
                                    vb, 1) != SNMPERR_SUCCESS) {
                incomplete = 1;
                tmp_len = -1;   /* is this necessary? Better safe than
                                 * sorry */
            } else {
                tmp_len = parsed_oid_len;
                DEBUGMSGTL(("helper:table", "  got 1 (incomplete=%d)\n",
                            incomplete));
                /*
                 * do not count incomplete indexes 
                 */
                if (incomplete)
                    continue;
                ++tbl_req_info->number_indexes; /** got one ok */
                if (tmp_len <= 0) {
                    incomplete = 1;
                    tmp_len = -1;       /* is this necessary? Better safe
                                         * than sorry */
                }
            }
        }                       /** for loop */

        DEBUGIF("helper:table:results") {
                unsigned int    count;
                u_char         *buf = NULL;
                size_t          buf_len = 0, out_len = 0;
                DEBUGMSGTL(("helper:table:results", "  found %d indexes\n",
                            tbl_req_info->number_indexes));
                DEBUGMSGTL(("helper:table:results",
                            "  column: %d, indexes: %d",
                            tbl_req_info->colnum,
                            tbl_req_info->number_indexes));
                for (vb = tbl_req_info->indexes, count = 0;
                     vb && count < tbl_req_info->number_indexes;
                     count++, vb = vb->next_variable) {
                    out_len = 0;
                    if (sprint_realloc_by_type(&buf, &buf_len, &out_len, 1,
                                               vb, NULL, NULL, NULL)) {
                        DEBUGMSG(("helper:table:results",
                                  "   index: type=%d(%02x), value=%s",
                                  vb->type, vb->type, buf));
                    } else {
                        if (buf != NULL) {
                            DEBUGMSG(("helper:table:results",
                                      "   index: type=%d(%02x), value=%s [TRUNCATED]",
                                      vb->type, vb->type, buf));
                        } else {
                            DEBUGMSG(("helper:table:results",
                                      "   index: type=%d(%02x), value=[NIL] [TRUNCATED]",
                                      vb->type, vb->type));
                        }
                    }
                }
                if (buf != NULL) {
                    free(buf);
                }
                DEBUGMSG(("helper:table:results", "\n"));
        }


        /*
         * do we have sufficient index info to continue?
         */

        if ((reqinfo->mode != MODE_GETNEXT) &&
            ((tbl_req_info->number_indexes != tbl_info->number_indexes) ||
             (tmp_len != -1))) {

            DEBUGMSGTL(("helper:table",
                        "invalid index(es) for table - skipping\n"));

#ifndef NETSNMP_NO_WRITE_SUPPORT
            if ( MODE_IS_SET(reqinfo->mode) ) {
                /*
                 * no point in continuing without indexes for set.
                 */
                netsnmp_assert(reqinfo->mode == MODE_SET_RESERVE1);
                /** clear first request so we wont try to run FREE mode */
                netsnmp_free_request_data_sets(requests);
                /** set actual error */
                table_helper_cleanup(reqinfo, request, SNMP_ERR_NOCREATION);
                need_processing = 0; /* don't call next handler */
                break;
            }
#endif /* NETSNMP_NO_WRITE_SUPPORT */
            table_helper_cleanup(reqinfo, request, SNMP_NOSUCHINSTANCE);
            continue;
        }
        netsnmp_assert(request->status == SNMP_ERR_NOERROR);
        
        ++need_processing;

    }                           /* for each request */
#ifndef NETSNMP_NO_WRITE_SUPPORT
    }
#endif /* NETSNMP_NO_WRITE_SUPPORT */

    /*
     * bail if there is nothing for our child handlers
     */
    if (0 == need_processing)
        return status;

    /*
     * call our child access function 
     */
    status =
        netsnmp_call_next_handler(handler, reginfo, reqinfo, requests);

    /*
     * check for sparse tables
     */
    if (reqinfo->mode == MODE_GETNEXT)
        sparse_table_helper_handler( handler, reginfo, reqinfo, requests );

    return status;
}

#define SPARSE_TABLE_HANDLER_NAME "sparse_table"

/** implements the sparse table helper handler
 * @internal
 *
 * @note
 * This function is static to prevent others from calling it
 * directly. It it automatically called by the table helper,
 * 
 */
static int
sparse_table_helper_handler(netsnmp_mib_handler *handler,
                     netsnmp_handler_registration *reginfo,
                     netsnmp_agent_request_info *reqinfo,
                     netsnmp_request_info *requests)
{
    int             status = SNMP_ERR_NOERROR;
    netsnmp_request_info *request;
    oid             coloid[MAX_OID_LEN];
    netsnmp_table_request_info *table_info;

    /*
     * since we don't call child handlers, warn if one was registered
     * beneath us. A special exception for the table helper, which calls
     * the handler directly. Use handle custom flag to only log once.
     */
    if((table_helper_handler != handler->access_method) &&
       (NULL != handler->next)) {
        /*
         * always warn if called without our own handler. If we
         * have our own handler, use custom bit 1 to only log once.
         */
        if((sparse_table_helper_handler != handler->access_method) ||
           !(handler->flags & MIB_HANDLER_CUSTOM1)) {
            snmp_log(LOG_WARNING, "handler (%s) registered after sparse table "
                     "hander will not be called\n",
                     handler->next->handler_name ?
                     handler->next->handler_name : "" );
            if(sparse_table_helper_handler == handler->access_method)
                handler->flags |= MIB_HANDLER_CUSTOM1;
        }
    }

    if (reqinfo->mode == MODE_GETNEXT) {
        for(request = requests ; request; request = request->next) {
            if ((request->requestvb->type == ASN_NULL && request->processed) ||
                request->delegated)
                continue;
            if (request->requestvb->type == SNMP_NOSUCHINSTANCE) {
                /*
                 * get next skipped this value for this column, we
                 * need to keep searching forward 
                 */
                DEBUGMSGT(("sparse", "retry for NOSUCHINSTANCE\n"));
                request->requestvb->type = ASN_PRIV_RETRY;
            }
            if (request->requestvb->type == SNMP_NOSUCHOBJECT ||
                request->requestvb->type == SNMP_ENDOFMIBVIEW) {
                /*
                 * get next has completely finished with this column,
                 * so we need to try with the next column (if any)
                 */
                DEBUGMSGT(("sparse", "retry for NOSUCHOBJECT\n"));
                table_info = netsnmp_extract_table_info(request);
                table_info->colnum = netsnmp_table_next_column(table_info);
                if (0 != table_info->colnum) {
                    memcpy(coloid, reginfo->rootoid,
                           reginfo->rootoid_len * sizeof(oid));
                    coloid[reginfo->rootoid_len]   = 1;   /* table.entry node */
                    coloid[reginfo->rootoid_len+1] = table_info->colnum;
                    snmp_set_var_objid(request->requestvb,
                                       coloid, reginfo->rootoid_len + 2);
                    
                    request->requestvb->type = ASN_PRIV_RETRY;
                }
                else {
                    /*
                     * If we don't have column info, reset to null so
                     * the agent will move on to the next table.
                     */
                    request->requestvb->type = ASN_NULL;
                }
            }
        }
    }
    return status;
}

/** create sparse table handler
 */
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_SPARSE
netsnmp_mib_handler *
netsnmp_sparse_table_handler_get(void)
{
    return netsnmp_create_handler(SPARSE_TABLE_HANDLER_NAME,
                                  sparse_table_helper_handler);
}

/** creates a table handler given the netsnmp_table_registration_info object,
 *  inserts it into the request chain and then calls
 *  netsnmp_register_handler() to register the table into the agent.
 */
int
netsnmp_sparse_table_register(netsnmp_handler_registration *reginfo,
                       netsnmp_table_registration_info *tabreq)
{
    netsnmp_mib_handler *handler1, *handler2;

    handler1 = netsnmp_create_handler(SPARSE_TABLE_HANDLER_NAME,
                                     sparse_table_helper_handler);
    if (NULL == handler1)
        return MIB_REGISTRATION_FAILED;

    handler2 = netsnmp_get_table_handler(tabreq);
    if (NULL == handler2 ) {
        netsnmp_handler_free(handler1);
        return SNMP_ERR_GENERR;
    }

    if (SNMPERR_SUCCESS != netsnmp_inject_handler(reginfo, handler1)) {
        netsnmp_handler_free(handler1);
        netsnmp_handler_free(handler2);
        return MIB_REGISTRATION_FAILED;
    }

    if (SNMPERR_SUCCESS != netsnmp_inject_handler(reginfo, handler2)) {
        /** handler1 is in reginfo... remove and free?? */
        netsnmp_handler_free(handler2);
        return MIB_REGISTRATION_FAILED;
    }

    /** both handlers now in reginfo, so nothing to do on error */
    return netsnmp_register_handler(reginfo);
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_SPARSE */


#ifndef NETSNMP_FEATURE_REMOVE_TABLE_BUILD_RESULT
/** Builds the result to be returned to the agent given the table information.
 *  Use this function to return results from lowel level handlers to
 *  the agent.  It takes care of building the proper resulting oid
 *  (containing proper indexing) and inserts the result value into the
 *  returning varbind.
 */
int
netsnmp_table_build_result(netsnmp_handler_registration *reginfo,
                           netsnmp_request_info *reqinfo,
                           netsnmp_table_request_info *table_info,
                           u_char type, u_char * result, size_t result_len)
{

    netsnmp_variable_list *var;

    if (!reqinfo || !table_info)
        return SNMPERR_GENERR;

    var = reqinfo->requestvb;

    if (var->name != var->name_loc)
        free(var->name);
    var->name = NULL;

    if (netsnmp_table_build_oid(reginfo, reqinfo, table_info) !=
        SNMPERR_SUCCESS)
        return SNMPERR_GENERR;

    snmp_set_var_typed_value(var, type, result, result_len);

    return SNMPERR_SUCCESS;
}

/** given a registration info object, a request object and the table
 *  info object it builds the request->requestvb->name oid from the
 *  index values and column information found in the table_info
 *  object. Index values are extracted from the table_info varbinds.
 */
int
netsnmp_table_build_oid(netsnmp_handler_registration *reginfo,
                        netsnmp_request_info *reqinfo,
                        netsnmp_table_request_info *table_info)
{
    oid             tmpoid[MAX_OID_LEN];
    netsnmp_variable_list *var;

    if (!reginfo || !reqinfo || !table_info)
        return SNMPERR_GENERR;

    /*
     * xxx-rks: inefficent. we do a copy here, then build_oid does it
     *          again. either come up with a new utility routine, or
     *          do some hijinks here to eliminate extra copy.
     *          Probably could make sure all callers have the
     *          index & variable list updated, and use
     *          netsnmp_table_build_oid_from_index() instead of all this.
     */
    memcpy(tmpoid, reginfo->rootoid, reginfo->rootoid_len * sizeof(oid));
    tmpoid[reginfo->rootoid_len] = 1;   /** .Entry */
    tmpoid[reginfo->rootoid_len + 1] = table_info->colnum; /** .column */

    var = reqinfo->requestvb;
    if (build_oid(&var->name, &var->name_length,
                  tmpoid, reginfo->rootoid_len + 2, table_info->indexes)
        != SNMPERR_SUCCESS)
        return SNMPERR_GENERR;

    return SNMPERR_SUCCESS;
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_BUILD_RESULT */

/** given a registration info object, a request object and the table
 *  info object it builds the request->requestvb->name oid from the
 *  index values and column information found in the table_info
 *  object.  Index values are extracted from the table_info index oid.
 */
int
netsnmp_table_build_oid_from_index(netsnmp_handler_registration *reginfo,
                                   netsnmp_request_info *reqinfo,
                                   netsnmp_table_request_info *table_info)
{
    oid             tmpoid[MAX_OID_LEN];
    netsnmp_variable_list *var;
    int             len;

    if (!reginfo || !reqinfo || !table_info)
        return SNMPERR_GENERR;

    var = reqinfo->requestvb;
    len = reginfo->rootoid_len;
    memcpy(tmpoid, reginfo->rootoid, len * sizeof(oid));
    tmpoid[len++] = 1;          /* .Entry */
    tmpoid[len++] = table_info->colnum; /* .column */
    memcpy(&tmpoid[len], table_info->index_oid,
           table_info->index_oid_len * sizeof(oid));
    len += table_info->index_oid_len;
    snmp_set_var_objid( var, tmpoid, len );

    return SNMPERR_SUCCESS;
}

/** parses an OID into table indexses */
int
netsnmp_update_variable_list_from_index(netsnmp_table_request_info *tri)
{
    if (!tri)
        return SNMPERR_GENERR;

    /*
     * free any existing allocated memory, then parse oid into varbinds
     */
    snmp_reset_var_buffers( tri->indexes);

    return parse_oid_indexes(tri->index_oid, tri->index_oid_len,
                             tri->indexes);
}

/** builds an oid given a set of indexes. */
int
netsnmp_update_indexes_from_variable_list(netsnmp_table_request_info *tri)
{
    if (!tri)
        return SNMPERR_GENERR;

    return build_oid_noalloc(tri->index_oid,
                             sizeof(tri->index_oid) / sizeof(tri->index_oid[0]),
                             &tri->index_oid_len, NULL, 0, tri->indexes);
}

/**
 * checks the original request against the current data being passed in if 
 * its greater than the request oid but less than the current valid
 * return, set the current valid return to the new value.
 * 
 * returns 1 if outvar was replaced with the oid from newvar (success).
 * returns 0 if not. 
 */
int
netsnmp_check_getnext_reply(netsnmp_request_info *request,
                            oid * prefix,
                            size_t prefix_len,
                            netsnmp_variable_list * newvar,
                            netsnmp_variable_list ** outvar)
{
    oid      myname[MAX_OID_LEN];
    size_t   myname_len;

    build_oid_noalloc(myname, MAX_OID_LEN, &myname_len,
                      prefix, prefix_len, newvar);
    /*
     * is the build of the new indexes less than our current result 
     */
    if ((!(*outvar) || snmp_oid_compare(myname + prefix_len,
                                        myname_len - prefix_len,
                                        (*outvar)->name + prefix_len,
                                        (*outvar)->name_length -
                                        prefix_len) < 0)) {
        /*
         * and greater than the requested oid 
         */
        if (snmp_oid_compare(myname, myname_len,
                             request->requestvb->name,
                             request->requestvb->name_length) > 0) {
            /*
             * the new result must be better than the old 
             */
#ifdef ONLY_WORKS_WITH_ONE_VARBIND
            if (!*outvar)
                *outvar = snmp_clone_varbind(newvar);
	    else
                /* 
                 * TODO: walk the full varbind list, setting
                 *       *all* the values - not just the first.
                 */
                snmp_set_var_typed_value(*outvar, newvar->type,
				newvar->val.string, newvar->val_len);
#else  /* Interim replacement approach - less efficient, but it works! */
            if (*outvar)
                snmp_free_varbind(*outvar);
            *outvar = snmp_clone_varbind(newvar);
#endif
            snmp_set_var_objid(*outvar, myname, myname_len);

            return 1;
        }
    }
    return 0;
}

netsnmp_table_registration_info *
netsnmp_table_registration_info_clone(netsnmp_table_registration_info *tri)
{
    netsnmp_table_registration_info *copy;
    copy = malloc(sizeof(*copy));
    if (copy) {
        *copy = *tri;
        copy->indexes = snmp_clone_varbind(tri->indexes);
        if (!copy->indexes) {
            free(copy);
            copy = NULL;
        }
    }
    return copy;
}

void
netsnmp_table_registration_info_free(netsnmp_table_registration_info *tri)
{
    if (NULL == tri)
        return;

    if (NULL != tri->indexes)
        snmp_free_varbind(tri->indexes);

#if 0
    /*
     * sigh... example use of valid_columns points to static memory,
     * so freeing it would be bad... we'll just have to live with any
     * leaks, for now...
     */
#endif

    free(tri);
}

/** @} */

/*
 * internal routines 
 */
void
table_data_free_func(void *data)
{
    netsnmp_table_request_info *info = (netsnmp_table_request_info *) data;
    if (!info)
        return;
    snmp_free_varbind(info->indexes);
    free(info);
}



static void
table_helper_cleanup(netsnmp_agent_request_info *reqinfo,
                     netsnmp_request_info *request, int status)
{
    netsnmp_set_request_error(reqinfo, request, status);
    netsnmp_free_request_data_sets(request);
    if (!request)
        return;
    request->parent_data = NULL;
}


/*
 * find the closest column to current (which may be current).
 *
 * called when a table runs out of rows for column X. This
 * function is called with current = X + 1, to verify that
 * X + 1 is a valid column, or find the next closest column if not.
 *
 * All list types should be sorted, lowest to highest.
 */
unsigned int
netsnmp_closest_column(unsigned int current,
                       netsnmp_column_info *valid_columns)
{
    unsigned int    closest = 0;
    int             idx;

    if (valid_columns == NULL)
        return 0;

    for( ; valid_columns; valid_columns = valid_columns->next) {

        if (valid_columns->isRange) {
            /*
             * if current < low range, it might be closest.
             * otherwise, if it's < high range, current is in
             * the range, and thus is an exact match.
             */
            if (current < valid_columns->details.range[0]) {
                if ( (valid_columns->details.range[0] < closest) ||
                     (0 == closest)) {
                    closest = valid_columns->details.range[0];
                }
            } else if (current <= valid_columns->details.range[1]) {
                closest = current;
                break;       /* can not get any closer! */
            }

        } /* range */
        else {                  /* list */
            /*
             * if current < first item, no need to iterate over list.
             * that item is either closest, or not.
             */
            if (current < valid_columns->details.list[0]) {
                if ((valid_columns->details.list[0] < closest) ||
                    (0 == closest))
                    closest = valid_columns->details.list[0];
                continue;
            }

            /** if current > last item in list, no need to iterate */
            if (current >
                valid_columns->details.list[(int)valid_columns->list_count - 1])
                continue;       /* not in list range. */

            /** skip anything less than current*/
            for (idx = 0; valid_columns->details.list[idx] < current; ++idx)
                ;
            
            /** check for exact match */
            if (current == valid_columns->details.list[idx]) {
                closest = current;
                break;      /* can not get any closer! */
            }
            
            /** list[idx] > current; is it < closest? */
            if ((valid_columns->details.list[idx] < closest) ||
                (0 == closest))
                closest = valid_columns->details.list[idx];

        }                       /* list */
    }                           /* for */

    return closest;
}

/**
 * This function can be used to setup the table's definition within
 * your module's initialize function, it takes a variable index parameter list
 * for example: the table_info structure is followed by two integer index types
 * netsnmp_table_helper_add_indexes(
 *                  table_info,   
 *	            ASN_INTEGER,  
 *		    ASN_INTEGER,  
 *		    0);
 *
 * @param tinfo is a pointer to a netsnmp_table_registration_info struct.
 *	The table handler needs to know up front how your table is structured.
 *	A netsnmp_table_registeration_info structure that is 
 *	passed to the table handler should contain the asn index types for the 
 *	table as well as the minimum and maximum column that should be used.
 *
 * @return void
 *
 */
void
netsnmp_table_helper_add_indexes(netsnmp_table_registration_info *tinfo,
                                 ...)
{
    va_list         debugargs;
    int             type;

    va_start(debugargs, tinfo);
    while ((type = va_arg(debugargs, int)) != 0) {
        netsnmp_table_helper_add_index(tinfo, type);
    }
    va_end(debugargs);
}

#ifndef NETSNMP_NO_WRITE_SUPPORT
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_GET_OR_CREATE_ROW_STASH
static void
_row_stash_data_list_free(void *ptr) {
    netsnmp_oid_stash_node **tmp = (netsnmp_oid_stash_node **)ptr;
    netsnmp_oid_stash_free(tmp, NULL);
    free(ptr);
}

/** returns a row-wide place to store data in.
    @todo This function will likely change to add free pointer functions. */
netsnmp_oid_stash_node **
netsnmp_table_get_or_create_row_stash(netsnmp_agent_request_info *reqinfo,
                                      const u_char * storage_name)
{
    netsnmp_oid_stash_node **stashp = NULL;
    stashp = (netsnmp_oid_stash_node **)
        netsnmp_agent_get_list_data(reqinfo, (const char *) storage_name);

    if (!stashp) {
        /*
         * hasn't be created yet.  we create it here. 
         */
        stashp = SNMP_MALLOC_TYPEDEF(netsnmp_oid_stash_node *);

        if (!stashp)
            return NULL;        /* ack. out of mem */

        netsnmp_agent_add_list_data(reqinfo,
                                    netsnmp_create_data_list((const char *) storage_name,
                                                             stashp,
                                                             _row_stash_data_list_free));
    }
    return stashp;
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_GET_OR_CREATE_ROW_STASH */
#endif /* NETSNMP_NO_WRITE_SUPPORT */

/*
 * advance the table info colnum to the next column, or 0 if there are no more
 *
 * @return new column, or 0 if there are no more
 */
unsigned int
netsnmp_table_next_column(netsnmp_table_request_info *table_info)
{
    if (NULL == table_info)
        return 0;

    /*
     * try and validate next column
     */
    if (table_info->reg_info->valid_columns)
        return netsnmp_closest_column(table_info->colnum + 1,
                                      table_info->reg_info->valid_columns);
    
    /*
     * can't validate. assume 1..max_column are valid
     */
    if (table_info->colnum < table_info->reg_info->max_column)
        return table_info->colnum + 1;
    
    return 0; /* out of range */
}
