/*
 * table_container.c
 * $Id: table_container.c 14171 2006-01-27 09:05:31Z tanders $
 */

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

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

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

#include <net-snmp/agent/table.h>
#include <net-snmp/agent/table_container.h>
#include <net-snmp/library/container.h>
#include <net-snmp/library/snmp_assert.h>

#if HAVE_DMALLOC_H
#include <dmalloc.h>
#endif

/*
 * snmp.h:#define SNMP_MSG_INTERNAL_SET_BEGIN        -1 
 * snmp.h:#define SNMP_MSG_INTERNAL_SET_RESERVE1     0 
 * snmp.h:#define SNMP_MSG_INTERNAL_SET_RESERVE2     1 
 * snmp.h:#define SNMP_MSG_INTERNAL_SET_ACTION       2 
 * snmp.h:#define SNMP_MSG_INTERNAL_SET_COMMIT       3 
 * snmp.h:#define SNMP_MSG_INTERNAL_SET_FREE         4 
 * snmp.h:#define SNMP_MSG_INTERNAL_SET_UNDO         5 
 */

/*
 * PRIVATE structure for holding important info for each table.
 */
typedef struct container_table_data_s {

   /** registration info for the table */
    netsnmp_table_registration_info *tblreg_info;

   /** container for the table rows */
   netsnmp_container          *table;

    /*
     * mutex_type                lock;
     */

   /* what type of key do we want? */
   char            key_type;

} container_table_data;

/** @defgroup table_container table_container
 *  Helps you implement a table when data can be found via a netsnmp_container.
 *  @ingroup table
 *
 *  The table_container handler is used (automatically) in conjuntion
 *  with the @link table table@endlink handler.
 *
 *  This handler will use the index information provided by
 *  the @link table @endlink handler to find the row needed to process
 *  the request.
 *
 *  The container must use one of 3 key types. It is the sub-handler's
 *  responsibility to ensure that the container and key type match (unless
 *  neither is specified, in which case a default will be used.)
 *
 *  The current key types are:
 *
 *    TABLE_CONTAINER_KEY_NETSNMP_INDEX
 *        The container should do comparisons based on a key that may be cast
 *        to a netsnmp index (netsnmp_index *). This index contains only the
 *        index portion of the OID, not the entire OID.
 *
 *    TABLE_CONTAINER_KEY_VARBIND_INDEX
 *        The container should do comparisons based on a key that may be cast
 *        to a netsnmp variable list (netsnmp_variable_list *). This variable
 *        list will contain one varbind for each index component.
 *
 *    TABLE_CONTAINER_KEY_VARBIND_RAW    (NOTE: unimplemented)
 *        While not yet implemented, future plans include passing the request
 *        varbind with the full OID to a container.
 *
 *  If a key type is not specified at registration time, the default key type
 *  of TABLE_CONTAINER_KEY_NETSNMP_INDEX will be used. If a container is
 *  provided, or the handler name is aliased to a container type, the container
 *  must use a netsnmp index.
 *
 *  If no container is provided, a lookup will be made based on the
 *  sub-handler's name, or if that isn't found, "table_container". The 
 *  table_container key type will be netsnmp_index.
 *
 *  The container must, at a minimum, implement find and find_next. If a NULL
 *  key is passed to the container, it must return the first item, if any.
 *  All containers provided by net-snmp fulfil this requirement.
 *
 *  This handler will only register to process 'data lookup' modes. In
 *  traditional net-snmp modes, that is any GET-like mode (GET, GET-NEXT,
 *  GET-BULK) or the first phase of a SET (RESERVE1). In the new baby-steps
 *  mode, DATA_LOOKUP is it's own mode, and is a pre-cursor to other modes.
 *
 *  When called, the handler will call the appropriate container method
 *  with the appropriate key type. If a row was not found, the result depends
 *  on the mode.
 *
 *  GET Processing
 *    An exact match must be found. If one is not, the error NOSUCHINSTANCE
 *    is set.
 *
 *  GET-NEXT / GET-BULK
 *    If no row is found, the column number will be increased (using any
 *    valid_columns structure that may have been provided), and the first row
 *    will be retrieved. If no first row is found, the processed flag will be
 *    set, so that the sub-handler can skip any processing related to the
 *    request. The agent will notice this unsatisfied request, and attempt to
 *    pass it to the next appropriate handler.
 *
 *  SET
 *    If the hander did not register with the HANDLER_CAN_NOT_CREATE flag
 *    set in the registration modes, it is assumed that this is a row
 *    creation request and a NULL row is added to the request's data list.
 *    The sub-handler is responsbile for dealing with any row creation
 *    contraints and inserting any newly created rows into the container
 *    and the request's data list.
 *
 *  If a row is found, it will be inserted into
 *  the request's data list. The sub-handler may retrieve it by calling
 *      netsnmp_container_table_extract_context(request); *
 *  NOTE NOTE NOTE:
 *
 *  This helper and it's API are still being tested and are subject to change.
 *
 * @{
 */

