/*
 * Note: this file originally auto-generated by mib2c using
 *  $
 */

#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 <openssl/ssl.h>
#include <openssl/x509.h>
#include <net-snmp/library/cert_util.h>
#include "tlstm-mib.h"

#include "snmpTlstmParamsTable.h"

netsnmp_feature_require(table_tdata);
netsnmp_feature_require(tlstmparams_find);
netsnmp_feature_require(tlstmparams_external);
netsnmp_feature_require(cert_fingerprints);
netsnmp_feature_require(table_tdata_delete_table);
netsnmp_feature_require(table_tdata_extract_table);
netsnmp_feature_require(table_tdata_remove_row);
#ifndef NETSNMP_NO_WRITE_SUPPORT
netsnmp_feature_require(check_vb_storagetype);
netsnmp_feature_require(check_vb_type_and_max_size);
netsnmp_feature_require(check_vb_rowstatus_with_storagetype);
netsnmp_feature_require(table_tdata_insert_row);
#endif /* NETSNMP_NO_WRITE_SUPPORT */

/** XXX - move these to table_data header? */
#define FATE_NEWLY_CREATED    1
#define FATE_NO_CHANGE        0
#define FATE_DELETE_ME        -1

/** **************************************************************************
 *
 * table structures
 *
 */
    /*
     * structure for undo storage and other vars for set processing
     */
 typedef struct snmpTlstmParamsTable_undo_s {
    char  fate;
    char  copied;
    signed char  is_consistent;
    netsnmp_request_info *req[SNMPTLSTMPARAMSTABLE_MAX_COLUMN + 1];
    /* undo Column space */
    char snmpTlstmParamsClientFingerprint[SNMPTLSTMPARAMSCLIENTFINGERPRINT_MAX_SIZE];
    size_t snmpTlstmParamsClientFingerprint_len;
    char snmpTlstmParamsStorageType;
    char snmpTlstmParamsRowStatus;
 } snmpTlstmParamsTable_undo;

    /*
     * Typical data structure for a row entry
     */
typedef struct snmpTlstmParamsTable_entry_s {
    /* Index values */
    char snmpTargetParamsName[SNMPTARGETPARAMSNAME_MAX_SIZE];
    size_t snmpTargetParamsName_len;

    /* Column values */
    char snmpTlstmParamsClientFingerprint[SNMPTLSTMPARAMSCLIENTFINGERPRINT_MAX_SIZE];
    size_t snmpTlstmParamsClientFingerprint_len;
    char snmpTlstmParamsStorageType;
    char snmpTlstmParamsRowStatus;

    char            hashType;
    char            params_flags;

    /* used during set processing */
     snmpTlstmParamsTable_undo *undo;
} snmpTlstmParamsTable_entry;

static Netsnmp_Node_Handler snmpTlstmParamsTable_handler;
static NetsnmpCacheLoad snmpTlstmParamsTable_load;
static NetsnmpCacheFree snmpTlstmParamsTable_free;

static int _tlstmParamsTable_save(int majorID, int minorID,
                                  void *serverarg, void *clientarg);
static int _count_handler(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info *reqinfo,
                          netsnmp_request_info *requests);
static void _tlstmParams_init_persistence(void);
static void _params_add(snmpTlstmParamsTable_entry *entry);
static void _params_remove(snmpTlstmParamsTable_entry *entry);
static void _params_tweak_storage(snmpTlstmParamsTable_entry *entry);

static netsnmp_handler_registration *params_table_reg;
static netsnmp_handler_registration *params_count_reg;
static netsnmp_handler_registration *last_changed_reg;
static netsnmp_table_registration_info *params_table;

static uint32_t                _last_changed = 0;
static netsnmp_tdata          *_table_data = NULL;

/*
 * Initialize the snmpTlstmParamsTable table by defining its contents
 * and how it's structured
 */
void
init_snmpTlstmParamsTable(void)
{
    oid reg_oid[] = {SNMP_TLS_TM_BASE,2,2,1,6};
    const size_t reg_oid_len   = OID_LENGTH(reg_oid);
    netsnmp_cache                   *cache;
    netsnmp_watcher_info            *last_changed_watcher;
    int                              rc;

    DEBUGMSGTL(("tlstmParamsTable:init", "initializing table snmpTlstmParamsTable\n"));

    params_table_reg = netsnmp_create_handler_registration
        ("snmpTlstmParamsTable", snmpTlstmParamsTable_handler, reg_oid,
         reg_oid_len, HANDLER_CAN_RWRITE);
    if (!params_table_reg) {
        snmp_log(LOG_ERR, "snmpTlstmParamsTable registration failed");
        return;
    }
    _table_data = netsnmp_tdata_create_table( "snmpTlstmParamsTable", 0 );
    if (!_table_data) {
        snmp_log(LOG_ERR,"error creating tdata table for snmpTlstmParamsTable\n");
        goto unregister_params_table;
    }
    cache = netsnmp_cache_create(SNMPTLSTMPARAMSTABLE_TIMEOUT,
                                 snmpTlstmParamsTable_load,
                                 snmpTlstmParamsTable_free,
                                 reg_oid, reg_oid_len);
    if (NULL == cache) {
        snmp_log(LOG_ERR,"error creating cache for snmpTlstmParamsTable\n");
        goto delete_table_data;
    }
    cache->magic = (void *)_table_data;
    cache->flags = NETSNMP_CACHE_DONT_INVALIDATE_ON_SET;

    params_table = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info );
    if (!params_table) {
        snmp_log(LOG_ERR,"error creating table info for snmpTlstmParamsTable\n");
        goto free_cache;
    }
    /*
     * populate index types
     */
    netsnmp_table_helper_add_indexes(params_table,
                                     /* index: snmpTargetParamsName */
                                     ASN_PRIV_IMPLIED_OCTET_STR,  0);

    params_table->min_column = SNMPTLSTMPARAMSTABLE_MIN_COLUMN;
    params_table->max_column = SNMPTLSTMPARAMSTABLE_MAX_COLUMN;
    
    rc = netsnmp_tdata_register(params_table_reg, _table_data,
                                params_table);
    if (rc) {
        snmp_log(LOG_ERR, "%s: netsnmp_tdata_register() returned %d\n",
                 __func__, rc);
        goto free_params_table;
    }
    netsnmp_inject_handler_before(params_table_reg,
                                  netsnmp_cache_handler_get(cache),
                                  "table_container");

    /*
     * register scalars
     */
    reg_oid[10] = 4;
    params_count_reg =
        netsnmp_create_handler_registration("snmpTlstmParamsCount",
                                            _count_handler, reg_oid,
                                            OID_LENGTH(reg_oid),
                                            HANDLER_CAN_RONLY);
    if (!params_count_reg) {
        snmp_log(LOG_ERR,
                 "could not create handler for snmpTlstmParamsCount\n");
        goto free_params_table;
    }
    {
        const int rc = netsnmp_register_scalar(params_count_reg);
        if (rc) {
            snmp_log(LOG_ERR, "%s: netsnmp_register_scalar() returned %d\n",
                     __func__, rc);
            goto unreg_params_count;
        }
        netsnmp_inject_handler_before(params_count_reg,
                                      netsnmp_cache_handler_get(cache),
                                      "snmpTlstmParamsCount");
    }
    
    reg_oid[10] = 5;
    last_changed_reg = netsnmp_create_handler_registration(
        "snmpTlstmParamsTableLastChanged", NULL, reg_oid,
        OID_LENGTH(reg_oid), HANDLER_CAN_RONLY);
    if (!last_changed_reg) {
        snmp_log(LOG_ERR,
                 "could not create handler for snmpTlstmParamsTableLastChanged\n");
        goto unreg_params_count;
    }
    last_changed_watcher = netsnmp_create_watcher_info((void*)&_last_changed,
                                          sizeof(_last_changed),
                                          ASN_TIMETICKS,
                                          WATCHER_FIXED_SIZE);
    if (!last_changed_watcher) {
        snmp_log(LOG_ERR,
                 "could not create handler for snmpTlstmParamsTableLastChanged\n");
        goto unreg_last_changed;
    }

    netsnmp_register_watched_scalar2(last_changed_reg, last_changed_watcher);

    /*
     * Initialise the contents of the table here
     */
    _tlstmParams_init_persistence();
    return;

unreg_last_changed:
    netsnmp_tdata_unregister(last_changed_reg);

unreg_params_count:
    netsnmp_tdata_unregister(params_count_reg);

free_params_table:
    netsnmp_table_registration_info_free(params_table);

free_cache:
    netsnmp_cache_free(cache);

delete_table_data:
    netsnmp_tdata_delete_table(_table_data);
    _table_data = NULL;

unregister_params_table:
    netsnmp_tdata_unregister(params_table_reg);
}

void
shutdown_snmpTlstmParamsTable(void)
{
    snmp_unregister_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_STORE_DATA,
                             _tlstmParamsTable_save, _table_data->container, 1);
    netsnmp_tdata_unregister(last_changed_reg);
    netsnmp_tdata_unregister(params_count_reg);
    netsnmp_tdata_unregister(params_table_reg);
    netsnmp_table_registration_info_free(params_table);
}

/** **************************************************************************
 *
 * utility functions for table structures
 *
 */
/* create a new row in the table */
netsnmp_tdata_row *
snmpTlstmParamsTable_createEntry(netsnmp_tdata *table_data,
                                 char* snmpTargetParamsName,
                                 size_t snmpTargetParamsName_len ) {
    snmpTlstmParamsTable_entry *entry;
    netsnmp_tdata_row *row;

    if ((NULL == snmpTargetParamsName) || (snmpTargetParamsName_len >
                                           sizeof(entry->snmpTargetParamsName)))
        return NULL;

    entry = SNMP_MALLOC_TYPEDEF(snmpTlstmParamsTable_entry);
    if (!entry)
        return NULL;

    row = netsnmp_tdata_create_row();
    if (!row) {
        SNMP_FREE(entry);
        return NULL;
    }
    row->data = entry;

    DEBUGMSGT(("tlstmParamsTable:entry:create", "entry %p / row %p\n",
               entry, row));

    DEBUGIF("snmpTlstmParamTable:entry:create") {
        char name[sizeof(entry->snmpTargetParamsName)+1];
        snprintf(name, sizeof(name), "%s", snmpTargetParamsName);
        DEBUGMSGT(("tlstmParamsTable:entry:create",
                   "entry %s %p / row %p\n",
                   name, entry, row));
    }

    /*
     * populate index
     */
    memcpy(entry->snmpTargetParamsName, snmpTargetParamsName,
           snmpTargetParamsName_len);
    entry->snmpTargetParamsName_len = snmpTargetParamsName_len;
    netsnmp_tdata_row_add_index( row, ASN_PRIV_IMPLIED_OCTET_STR,
                                 entry->snmpTargetParamsName,
                                 snmpTargetParamsName_len);

    entry->snmpTlstmParamsClientFingerprint[0] = '\0';
    entry->snmpTlstmParamsClientFingerprint_len = 0;
    entry->snmpTlstmParamsRowStatus = RS_NOTREADY;
    entry->snmpTlstmParamsStorageType = ST_NONVOLATILE;
        
    if (table_data) {
        DEBUGMSGTL(("tlstmParamsTable:row:insert", "row %p\n",
                    row));
        netsnmp_tdata_add_row( table_data, row );
    }
    return row;
}

/* allocate undo resources */
static snmpTlstmParamsTable_undo *
_allocUndo(snmpTlstmParamsTable_entry *entry)
{
    if (!entry)
        return NULL;
    
    entry->undo = SNMP_MALLOC_TYPEDEF(snmpTlstmParamsTable_undo);
    if (!entry->undo)
        return NULL;

    entry->undo->is_consistent = -1; /* don't know */

    /* TODO: allocated any other resources needed */

    return entry->undo;
}

/* free undo resources */
static void
_freeUndo(snmpTlstmParamsTable_entry *entry)
{
    if (!entry || !entry->undo)
        return;

    /* TODO: release any allocated resources */
    SNMP_FREE(entry->undo);
}