static int
_container_table_handler(netsnmp_mib_handler *handler,
                         netsnmp_handler_registration *reginfo,
                         netsnmp_agent_request_info *agtreq_info,
                         netsnmp_request_info *requests);

/**********************************************************************
 **********************************************************************
 *                                                                    *
 *                                                                    *
 * PUBLIC Registration functions                                      *
 *                                                                    *
 *                                                                    *
 **********************************************************************
 **********************************************************************/
/** register specified callbacks for the specified table/oid.
*/
netsnmp_mib_handler *
netsnmp_container_table_handler_get(netsnmp_table_registration_info *tabreg,
                                    netsnmp_container *container, char key_type)
{
    container_table_data *tad;
    netsnmp_mib_handler *handler;

    if (NULL == tabreg) {
        snmp_log(LOG_ERR, "bad param in netsnmp_container_table_register\n");
        return NULL;
    }

    tad = SNMP_MALLOC_TYPEDEF(container_table_data);
    handler = netsnmp_create_handler("table_container",
                                     _container_table_handler);
    if((NULL == tad) || (NULL == handler)) {
        if(tad) free(tad); /* SNMP_FREE wasted on locals */
        if(handler) free(handler); /* SNMP_FREE wasted on locals */
        snmp_log(LOG_ERR,
                 "malloc failure in netsnmp_container_table_register\n");
        return NULL;
    }

    tad->tblreg_info = tabreg;  /* we need it too, but it really is not ours */
    if(key_type)
        tad->key_type = key_type;
    else
        tad->key_type = TABLE_CONTAINER_KEY_NETSNMP_INDEX;

    if(NULL == container)
        container = netsnmp_container_find("table_container");
    tad->table = container;

    if (NULL==container->compare)
        container->compare = netsnmp_compare_netsnmp_index;
    if (NULL==container->ncompare)
        container->ncompare = netsnmp_ncompare_netsnmp_index;
    
    handler->myvoid = (void*)tad;
    handler->flags |= MIB_HANDLER_AUTO_NEXT;
    
    return handler;
}

int
netsnmp_container_table_register(netsnmp_handler_registration *reginfo,
                                 netsnmp_table_registration_info *tabreg,
                                 netsnmp_container *container, char key_type )
{
    netsnmp_mib_handler *handler;

    if ((NULL == reginfo) || (NULL == reginfo->handler) || (NULL == tabreg)) {
        snmp_log(LOG_ERR, "bad param in netsnmp_container_table_register\n");
        return SNMPERR_GENERR;
    }

    if (NULL==container)
        container = netsnmp_container_find(reginfo->handlerName);

    handler = netsnmp_container_table_handler_get(tabreg, container, key_type);
    netsnmp_inject_handler(reginfo, handler );

    return netsnmp_register_table(reginfo, tabreg);
}

/** @} */

/** @cond */
/**********************************************************************
 **********************************************************************
 *                                                                    *
 *                                                                    *
 * DATA LOOKUP functions                                              *
 *                                                                    *
 *                                                                    *
 **********************************************************************
 **********************************************************************/