/* remove a row from the table */
void
snmpTlstmParamsTable_removeEntry(netsnmp_tdata     *table_data, 
                                 netsnmp_tdata_row *row)
{
    snmpTlstmParamsTable_entry *entry;

    if (!row)
        return;    /* Nothing to remove */

    entry = (snmpTlstmParamsTable_entry *)row->data;

    DEBUGMSGT(("tlstmParamsTable:entry:delete", "entry %p / row %p\n",
               entry, row));

    if (table_data) {
        DEBUGMSGTL(("tlstmParamsTable:row:remove", "row %p\n", row));
        netsnmp_tdata_remove_and_delete_row( table_data, row );
    }
    else
        netsnmp_tdata_delete_row( row );    

    if (entry && entry->undo)
        _freeUndo(entry);
    SNMP_FREE( entry ); /* TODO - release any other internal resources */
}

/** **************************************************************************
 *
 * handle cache / interactions with snmpTlstmParams container in snmplib
 *
 */
static void
_params_add(snmpTlstmParamsTable_entry *entry)
{
    snmpTlstmParams     *params;

    if (NULL == entry)
        return;

    DEBUGMSGTL(("tlstmParamsTable:params:add", "name %s, fp %s\n",
                entry->snmpTargetParamsName, entry->snmpTlstmParamsClientFingerprint));

    params =
        netsnmp_tlstmParams_create(entry->snmpTargetParamsName,
                                   entry->hashType,
                                   entry->snmpTlstmParamsClientFingerprint,
                                   entry->snmpTlstmParamsClientFingerprint_len);
    if (NULL == params)
        return;

    params->flags = TLSTM_PARAMS_FROM_MIB;
    if (entry->snmpTlstmParamsStorageType == ST_NONVOLATILE)
        params->flags |= TLSTM_PARAMS_NONVOLATILE;

    netsnmp_tlstmParams_add(params);
}

static void
_params_remove(snmpTlstmParamsTable_entry *entry)
{
    snmpTlstmParams      index, *found;

    if (NULL == entry)
        return;

    DEBUGMSGTL(("tlstmParamsTable:params:remove", "name %s\n",
                entry->snmpTargetParamsName));

    index.name = entry->snmpTargetParamsName;
    found = netsnmp_tlstmParams_find(&index);
    if (found) {
        netsnmp_tlstmParams_remove(found);
        netsnmp_tlstmParams_free(found);
    }
    entry->params_flags = 0;
}

static void
_params_tweak_storage(snmpTlstmParamsTable_entry *entry)
{
    snmpTlstmParams     *params, index;

    if (NULL == entry)
        return;

    DEBUGMSGTL(("tlstmParamsTable:params:tweak", "name %s, st %d\n",
                entry->snmpTargetParamsName, entry->snmpTlstmParamsStorageType));

    index.name = entry->snmpTargetParamsName;
    params = netsnmp_tlstmParams_find(&index);
    if (NULL == params) {
        DEBUGMSGTL(("tlstmParamsTable:params:tweak",
                    "couldn't find params!\n"));
        return;
    }

    if (entry->snmpTlstmParamsStorageType == ST_NONVOLATILE)
        params->flags |= TLSTM_PARAMS_NONVOLATILE;
    else
        params->flags &= ~TLSTM_PARAMS_NONVOLATILE;
}

static netsnmp_tdata_row *
_entry_from_params(snmpTlstmParams  *params)
{
    netsnmp_tdata_row *row;
    snmpTlstmParamsTable_entry *entry;

    row = snmpTlstmParamsTable_createEntry(NULL, params->name,
                                       strlen(params->name));
    if (NULL == row) {
        snmp_log(LOG_ERR, "can create snmpTlstmParams row entry\n");
        return NULL;
    }
    entry = row->data;

    if (params->flags & TLSTM_PARAMS_FROM_CONFIG)
        entry->snmpTlstmParamsStorageType = ST_PERMANENT;
    else if (! (params->flags & TLSTM_PARAMS_NONVOLATILE))
        entry->snmpTlstmParamsStorageType = ST_VOLATILE;

    entry->snmpTlstmParamsRowStatus = RS_ACTIVE;

    if (params->fingerprint) {
        entry->snmpTlstmParamsClientFingerprint_len = 
            strlen(params->fingerprint);
        if (entry->snmpTlstmParamsClientFingerprint_len >
            sizeof(entry->snmpTlstmParamsClientFingerprint))
            entry->snmpTlstmParamsClientFingerprint_len =
                sizeof(entry->snmpTlstmParamsClientFingerprint) - 1;
        memcpy(entry->snmpTlstmParamsClientFingerprint, params->fingerprint,
               entry->snmpTlstmParamsClientFingerprint_len);
        entry->snmpTlstmParamsClientFingerprint[sizeof(entry->snmpTlstmParamsClientFingerprint) - 1] = 0;
    }
    entry->hashType = params->hashType;
    entry->params_flags = params->flags;

    return row;
}

static int
snmpTlstmParamsTable_load( netsnmp_cache *cache, void *vmagic )
 {
    netsnmp_tdata     *table = (netsnmp_tdata *)vmagic;
    netsnmp_tdata_row *row;
    netsnmp_container *active_params;
    netsnmp_iterator  *itr;
    snmpTlstmParams   *params;
    int                rc = 0;

    active_params = netsnmp_tlstmParams_container();
    if (NULL == active_params)
        return 0;

    DEBUGMSGTL(("tlstmParamsTable:cache:load", "snmpTlstmParams %" NETSNMP_PRIz "d rows\n",
                CONTAINER_SIZE(active_params)));
    itr = CONTAINER_ITERATOR(active_params);
    if (NULL == itr) {
        DEBUGMSGTL(("tlstmParamsTable:cache:load",
                    "cant get iterator\n"));
        return -1;
    }

    /*
     * insert rows for active params into tbl container
     */
    params = ITERATOR_FIRST(itr);
    for( ; params; params = ITERATOR_NEXT(itr)) {

        row = _entry_from_params(params);
        if (NULL == row) {
            rc =-1;
            break;
        }
        if (netsnmp_tdata_add_row(table, row) != SNMPERR_SUCCESS) {
            snmpTlstmParamsTable_removeEntry(NULL, row);
            rc = -1;
            break;
        }
    }
    ITERATOR_RELEASE(itr);

    DEBUGMSGTL(("tlstmParamsTable:cache:load", "done, %" NETSNMP_PRIz "d rows\n",
                CONTAINER_SIZE(table->container)));

    return rc;
}

static void
snmpTlstmParamsTable_free( netsnmp_cache *cache, void *vmagic )
{
    netsnmp_tdata            *table = (netsnmp_tdata *)vmagic;
    netsnmp_tdata_row        *row;
    netsnmp_iterator         *tbl_itr;
    snmpTlstmParamsTable_entry   *entry;

    DEBUGMSGTL(("tlstmParamsTable:cache:free", "called, %" NETSNMP_PRIz "d rows\n",
                CONTAINER_SIZE(table->container)));

    tbl_itr = CONTAINER_ITERATOR(table->container);
    if (NULL == tbl_itr) {
        DEBUGMSGTL(("tlstmParamsTable:cache:free",
                    "cant get entry iterator\n"));
        return;
    }

    row = ITERATOR_FIRST(tbl_itr);
    for( ; row; row = ITERATOR_NEXT(tbl_itr)) {
        entry = row->data;

        /*
         * remove all active rows (they are in the params container kept
         * by the library). Keep inactive ones for next time.
         */
        if (entry->snmpTlstmParamsRowStatus == RS_ACTIVE) {
            snmpTlstmParamsTable_removeEntry(NULL, row);
            ITERATOR_REMOVE(tbl_itr);
            continue;
        }
    }
    ITERATOR_RELEASE(tbl_itr);

    DEBUGMSGTL(("tlstmParamsTable:cache:free", "done, %" NETSNMP_PRIz "d rows\n",
                CONTAINER_SIZE(table->container)));
}

/** **************************************************************************
 *
 * handles requests for the snmpTlstmParamsTable table
 *
 */