NETSNMP_STATIC_INLINE void
_set_key( container_table_data * tad, netsnmp_request_info *request,
          netsnmp_table_request_info *tblreq_info,
          void **key, netsnmp_index *index )
{
    if (TABLE_CONTAINER_KEY_NETSNMP_INDEX == tad->key_type) {
        index->oids = tblreq_info->index_oid;
        index->len = tblreq_info->index_oid_len;
        *key = index;
    }
    else if (TABLE_CONTAINER_KEY_VARBIND_INDEX == tad->key_type) {
        *key = tblreq_info->indexes;
    }
#if 0
    else if (TABLE_CONTAINER_KEY_VARBIND_RAW == tad->key_type) {
        *key = request->requestvb;
    }
#endif
    else
        *key = NULL;
}

static void *
_find_next_row(netsnmp_container *c,
               netsnmp_table_request_info *tblreq,
               void * key)
{
    void *row = NULL;

    if (!c || !tblreq || !tblreq->reg_info ) {
        snmp_log(LOG_ERR,"_find_next_row param error\n");
        return NULL;
    }

    /*
     * table helper should have made sure we aren't below our minimum column
     */
    netsnmp_assert(tblreq->colnum >= tblreq->reg_info->min_column);

    /*
     * if no indexes then use first row.
     */
    if(tblreq->number_indexes == 0) {
        row = CONTAINER_FIRST(c);
    } else {

        if(NULL == key) {
            netsnmp_index index;
            index.oids = tblreq->index_oid;
            index.len = tblreq->index_oid_len;
            row = CONTAINER_NEXT(c, &index);
        }
        else
            row = CONTAINER_NEXT(c, key);

        /*
         * we don't have a row, but we might be at the end of a
         * column, so try the next column.
         */
        if (NULL == row) {
            /*
             * don't set tblreq next_col unless we know there is one,
             * so we don't mess up table handler sparse table processing.
             */
            oid next_col = netsnmp_table_next_column(tblreq);
            if (0 != next_col) {
                tblreq->colnum = next_col;
                row = CONTAINER_FIRST(c);
            }
        }
    }
    
    return row;
}

/**
 * deprecated, backwards compatability only
 *
 * expected impact to remove: none
 *  - used between helpers, shouldn't have been used by end users
 *
 * replacement: none
 *  - never should have been a public method in the first place
 */
netsnmp_index *
netsnmp_table_index_find_next_row(netsnmp_container *c,
                                  netsnmp_table_request_info *tblreq)
{
    return _find_next_row(c, tblreq, NULL );
}