static int
snmpTlstmParamsTable_handler(
    netsnmp_mib_handler               *handler,
    netsnmp_handler_registration      *reginfo,
    netsnmp_agent_request_info        *reqinfo,
    netsnmp_request_info              *requests) {

    netsnmp_request_info       *request = NULL;
    netsnmp_table_request_info *table_info;
    netsnmp_tdata              *table_data;
    snmpTlstmParamsTable_entry          *table_entry;
    int                         ret = SNMP_ERR_NOERROR;
    
    DEBUGMSGTL(("tlstmParamsTable:handler", "Processing %s mode (%d) request\n",
                se_find_label_in_slist("agent_mode", reqinfo->mode),
                reqinfo->mode));

    switch (reqinfo->mode) {
    /** ######################################################### GET #####
     *
     *   Read-support (also covers GetNext requests)
     */
    case MODE_GET:
        for (request=requests; request; request=request->next) {
            if (request->processed)
                continue;

            table_entry = (snmpTlstmParamsTable_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_info  =     netsnmp_extract_table_info( request);
            switch (table_info->colnum) {
            case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT: 
            {
                u_char bin[42], *ptr = bin;
                size_t len = sizeof(bin), offset = 1;
                int    rc;
                bin[0] = table_entry->hashType;
                netsnmp_assert(table_entry->hashType != 0);
                rc = netsnmp_hex_to_binary(
                    &ptr, &len, &offset, 0,
                    table_entry->snmpTlstmParamsClientFingerprint, NULL);
                if (1 != rc)
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_GENERR);
                else
                    snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR,
                                             bin, offset);
            }
                break; /* case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT */
            case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE:
                snmp_set_var_typed_integer( request->requestvb, ASN_INTEGER,
                                            table_entry->snmpTlstmParamsStorageType);
                break; /* case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE */
            case COLUMN_SNMPTLSTMPARAMSROWSTATUS:
                snmp_set_var_typed_integer( request->requestvb, ASN_INTEGER,
                                            table_entry->snmpTlstmParamsRowStatus);
                break; /* case COLUMN_SNMPTLSTMPARAMSROWSTATUS */
            default:
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHOBJECT);
                break;
            } /* switch colnum */
        } /* for requests */
        break; /* case MODE_GET */

        /*
         * Write-support
         */
    /** #################################################### RESERVE1 #####
     *
     *   In RESERVE1 we are just checking basic ASN.1 size/type restrictions.
     * You probably don't need to change any of this code. Don't change any
     * of the column values here. Save that for the ACTION phase.
     *
     *   The next phase is RESERVE2 or FREE.
     */
    case MODE_SET_RESERVE1:
        for (request=requests; request; request=request->next) {
            table_entry = (snmpTlstmParamsTable_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_info  =     netsnmp_extract_table_info( request);

            if ((NULL != table_entry) &&
                (ST_READONLY == table_entry->snmpTlstmParamsStorageType)) {
                ret = SNMP_ERR_NOTWRITABLE;
                break;
            }
            
            /*
             * for each column, allocate any additional resources needed
             * beyond what is in the undo structure.
             */
            switch (table_info->colnum) {
            case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT:
                ret = netsnmp_check_vb_type_and_max_size(
                          request->requestvb, ASN_OCTET_STR, sizeof(table_entry->snmpTlstmParamsClientFingerprint));
                /** check len/algorithm MIB requirements */
                if (SNMP_ERR_NOERROR == ret)
                    ret = netsnmp_cert_check_vb_fingerprint(request->requestvb);

                break; /* case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT */

            case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE:
                ret = netsnmp_check_vb_storagetype(request->requestvb,
                         (table_entry ? table_entry->snmpTlstmParamsStorageType : ST_NONE ));
                break; /* case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE */

            case COLUMN_SNMPTLSTMPARAMSROWSTATUS:
                ret = netsnmp_check_vb_rowstatus_with_storagetype(request->requestvb,
                         (table_entry ? table_entry->snmpTlstmParamsRowStatus : RS_NONEXISTENT ),
                         (table_entry ? table_entry->snmpTlstmParamsStorageType : ST_NONE));
                break; /* case COLUMN_SNMPTLSTMPARAMSROWSTATUS */
            default:
                ret = SNMP_ERR_NOTWRITABLE;
            } /* switch colnum */

            if ( ret != SNMP_ERR_NOERROR )
                break;
        }/* for requests */
        break; /* case MODE_SET_RESERVE1 */

    /** #################################################### RESERVE2 #####
     *
     *   RESERVE2 is for checking additional restrictions from the MIB.
     * Since these restrictions are often in the description of the object,
     * mib2c can't generate code. It's possible that you need to add
     * additional checks here. However, don't change any of the column
     * values here. Save that for the ACTION phase.
     *
     *   The next phase is ACTION or FREE.
     */
    case MODE_SET_RESERVE2:
        for (request=requests; request; request=request->next) {
            netsnmp_tdata_row *table_row = NULL;

            table_entry = (snmpTlstmParamsTable_entry *)
                netsnmp_tdata_extract_entry(request);
            table_data = netsnmp_tdata_extract_table(request);
            table_info = netsnmp_extract_table_info( request);
            /*
             * if no table_row, create one
             */
            if ( !table_entry ) {
                table_row = snmpTlstmParamsTable_createEntry
                    (table_data,  (char*)table_info->indexes->val.string, 
                     table_info->indexes->val_len);
                if (!table_row) {
                    ret = SNMP_ERR_RESOURCEUNAVAILABLE;
                    break;
                }
                table_entry = table_row->data;
                _allocUndo(table_entry);
                if (table_entry && !table_entry->undo) {
                    snmpTlstmParamsTable_removeEntry(table_data, table_row);
                    table_row = NULL;
                    ret = SNMP_ERR_RESOURCEUNAVAILABLE;
                    break;
                }
                table_entry->undo->fate = FATE_NEWLY_CREATED;
                /** associate row with requests */
                netsnmp_insert_tdata_row( request, table_row );
            }
            
            /** allocate undo structure, if needed */
            if(!table_entry->undo) {
                _allocUndo(table_entry);
                if (!table_entry->undo) {
                    ret = SNMP_ERR_RESOURCEUNAVAILABLE;
                    break;
                }
            }
            
            /** don't allow multiple sets of same column */
            if (table_entry->undo->req[table_info->colnum]) {
                DEBUGMSGT(("tlstmParamsTable:reserve2",
                           "multiple sets to col %d in request\n",
                           table_info->colnum));
                ret = SNMP_ERR_INCONSISTENTNAME;
                break;
            }
            table_entry->undo->req[table_info->colnum] = request;
            
            if ( ret != SNMP_ERR_NOERROR )
                break;
        } /* for requests */

        /** make sure rowstatus is used to create rows */
        if ( ret == SNMP_ERR_NOERROR ) {
            for (request = requests; request; request = request->next) {
                if (request->processed)
                    continue;

                table_entry = (snmpTlstmParamsTable_entry *)
                    netsnmp_tdata_extract_entry(request);
                if ((table_entry->undo->fate != FATE_NEWLY_CREATED) ||
                    (table_entry->undo->req[COLUMN_SNMPTLSTMPARAMSROWSTATUS]))
                    continue;
                ret = SNMP_ERR_INCONSISTENTNAME;
            break;
            } /* for requests / creation */
        } /* if no error */
        break; /* case MODE_SET_RESERVE2 */

    /** ######################################################## FREE #####
     *
     *   FREE is for cleaning up after a failed request (during either
     * RESERVE1 or RESERVE2). So any allocated resources need to be
     * released.
     *
     *   This the final phase for this path in the state machine.
     */
    case MODE_SET_FREE:
        /*
         * release undo resources
         * remove any newly created rows
         */
        for (request=requests; request; request=request->next) {
            netsnmp_tdata_row *table_row = netsnmp_tdata_extract_row(request);

            table_data  =  netsnmp_tdata_extract_table(request);
            table_entry = (snmpTlstmParamsTable_entry *)
                              netsnmp_tdata_extract_entry(request);
            
            if ( !table_entry || !table_entry->undo )
                continue;

            /** disassociate row with requests */
            netsnmp_remove_tdata_row( request, table_row );

            if (FATE_NEWLY_CREATED == table_entry->undo->fate )
                snmpTlstmParamsTable_removeEntry( table_data, table_row );
            else
                _freeUndo(table_entry);
        }
        break; /* case MODE_SET_FREE */

    /** ###################################################### ACTION #####
     *
     *   In the ACTION phase, we perform any sets that can be undone.
     * (Save anything that can't be undone for the COMMIT phase.)
     *
     *   After individual columns have been done, you should check that the
     * row as a whole is consistent.
     *
     * The next phase is UNDO or COMMIT.
     */
    case MODE_SET_ACTION:
        for (request=requests; request; request=request->next) {
            table_entry = (snmpTlstmParamsTable_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_info  =     netsnmp_extract_table_info( request);

            switch (table_info->colnum) {
            case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT:
            {
                u_char *tmp = (u_char*)table_entry->snmpTlstmParamsClientFingerprint;

                memcpy( table_entry->undo->snmpTlstmParamsClientFingerprint,
                        table_entry->snmpTlstmParamsClientFingerprint,
                        sizeof(table_entry->snmpTlstmParamsClientFingerprint));
                table_entry->undo->snmpTlstmParamsClientFingerprint_len =
                    table_entry->snmpTlstmParamsClientFingerprint_len;

                table_entry->hashType = request->requestvb->val.string[0];
                memset( table_entry->snmpTlstmParamsClientFingerprint, 0,
                        sizeof(table_entry->snmpTlstmParamsClientFingerprint));
                table_entry->snmpTlstmParamsClientFingerprint_len =
                    sizeof(table_entry->snmpTlstmParamsClientFingerprint);
                table_entry->snmpTlstmParamsClientFingerprint_len =
                    netsnmp_binary_to_hex(&tmp, &table_entry->snmpTlstmParamsClientFingerprint_len,
                                          0, &request->requestvb->val.string[1],
                                          request->requestvb->val_len - 1);
                if (0 == table_entry->snmpTlstmParamsClientFingerprint_len)
                    ret = SNMP_ERR_GENERR;
            }
                break; /* case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT */

            case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE:
                /* save snmpTlstmParamsStorageType value */
                table_entry->undo->snmpTlstmParamsStorageType = table_entry->snmpTlstmParamsStorageType;
                /* get value from varbind */
                table_entry->snmpTlstmParamsStorageType = *request->requestvb->val.integer;
                break; /* case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE */

            case COLUMN_SNMPTLSTMPARAMSROWSTATUS:
                /* save snmpTlstmParamsRowStatus value */
                table_entry->undo->snmpTlstmParamsRowStatus = table_entry->snmpTlstmParamsRowStatus;
                /* get value from varbind  */
                table_entry->snmpTlstmParamsRowStatus     = *request->requestvb->val.integer;
                break; /* case COLUMN_SNMPTLSTMPARAMSROWSTATUS */
            } /* switch colnum */
            if ( ret != SNMP_ERR_NOERROR )
                break;
        } /* set values for requests */

        if ( ret == SNMP_ERR_NOERROR ) {

            /*
             * All columns now have their final values set. check the
             * internal consistency of each row.
             */
            for (request=requests; request; request=request->next) {
                table_entry = (snmpTlstmParamsTable_entry *)
                    netsnmp_tdata_extract_entry(request);
                table_info  =     netsnmp_extract_table_info( request);
                
                if (table_entry->undo->is_consistent != -1)
                    continue; /* already checked */
                
                /** assume consistency */
                table_entry->undo->is_consistent = 1;
                
                if ((RS_IS_ACTIVE(table_entry->snmpTlstmParamsRowStatus)) &&
                    ((!table_entry->undo->req[COLUMN_SNMPTLSTMPARAMSROWSTATUS]) ||
                     (RS_IS_ACTIVE(table_entry->undo->snmpTlstmParamsRowStatus)))) {
                    /*
                     * check mib restrictions on active rows.
                     */
                    if (table_entry->undo->req[COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT]) {
                        table_entry->undo->is_consistent = 0;
                        request = table_entry->undo->req[COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT];
                        ret = SNMP_ERR_INCONSISTENTVALUE; /* per mib */
                    }
                } /* active row */
                else if (RS_IS_GOING_ACTIVE(table_entry->snmpTlstmParamsRowStatus)) {
                    /*
                     * check restrictions for activating a row
                     */
                    /** if going active, inconsistency is fatal */
                    if (!table_entry->undo->is_consistent) {
                        if (FATE_NEWLY_CREATED == table_entry->undo->fate)
                            ret = SNMP_ERR_INCONSISTENTNAME;
                        else
                            ret = SNMP_ERR_INCONSISTENTVALUE;
                        request = table_entry->undo->req[COLUMN_SNMPTLSTMPARAMSROWSTATUS];
                    }
                } /* going active */
                else if (RS_DESTROY == table_entry->snmpTlstmParamsRowStatus) {
                    /*
                     * TODO: check restrictions for deleting a row
                     */
                    /** can't delete active row */
                    if (RS_IS_ACTIVE(table_entry->undo->snmpTlstmParamsRowStatus)) {
                        ret = SNMP_ERR_INCONSISTENTVALUE;
                        request = table_entry->undo->req[COLUMN_SNMPTLSTMPARAMSROWSTATUS];
                    }
                } /* destroy */
                if ( ret != SNMP_ERR_NOERROR )
                    break;
            } /* consistency for requests */
        } /* if no error */
        break; /* case MODE_SET_ACTION */

    /** ######################################################## UNDO #####
     *
     *   UNDO is for cleaning up any failed requests that went through the
     * ACTION phase.
     *
     *   This the final phase for this path in the state machine.
     */
    case MODE_SET_UNDO:
        for (request=requests; request; request=request->next) {
            table_entry = (snmpTlstmParamsTable_entry *)
                              netsnmp_tdata_extract_entry(request);
            table_data  =     netsnmp_tdata_extract_table(request);
            table_info  =     netsnmp_extract_table_info( request);

            switch (table_info->colnum) {
            case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT:
                /* restore snmpTlstmParamsClientFingerprint value */
                memcpy( table_entry->snmpTlstmParamsClientFingerprint,
                        table_entry->undo->snmpTlstmParamsClientFingerprint,
                        sizeof(table_entry->snmpTlstmParamsClientFingerprint));
                table_entry->snmpTlstmParamsClientFingerprint_len =
                    table_entry->undo->snmpTlstmParamsClientFingerprint_len;
                break; /* case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT */

            case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE:
                /* restore snmpTlstmParamsStorageType value */
                table_entry->snmpTlstmParamsStorageType =
                    table_entry->undo->snmpTlstmParamsStorageType;
                break; /* case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE */

                case COLUMN_SNMPTLSTMPARAMSROWSTATUS:
                    /* restore snmpTlstmParamsRowStatus value */
                    table_entry->snmpTlstmParamsRowStatus = table_entry->undo->snmpTlstmParamsRowStatus;
                    break; /* case COLUMN_SNMPTLSTMPARAMSROWSTATUS */
            } /* switch colnum */
        } /* for requests */

        /*
         * release undo data
         * or remove any newly created rows
         */
        for (request=requests; request; request=request->next) {
            netsnmp_tdata_row *table_row = netsnmp_tdata_extract_row(request);

            table_entry = (snmpTlstmParamsTable_entry *)
                              netsnmp_tdata_extract_entry(request);
            
            if ( !table_entry || !table_entry->undo )
                continue;

            /** disassociate row with requests */
            netsnmp_remove_tdata_row( request, table_row );
            
            if (FATE_NEWLY_CREATED == table_entry->undo->fate )
                snmpTlstmParamsTable_removeEntry( table_data, table_row );
            else
                _freeUndo(table_entry);
        } /* for requests */
        break; /* case MODE_SET_UNDO */

    /** ###################################################### COMMIT #####
     *
     *   COMMIT is the final success state, when all changes are finalized.
     * There is not recovery state should something fail here.
     *
     *   This the final phase for this path in the state machine.
     */
    case MODE_SET_COMMIT:
        for (request=requests; request; request=request->next) {
            netsnmp_tdata_row *table_row = netsnmp_tdata_extract_row(request);

            table_data  =     netsnmp_tdata_extract_table(request);
            table_info  =     netsnmp_extract_table_info(    request);
            table_entry = (snmpTlstmParamsTable_entry *)
                              netsnmp_tdata_extract_entry(request);

            if (!table_entry || !table_entry->undo)
                continue;
            
            if ((RS_NOTREADY == table_entry->snmpTlstmParamsRowStatus) &&
                table_entry->undo->is_consistent)
                table_entry->snmpTlstmParamsRowStatus = RS_NOTINSERVICE;
            else if ((RS_NOTINSERVICE == table_entry->snmpTlstmParamsRowStatus) &&
                     (0 == table_entry->undo->is_consistent))
                table_entry->snmpTlstmParamsRowStatus = RS_NOTREADY;

            /** release undo data for requests with no rowstatus */
            if (table_entry->undo &&
                table_entry->undo->req[COLUMN_SNMPTLSTMPARAMSROWSTATUS] == NULL) {
                _freeUndo(table_entry);
                
                /** update active addrs */
                if ((0 == table_entry->params_flags) &&
                    (table_entry->snmpTlstmParamsRowStatus == RS_ACTIVE))
                    _params_add(table_entry);
                else if ((0 != table_entry->params_flags) &&
                         (table_entry->snmpTlstmParamsRowStatus == RS_DESTROY))
                    _params_remove(table_entry);
            }

            switch (table_info->colnum) {

            case COLUMN_SNMPTLSTMPARAMSROWSTATUS:
                switch (table_entry->snmpTlstmParamsRowStatus) {
                case RS_CREATEANDGO:
                    /** Fall-through */
                case RS_ACTIVE:
                    table_entry->snmpTlstmParamsRowStatus = RS_ACTIVE;
                    if (0 == table_entry->params_flags)
                        _params_add(table_entry);
                    break;

                case RS_CREATEANDWAIT:
                    /** Fall-through */
                case RS_NOTINSERVICE:
                    /** simply set status based on consistency */
                    if (table_entry->undo->is_consistent)
                        table_entry->snmpTlstmParamsRowStatus = RS_NOTINSERVICE;
                    else
                        table_entry->snmpTlstmParamsRowStatus = RS_NOTREADY;
                    if (0 != table_entry->params_flags)
                        _params_remove(table_entry);
                    break;

                case RS_DESTROY:
                    if (0 != table_entry->params_flags)
                        _params_remove(table_entry);
                    /** disassociate row with requests */
                    netsnmp_remove_tdata_row( request, table_row );
                    snmpTlstmParamsTable_removeEntry(table_data, table_row );
                    table_entry = NULL;
                }
                /** release undo data */
                _freeUndo(table_entry);
                break; /* case COLUMN_SNMPTLSTMPARAMSROWSTATUS */

            case COLUMN_SNMPTLSTMPARAMSSTORAGETYPE:
                    if (RS_ACTIVE == table_entry->snmpTlstmParamsRowStatus)
                        _params_tweak_storage(table_entry);
                    break;

            case COLUMN_SNMPTLSTMPARAMSCLIENTFINGERPRINT:
                break;
            } /* switch colnum */
        } /* for requests */

        /** update last changed */
        _last_changed = netsnmp_get_agent_uptime();

        /** set up to save persistent store */
        snmp_store_needed(NULL);

        break; /* case MODE_SET_COMMIT */
    }  /* switch (reqinfo->mode) */
    
    if ( ret != SNMP_ERR_NOERROR )
        netsnmp_set_request_error( reqinfo, request, ret);
    
    return SNMP_ERR_NOERROR;
}