NETSNMP_STATIC_INLINE void
_data_lookup(netsnmp_handler_registration *reginfo,
            netsnmp_agent_request_info *agtreq_info,
            netsnmp_request_info *request, container_table_data * tad)
{
    netsnmp_index *row = NULL;
    netsnmp_table_request_info *tblreq_info;
    netsnmp_variable_list *var;
    netsnmp_index index;
    void *key;

    var = request->requestvb;

    DEBUGIF("table_container") {
        DEBUGMSGTL(("table_container", "  data_lookup oid:"));
        DEBUGMSGOID(("table_container", var->name, var->name_length));
        DEBUGMSG(("table_container", "\n"));
    }

    /*
     * Get pointer to the table information for this request. This
     * information was saved by table_helper_handler.
     */
    tblreq_info = netsnmp_extract_table_info(request);
    /** the table_helper_handler should enforce column boundaries. */
    netsnmp_assert((NULL != tblreq_info) &&
                   (tblreq_info->colnum <= tad->tblreg_info->max_column));
    
    if ((agtreq_info->mode == MODE_GETNEXT) ||
        (agtreq_info->mode == MODE_GETBULK)) {
        /*
         * find the row. This will automatically move to the next
         * column, if necessary.
         */
        _set_key( tad, request, tblreq_info, &key, &index );
        row = _find_next_row(tad->table, tblreq_info, key);
        if (row) {
            /*
             * update indexes in tblreq_info (index & varbind),
             * then update request varbind oid
             */
            if(TABLE_CONTAINER_KEY_NETSNMP_INDEX == tad->key_type) {
                tblreq_info->index_oid_len = row->len;
                memcpy(tblreq_info->index_oid, row->oids,
                       row->len * sizeof(oid));
                netsnmp_update_variable_list_from_index(tblreq_info);
            }
            else if (TABLE_CONTAINER_KEY_VARBIND_INDEX == tad->key_type) {
                netsnmp_update_indexes_from_variable_list(tblreq_info);
            }

            if (TABLE_CONTAINER_KEY_VARBIND_RAW != tad->key_type) {
                netsnmp_table_build_oid_from_index(reginfo, request,
                                                   tblreq_info);
            }
        }
        else {
            /*
             * no results found. Flag the request so lower handlers will
             * ignore it, but it is not an error - getnext will move
             * on to another handler to process this request.
             */
            netsnmp_set_request_error(agtreq_info, request, SNMP_ENDOFMIBVIEW);
            DEBUGMSGTL(("table_container", "no row found\n"));
        }
    } /** GETNEXT/GETBULK */
    else {

        _set_key( tad, request, tblreq_info, &key, &index );
        row = CONTAINER_FIND(tad->table, key);
        if (NULL == row) {
            /*
             * not results found. For a get, that is an error
             */
            DEBUGMSGTL(("table_container", "no row found\n"));
            if((agtreq_info->mode != MODE_SET_RESERVE1) || /* get */
               (reginfo->modes & HANDLER_CAN_NOT_CREATE)) { /* no create */
                netsnmp_set_request_error(agtreq_info, request,
                                          SNMP_NOSUCHINSTANCE);
            }
        }
    } /** GET/SET */
    
    /*
     * save the data and table in the request.
     */
    if (SNMP_ENDOFMIBVIEW != request->status) {
        if (NULL != row)
            netsnmp_request_add_list_data(request,
                                          netsnmp_create_data_list
                                          (TABLE_CONTAINER_ROW,
                                           row, NULL));
        netsnmp_request_add_list_data(request,
                                      netsnmp_create_data_list
                                      (TABLE_CONTAINER_CONTAINER,
                                       tad->table, NULL));
    }
}

/**********************************************************************
 **********************************************************************
 *                                                                    *
 *                                                                    *
 * netsnmp_table_container_helper_handler()                           *
 *                                                                    *
 *                                                                    *
 **********************************************************************
 **********************************************************************/
static int
_container_table_handler(netsnmp_mib_handler *handler,
                         netsnmp_handler_registration *reginfo,
                         netsnmp_agent_request_info *agtreq_info,
                         netsnmp_request_info *requests)
{
    int             rc = SNMP_ERR_NOERROR;
    int             oldmode, need_processing = 0;
    container_table_data *tad;

    /** sanity checks */
    netsnmp_assert((NULL != handler) && (NULL != handler->myvoid));
    netsnmp_assert((NULL != reginfo) && (NULL != agtreq_info));

    DEBUGMSGTL(("table_container", "Mode %s, Got request:\n",
                se_find_label_in_slist("agent_mode",agtreq_info->mode)));

    /*
     * First off, get our pointer from the handler. This
     * lets us get to the table registration information we
     * saved in get_table_container_handler(), as well as the
     * container where the actual table data is stored.
     */
    tad = (container_table_data *)handler->myvoid;

    /*
     * only do data lookup for first pass
     *
     * xxx-rks: this should really be handled up one level. we should
     * be able to say what modes we want to be called for during table
     * registration.
     */
    oldmode = agtreq_info->mode;
    if(MODE_IS_GET(oldmode) || (MODE_SET_RESERVE1 == oldmode)) {
        netsnmp_request_info *curr_request;
        /*
         * Loop through each of the requests, and
         * try to find the appropriate row from the container.
         */
        for (curr_request = requests; curr_request; curr_request = curr_request->next) {
            /*
             * skip anything that doesn't need processing.
             */
            if (curr_request->processed != 0) {
                DEBUGMSGTL(("table_container", "already processed\n"));
                continue;
            }
            
            /*
             * find data for this request
             */
            _data_lookup(reginfo, agtreq_info, curr_request, tad);

            if(curr_request->processed)
                continue;

            ++need_processing;
        } /** for ( ... requests ... ) */
    }
    
    /*
     * send GET instead of GETNEXT to sub-handlers
     * xxx-rks: again, this should be handled further up.
     */
    if ((oldmode == MODE_GETNEXT) && (handler->next)) {
        /*
         * tell agent handlder not to auto call next handler
         */
        handler->flags |= MIB_HANDLER_AUTO_NEXT_OVERRIDE_ONCE;

        /*
         * if we found rows to process, pretend to be a get request
         * and call handler below us.
         */
        if(need_processing > 0) {
            agtreq_info->mode = MODE_GET;
            rc = netsnmp_call_next_handler(handler, reginfo, agtreq_info,
                                           requests);
            if (rc != SNMP_ERR_NOERROR) {
                DEBUGMSGTL(("table_container",
                            "next handler returned %d\n", rc));
            }

            agtreq_info->mode = oldmode; /* restore saved mode */
        }
    }

    return rc;
}
/** @endcond */