static int
_count_handler(netsnmp_mib_handler *handler,
               netsnmp_handler_registration *reginfo,
               netsnmp_agent_request_info *reqinfo,
               netsnmp_request_info *requests)
{
    int                val;

    if (MODE_GET != reqinfo->mode) {
        snmp_log(LOG_ERR, "bad mode in RO handler");
        return SNMP_ERR_GENERR;
    }

    if (NULL == _table_data->container)
        val = 0;
    else
        val = CONTAINER_SIZE(_table_data->container);

    snmp_set_var_typed_value(requests->requestvb, ASN_GAUGE,
                             (u_char *) &val, sizeof(val));
   
    if (handler->next && handler->next->access_method)
        return netsnmp_call_next_handler(handler, reginfo, reqinfo,
                                         requests);
    
    return SNMP_ERR_NOERROR;
}

/***********************************************************************
 *
 * PERSISTENCE
 *
 ***********************************************************************/

static int _save_params(snmpTlstmParams *params, void *app_type);
static int _save_entry(snmpTlstmParamsTable_entry *entry, void *type);
static void _tlstmParamsTable_row_restore_mib(const char *token, char *buf);

static const char mib_token[] = "snmpTlstmParamsEntry";

/************************************************************
 * *_init_persistence should be called from the main table
 * init routine.
 *
 * If your table depends on rows in another table,
 * you should register your callback after the other table,
 * which should ensure the rows on which you depend are saved
 * (and re-created) before the dependent rows.
 */