/** retrieve the container used by the table_container helper */
netsnmp_container*
netsnmp_container_table_container_extract(netsnmp_request_info *request)
{
    return (netsnmp_container *)
         netsnmp_request_get_list_data(request, TABLE_CONTAINER_CONTAINER);
}

/** inserts a newly created table_container entry into a request list */
void
netsnmp_container_table_row_insert(netsnmp_request_info *request,
                                   netsnmp_index        *row)
{
    netsnmp_request_info       *req;
    netsnmp_table_request_info *table_info = NULL;
    netsnmp_variable_list      *this_index = NULL;
    netsnmp_variable_list      *that_index = NULL;
    oid      base_oid[] = {0, 0};	/* Make sure index OIDs are legal! */
    oid      this_oid[MAX_OID_LEN];
    oid      that_oid[MAX_OID_LEN];
    size_t   this_oid_len, that_oid_len;

    if (!request)
        return;

    /*
     * We'll add the new row information to any request
     * structure with the same index values as the request
     * passed in (which includes that one!).
     *
     * So construct an OID based on these index values.
     */

    table_info = netsnmp_extract_table_info(request);
    this_index = table_info->indexes;
    build_oid_noalloc(this_oid, MAX_OID_LEN, &this_oid_len,
                      base_oid, 2, this_index);

    /*
     * We need to look through the whole of the request list
     * (as received by the current handler), as there's no
     * guarantee that this routine will be called by the first
     * varbind that refers to this row.
     *   In particular, a RowStatus controlled row creation
     * may easily occur later in the variable list.
     *
     * So first, we rewind to the head of the list....
     */
    for (req=request; req->prev; req=req->prev)
        ;

    /*
     * ... and then start looking for matching indexes
     * (by constructing OIDs from these index values)
     */
    for (; req; req=req->next) {
        if (req->processed)
            continue;
        table_info = netsnmp_extract_table_info(req);
        that_index = table_info->indexes;
        build_oid_noalloc(that_oid, MAX_OID_LEN, &that_oid_len,
                          base_oid, 2, that_index);
      
        /*
         * This request has the same index values,
         * so add the newly-created row information.
         */
        if (snmp_oid_compare(this_oid, this_oid_len,
                             that_oid, that_oid_len) == 0) {
            netsnmp_request_add_list_data(req,
                netsnmp_create_data_list(TABLE_CONTAINER_ROW, row, NULL));
        }
    }
}

#ifndef NETSNMP_USE_INLINE
/** find the context data used by the table_container helper */
void *
netsnmp_container_table_row_extract(netsnmp_request_info *request)
{
    /*
     * NOTE: this function must match in table_container.c and table_container.h.
     *       if you change one, change them both!
     */
    return netsnmp_request_get_list_data(request, TABLE_CONTAINER_ROW);
}
#endif /* inline */