void
_tlstmParams_init_persistence(void)
{
    int             rc;

    register_config_handler(NULL, mib_token, _tlstmParamsTable_row_restore_mib,
                            NULL, NULL);
    rc = snmp_register_callback(SNMP_CALLBACK_LIBRARY,
                                SNMP_CALLBACK_STORE_DATA,
                                _tlstmParamsTable_save,
                                _table_data->container);

    if (rc != SNMP_ERR_NOERROR)
        snmp_log(LOG_ERR, "error registering for STORE_DATA callback "
                 "in _tlstmParams_init_persistence\n");
}

static int
_tlstmParamsTable_save(int majorID, int minorID, void *serverarg,
                       void *clientarg)
{
    char            sep[] =
        "##############################################################";
    char            buf[] = "#\n" "# tlstmParams persistent data\n" "#";
    char           *type = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
                                                 NETSNMP_DS_LIB_APPTYPE);

    netsnmp_container *active_params = netsnmp_tlstmParams_container();
    netsnmp_tdata_row *row;
    netsnmp_iterator  *tbl_itr, *params_itr;
    snmpTlstmParams   *params;
    snmpTlstmParamsTable_entry *entry;

    if ((CONTAINER_SIZE(active_params) == 0) &&
        (CONTAINER_SIZE(_table_data->container) == 0))
        return SNMPERR_SUCCESS;

    read_config_store((char *) type, sep);
    read_config_store((char *) type, buf);

    /*
     * save active rows from params container
     */
    if (NULL != active_params) {
        params_itr = CONTAINER_ITERATOR(active_params);
        if (NULL == params_itr) {
            DEBUGMSGTL(("tlstmParamsTable:save",
                        "cant get params iterator\n"));
            params = NULL;
        }
        else
            params = ITERATOR_FIRST(params_itr);

        for( ; params; params = ITERATOR_NEXT(params_itr)) {
            /** don't store config rows */
            if ((params->flags & TLSTM_PARAMS_FROM_CONFIG) ||
                ! (params->flags & TLSTM_PARAMS_NONVOLATILE))
                continue;
            _save_params(params, type);
        }
        ITERATOR_RELEASE(params_itr);
    }

    /*
     * save inactive rows from mib
     */
    tbl_itr = CONTAINER_ITERATOR(_table_data->container);
    if (NULL == tbl_itr)
        DEBUGMSGTL(("tlstmParamsTable:save", "cant get table iterator\n"));
    else {
        row = ITERATOR_FIRST(tbl_itr);
        for( ; row; row = ITERATOR_NEXT(tbl_itr)) {
            entry = row->data;

            /*
             * skip all active rows (should be in active_params and thus saved
             * above) and volatile rows.
             */
            if ((entry->snmpTlstmParamsRowStatus == RS_ACTIVE) ||
                (entry->snmpTlstmParamsStorageType != ST_NONVOLATILE))
                continue;

            _save_entry(entry, type);
        }
        ITERATOR_RELEASE(tbl_itr);
    }

    read_config_store((char *) type, sep);
    read_config_store((char *) type, "\n");

    /*
     * never fails 
     */
    return SNMPERR_SUCCESS;
}

/************************************************************
 * _tlstmParamsTable_container_row_save
 */
static int
_save_entry(snmpTlstmParamsTable_entry *entry, void *type)
{
    char   buf[SNMP_MAXBUF_SMALL], *hashType;

    hashType = se_find_label_in_slist("cert_hash_alg", entry->hashType);
    if (NULL == hashType) {
        snmp_log(LOG_ERR, "skipping entry unknown hash type %d\n",
                 entry->hashType);
        return SNMP_ERR_GENERR;
    }

    /*
     * build the line
     */
    netsnmp_assert(0 == entry->snmpTargetParamsName[
                       entry->snmpTargetParamsName_len]);
    netsnmp_assert(0 == entry->snmpTlstmParamsClientFingerprint[
                       entry->snmpTlstmParamsClientFingerprint_len]);
    snprintf(buf, sizeof(buf), "%s %s %s %s %d", mib_token,
             entry->snmpTargetParamsName, hashType,
             entry->snmpTlstmParamsClientFingerprint,
             entry->snmpTlstmParamsRowStatus);
    buf[sizeof(buf)-1] = 0;

    read_config_store(type, buf);
    DEBUGMSGTL(("tlstmParamsTable:row:save", "saving entry '%s'\n", buf));

    return SNMP_ERR_NOERROR;
}

static int
_save_params(snmpTlstmParams *params, void *app_type)
{
    char buf[SNMP_MAXBUF_SMALL], *hashType;

    if (NULL == params)
        return SNMP_ERR_GENERR;

    hashType = se_find_label_in_slist("cert_hash_alg", params->hashType);
    if (NULL == hashType) {
        snmp_log(LOG_ERR, "skipping entry unknown hash type %d\n",
                 params->hashType);
        return SNMP_ERR_GENERR;
    }
    snprintf(buf, sizeof(buf), "%s %s --%s %s %d", mib_token, params->name,
             hashType, params->fingerprint, RS_ACTIVE);

    DEBUGMSGTL(("tlstmParamsTable:params:save", "saving params '%s'\n",
                buf));
    read_config_store(app_type, buf);

    return SNMP_ERR_NOERROR;
}

static void
_tlstmParamsTable_row_restore_mib(const char *token, char *buf)
{
    u_char                 rowStatus;
    snmpTlstmParams       *params;

    /** need somewhere to save rows */
    netsnmp_assert(_table_data && _table_data->container); 

    params = netsnmp_tlstmParams_restore_common(&buf);
    if (NULL == params)
        return;

    if (NULL == buf) {
        config_perror("incomplete line");
        return;
    }
    rowStatus = atoi(buf);

    /*
     * if row is active, add it to the params container so it is available
     * for use. Do not add it to the table, since it will be added
     * during cache_load.
     */
    if (RS_ACTIVE == rowStatus) {
        params->flags = TLSTM_PARAMS_FROM_MIB | TLSTM_PARAMS_NONVOLATILE;

        netsnmp_tlstmParams_add(params);
    } else {
        netsnmp_tdata_row     *row;
        snmpTlstmParamsTable_entry  *entry;

        row = snmpTlstmParamsTable_createEntry(_table_data, params->name,
                                               strlen(params->name));
        if (!row) {
            netsnmp_tlstmParams_free(params);
            return;
        }

        entry = row->data;
        
        entry->hashType = params->hashType;
        strlcpy(entry->snmpTlstmParamsClientFingerprint, params->fingerprint,
                sizeof(entry->snmpTlstmParamsClientFingerprint));
        entry->snmpTlstmParamsClientFingerprint_len =
            strlen(entry->snmpTlstmParamsClientFingerprint);
        entry->snmpTlstmParamsStorageType = ST_NONVOLATILE;
        entry->snmpTlstmParamsRowStatus = rowStatus;
        netsnmp_tlstmParams_free(params);
    }
}
