/*
 * baby_steps.c
 * $Id$
 *
 * 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>

netsnmp_feature_provide(baby_steps);
netsnmp_feature_child_of(baby_steps, mib_helpers);

#ifdef NETSNMP_FEATURE_REQUIRE_BABY_STEPS
netsnmp_feature_require(check_requests_error);
#endif

#ifndef NETSNMP_FEATURE_REMOVE_BABY_STEPS

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

#define BABY_STEPS_PER_MODE_MAX     4
#define BSTEP_USE_ORIGINAL          0xffff

static u_short get_mode_map[BABY_STEPS_PER_MODE_MAX] = {
    MODE_BSTEP_PRE_REQUEST, MODE_BSTEP_OBJECT_LOOKUP, BSTEP_USE_ORIGINAL, MODE_BSTEP_POST_REQUEST };

#ifndef NETSNMP_NO_WRITE_SUPPORT
static u_short set_mode_map[SNMP_MSG_INTERNAL_SET_MAX][BABY_STEPS_PER_MODE_MAX] = {
    /*R1*/
    { MODE_BSTEP_PRE_REQUEST, MODE_BSTEP_OBJECT_LOOKUP, MODE_BSTEP_ROW_CREATE,
      MODE_BSTEP_CHECK_VALUE },
    /*R2*/
    { MODE_BSTEP_UNDO_SETUP, BABY_STEP_NONE, BABY_STEP_NONE, BABY_STEP_NONE },
    /*A */
    { MODE_BSTEP_SET_VALUE,MODE_BSTEP_CHECK_CONSISTENCY,
      MODE_BSTEP_COMMIT, BABY_STEP_NONE },
    /*C */
    { MODE_BSTEP_IRREVERSIBLE_COMMIT, MODE_BSTEP_UNDO_CLEANUP, MODE_BSTEP_POST_REQUEST,
      BABY_STEP_NONE},
    /*F */
    { MODE_BSTEP_UNDO_CLEANUP, MODE_BSTEP_POST_REQUEST, BABY_STEP_NONE,
      BABY_STEP_NONE },
    /*U */
    { MODE_BSTEP_UNDO_COMMIT, MODE_BSTEP_UNDO_SET, MODE_BSTEP_UNDO_CLEANUP,
      MODE_BSTEP_POST_REQUEST}
};
#endif /* NETSNMP_NO_WRITE_SUPPORT */

static int
_baby_steps_helper(netsnmp_mib_handler *handler,
                   netsnmp_handler_registration *reginfo,
                   netsnmp_agent_request_info *reqinfo,
                   netsnmp_request_info *requests);
static int
_baby_steps_access_multiplexer(netsnmp_mib_handler *handler,
                               netsnmp_handler_registration *reginfo,
                               netsnmp_agent_request_info *reqinfo,
                               netsnmp_request_info *requests);
    
/** @defgroup baby_steps baby_steps
 *  Calls your handler in baby_steps for set processing.
 *  @ingroup handler
 *  @{
 */

static void *
netsnmp_baby_steps_modes_ref(void *p)
{
    netsnmp_baby_steps_modes *md = p;

    md->refcnt++;
    return md;
}

static void
netsnmp_baby_steps_modes_deref(void *p)
{
    netsnmp_baby_steps_modes *md = p;

    if (--md->refcnt == 0)
	free(md);
}

/** returns a baby_steps handler that can be injected into a given
 *  handler chain.
 */
netsnmp_mib_handler *
netsnmp_baby_steps_handler_get(u_long modes)
{
    netsnmp_mib_handler *mh;
    netsnmp_baby_steps_modes *md;

    mh = netsnmp_create_handler("baby_steps", _baby_steps_helper);
    if(!mh)
        return NULL;

    md = SNMP_MALLOC_TYPEDEF(netsnmp_baby_steps_modes);
    if (NULL == md) {
        snmp_log(LOG_ERR,"malloc failed in netsnmp_baby_steps_handler_get\n");
        netsnmp_handler_free(mh);
        mh = NULL;
    }
    else {
	md->refcnt = 1;
        mh->myvoid = md;
	mh->data_clone = netsnmp_baby_steps_modes_ref;
	mh->data_free = netsnmp_baby_steps_modes_deref;
        if (0 == modes)
            modes = BABY_STEP_ALL;
        md->registered = modes;
    }

    /*
     * don't set MIB_HANDLER_AUTO_NEXT, since we need to call lower
     * handlers with a munged mode.
     */
    
    return mh;
}

/** @internal Implements the baby_steps handler */
static int
_baby_steps_helper(netsnmp_mib_handler *handler,
                         netsnmp_handler_registration *reginfo,
                         netsnmp_agent_request_info *reqinfo,
                         netsnmp_request_info *requests)
{
    netsnmp_baby_steps_modes *bs_modes;
    int save_mode, i, rc = SNMP_ERR_NOERROR;
    u_short *mode_map_ptr;
    
    DEBUGMSGTL(("baby_steps", "Got request, mode %s\n",
                se_find_label_in_slist("agent_mode",reqinfo->mode)));

    bs_modes = (netsnmp_baby_steps_modes*)handler->myvoid;
    netsnmp_assert(NULL != bs_modes);

    switch (reqinfo->mode) {

#ifndef NETSNMP_NO_WRITE_SUPPORT
    case MODE_SET_RESERVE1:
        /*
         * clear completed modes
         * xxx-rks: this will break for pdus with set requests to different
         * rows in the same table when the handler is set up to use the row
         * merge helper as well (or if requests are serialized).
         */
        bs_modes->completed = 0;
        /* FALL THROUGH */
    case MODE_SET_RESERVE2:
    case MODE_SET_ACTION:
    case MODE_SET_COMMIT:
    case MODE_SET_FREE:
    case MODE_SET_UNDO:
        mode_map_ptr = set_mode_map[reqinfo->mode];
        break;
#endif /* NETSNMP_NO_WRITE_SUPPORT */
            
    default:
        /*
         * clear completed modes
         */
        bs_modes->completed = 0;

        mode_map_ptr = get_mode_map;
    }

    /*
     * NOTE: if you update this chart, please update the versions in
     *       local/mib2c-conf.d/parent-set.m2i
     *       agent/mibgroup/helpers/baby_steps.c
     * while you're at it.
     */
    /*
     ***********************************************************************
     * Baby Steps Flow Chart (2004.06.05)                                  *
     *                                                                     *
     * +--------------+    +================+    U = unconditional path    *
     * |optional state|    ||required state||    S = path for success      *
     * +--------------+    +================+    E = path for error        *
     ***********************************************************************
     *
     *                        +--------------+
     *                        |     pre      |
     *                        |   request    |
     *                        +--------------+
     *                               | U
     * +-------------+        +==============+
     * |    row    |f|<-------||  object    ||
     * |  create   |1|      E ||  lookup    ||
     * +-------------+        +==============+
     *     E |   | S                 | S
     *       |   +------------------>|
     *       |                +==============+
     *       |              E ||   check    ||
     *       |<---------------||   values   ||
     *       |                +==============+
     *       |                       | S
     *       |                +==============+
     *       |       +<-------||   undo     ||
     *       |       |      E ||   setup    ||
     *       |       |        +==============+
     *       |       |               | S
     *       |       |        +==============+
     *       |       |        ||    set     ||-------------------------->+
     *       |       |        ||   value    || E                         |
     *       |       |        +==============+                           |
     *       |       |               | S                                 |
     *       |       |        +--------------+                           |
     *       |       |        |    check     |-------------------------->|
     *       |       |        |  consistency | E                         |
     *       |       |        +--------------+                           |
     *       |       |               | S                                 |
     *       |       |        +==============+         +==============+  |
     *       |       |        ||   commit   ||-------->||     undo   ||  |
     *       |       |        ||            || E       ||    commit  ||  |
     *       |       |        +==============+         +==============+  |
     *       |       |               | S                     U |<--------+
     *       |       |        +--------------+         +==============+
     *       |       |        | irreversible |         ||    undo    ||
     *       |       |        |    commit    |         ||     set    ||
     *       |       |        +--------------+         +==============+
     *       |       |               | U                     U |
     *       |       +-------------->|<------------------------+
     *       |                +==============+
     *       |                ||   undo     ||
     *       |                ||  cleanup   ||
     *       |                +==============+
     *       +---------------------->| U
     *                               |
     *                          (err && f1)------------------->+
     *                               |                         |
     *                        +--------------+         +--------------+
     *                        |    post      |<--------|      row     |
     *                        |   request    |       U |    release   |
     *                        +--------------+         +--------------+
     *
     */
    /*
     * save original mode
     */
    save_mode = reqinfo->mode;
    for(i = 0; i < BABY_STEPS_PER_MODE_MAX; ++i ) {
        /*
         * break if we run out of baby steps for this mode
         */
        if(mode_map_ptr[i] == BABY_STEP_NONE)
            break;

        DEBUGMSGTL(("baby_steps", " baby step mode %s\n",
                    se_find_label_in_slist("babystep_mode",mode_map_ptr[i])));

        /*
         * skip modes the handler didn't register for
         */
        if (BSTEP_USE_ORIGINAL != mode_map_ptr[i]) {
            u_int    mode_flag;

#ifndef NETSNMP_NO_WRITE_SUPPORT
            /*
             * skip undo commit if commit wasn't hit, and
             * undo_cleanup if undo_setup wasn't hit.
             */
            if((MODE_SET_UNDO == save_mode) &&
               (MODE_BSTEP_UNDO_COMMIT == mode_map_ptr[i]) &&
               !(BABY_STEP_COMMIT & bs_modes->completed)) {
                DEBUGMSGTL(("baby_steps",
                            "   skipping commit undo (no commit)\n"));
                continue;
            }
            else if((MODE_SET_FREE == save_mode) &&
               (MODE_BSTEP_UNDO_CLEANUP == mode_map_ptr[i]) &&
               !(BABY_STEP_UNDO_SETUP & bs_modes->completed)) {
                DEBUGMSGTL(("baby_steps",
                            "   skipping undo cleanup (no undo setup)\n"));
                continue;
            }
#endif /* NETSNMP_NO_WRITE_SUPPORT */

            reqinfo->mode = mode_map_ptr[i];
            mode_flag = netsnmp_baby_step_mode2flag( mode_map_ptr[i] );
            if((mode_flag & bs_modes->registered))
                bs_modes->completed |= mode_flag;
            else {
                DEBUGMSGTL(("baby_steps",
                            "   skipping mode (not registered)\n"));
                continue;
            }

        
        }
        else {
            reqinfo->mode = save_mode;
        }

#ifdef BABY_STEPS_NEXT_MODE
        /*
         * I can't remember why I wanted the next mode in the request,
         * but it's not used anywhere, so don't use this code. saved,
         * in case I remember why I thought needed it. - rstory 040911
         */
        if((BABY_STEPS_PER_MODE_MAX - 1) == i)
            reqinfo->next_mode_ok = BABY_STEP_NONE;
        else {
            if(BSTEP_USE_ORIGINAL == mode_map_ptr[i+1])
                reqinfo->next_mode_ok = save_mode;
            else
                reqinfo->next_mode_ok = mode_map_ptr[i+1];
        }
#endif

        /*
         * call handlers for baby step
         */
        rc = netsnmp_call_next_handler(handler, reginfo, reqinfo,
                                       requests);

        /*
         * check for error calling handler (unlikely, but...)
         */
        if(rc) {
            DEBUGMSGTL(("baby_steps", "   ERROR:handler error\n"));
            break;
        }

        /*
         * check for errors in any of the requests for GET-like, reserve1,
         * reserve2 and action. (there is no recovery from errors
         * in commit, free or undo.)
         */
        if (MODE_IS_GET(save_mode)
#ifndef NETSNMP_NO_WRITE_SUPPORT
            || (save_mode < SNMP_MSG_INTERNAL_SET_COMMIT)
#endif /* NETSNMP_NO_WRITE_SUPPORT */
            ) {
            rc = netsnmp_check_requests_error(requests);
            if(rc) {
                DEBUGMSGTL(("baby_steps", "   ERROR:request error\n"));
                break;
            }
        }
    }

    /*
     * restore original mode
     */
    reqinfo->mode = save_mode;

    
    return rc;
}

/** initializes the baby_steps helper which then registers a baby_steps
 *  handler as a run-time injectable handler for configuration file
 *  use.
 */
netsnmp_feature_child_of(netsnmp_baby_steps_handler_init,netsnmp_unused);
#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_BABY_STEPS_HANDLER_INIT
void
netsnmp_baby_steps_handler_init(void)
{
    netsnmp_mib_handler *handler =
        netsnmp_baby_steps_handler_get(BABY_STEP_ALL);
    if (NULL == handler) {
        netsnmp_handler_free(handler);
        snmp_log(LOG_ERR, "could not create baby steps handler\n");
        return;
    }

    netsnmp_register_handler_by_name("baby_steps", handler);
}
#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_BABY_STEPS_HANDLER_INIT */

/** @} */

/** @defgroup access_multiplexer baby_steps_access_multiplexer: calls individual access methods based on baby_step mode.
 *  @ingroup baby_steps
 *  @{
 */

/** returns a baby_steps handler that can be injected into a given
 *  handler chain.
 */
netsnmp_mib_handler *
netsnmp_baby_steps_access_multiplexer_get(netsnmp_baby_steps_access_methods *am)
{
    netsnmp_mib_handler *mh;

    mh = netsnmp_create_handler("baby_steps_mux",
                                _baby_steps_access_multiplexer);
    if(!mh)
        return NULL;

    mh->myvoid = am;
    mh->flags |= MIB_HANDLER_AUTO_NEXT;
    
    return mh;
}

/** @internal Implements the baby_steps handler */
static int
_baby_steps_access_multiplexer(netsnmp_mib_handler *handler,
                               netsnmp_handler_registration *reginfo,
                               netsnmp_agent_request_info *reqinfo,
                               netsnmp_request_info *requests)
{
    void *temp_void;
    Netsnmp_Node_Handler *method = NULL;
    netsnmp_baby_steps_access_methods *access_methods;
    int rc = SNMP_ERR_NOERROR;

    /** call handlers should enforce these */
    netsnmp_assert((handler!=NULL) && (reginfo!=NULL) && (reqinfo!=NULL) &&
                   (requests!=NULL));

    DEBUGMSGT(("baby_steps_mux", "mode %s\n",
               se_find_label_in_slist("babystep_mode",reqinfo->mode)));

    access_methods = (netsnmp_baby_steps_access_methods *)handler->myvoid;
    if(!access_methods) {
        snmp_log(LOG_ERR,"baby_steps_access_multiplexer has no methods\n");
        return SNMPERR_GENERR;
    }

    switch(reqinfo->mode) {
        
    case MODE_BSTEP_PRE_REQUEST:
        if( access_methods->pre_request )
            method = access_methods->pre_request;
        break;
        
    case MODE_BSTEP_OBJECT_LOOKUP:
        if( access_methods->object_lookup )
            method = access_methods->object_lookup;
        break;

    case SNMP_MSG_GET:
    case SNMP_MSG_GETNEXT:
        if( access_methods->get_values )
            method = access_methods->get_values;
        break;
        
#ifndef NETSNMP_NO_WRITE_SUPPORT
    case MODE_BSTEP_CHECK_VALUE:
        if( access_methods->object_syntax_checks )
            method = access_methods->object_syntax_checks;
        break;

    case MODE_BSTEP_ROW_CREATE:
        if( access_methods->row_creation )
            method = access_methods->row_creation;
        break;

    case MODE_BSTEP_UNDO_SETUP:
        if( access_methods->undo_setup )
            method = access_methods->undo_setup;
        break;

    case MODE_BSTEP_SET_VALUE:
        if( access_methods->set_values )
            method = access_methods->set_values;
        break;

    case MODE_BSTEP_CHECK_CONSISTENCY:
        if( access_methods->consistency_checks )
            method = access_methods->consistency_checks;
        break;

    case MODE_BSTEP_UNDO_SET:
        if( access_methods->undo_sets )
            method = access_methods->undo_sets;
        break;

    case MODE_BSTEP_COMMIT:
        if( access_methods->commit )
            method = access_methods->commit;
        break;

    case MODE_BSTEP_UNDO_COMMIT:
        if( access_methods->undo_commit )
            method = access_methods->undo_commit;
        break;

    case MODE_BSTEP_IRREVERSIBLE_COMMIT:
        if( access_methods->irreversible_commit )
            method = access_methods->irreversible_commit;
        break;

    case MODE_BSTEP_UNDO_CLEANUP:
        if( access_methods->undo_cleanup )
            method = access_methods->undo_cleanup;
        break;
#endif /* NETSNMP_NO_WRITE_SUPPORT */
        
    case MODE_BSTEP_POST_REQUEST:
        if( access_methods->post_request )
            method = access_methods->post_request;
        break;

    default:
        snmp_log(LOG_ERR,"unknown mode %d\n", reqinfo->mode);
        return SNMP_ERR_GENERR;
    }

    /*
     * if method exists, set up handler void and call method.
     */
    if(NULL != method) {
        temp_void = handler->myvoid;
        handler->myvoid = access_methods->my_access_void;
        rc = (*method)(handler, reginfo, reqinfo, requests);
        handler->myvoid = temp_void;
    }
    else {
        rc = SNMP_ERR_GENERR;
        snmp_log(LOG_ERR,"baby steps multiplexer handler called for a mode "
                 "with no handler\n");
        netsnmp_assert(NULL != method);
    }

    /*
     * don't call any lower handlers, it will be done for us 
     * since we set MIB_HANDLER_AUTO_NEXT
     */

    return rc;
}

/*
 * give a baby step mode, return the flag for that mode
 */
int
netsnmp_baby_step_mode2flag( u_int mode )
{
    switch( mode ) {
        case MODE_BSTEP_OBJECT_LOOKUP:
            return BABY_STEP_OBJECT_LOOKUP;
#ifndef NETSNMP_NO_WRITE_SUPPORT
        case MODE_BSTEP_SET_VALUE:
            return BABY_STEP_SET_VALUE;
        case MODE_BSTEP_IRREVERSIBLE_COMMIT:
            return BABY_STEP_IRREVERSIBLE_COMMIT;
        case MODE_BSTEP_CHECK_VALUE:
            return BABY_STEP_CHECK_VALUE;
        case MODE_BSTEP_PRE_REQUEST:
            return BABY_STEP_PRE_REQUEST;
        case MODE_BSTEP_POST_REQUEST:
            return BABY_STEP_POST_REQUEST;
        case MODE_BSTEP_UNDO_SETUP:
            return BABY_STEP_UNDO_SETUP;
        case MODE_BSTEP_UNDO_CLEANUP:
            return BABY_STEP_UNDO_CLEANUP;
        case MODE_BSTEP_UNDO_SET:
            return BABY_STEP_UNDO_SET;
        case MODE_BSTEP_ROW_CREATE:
            return BABY_STEP_ROW_CREATE;
        case MODE_BSTEP_CHECK_CONSISTENCY:
            return BABY_STEP_CHECK_CONSISTENCY;
        case MODE_BSTEP_COMMIT:
            return BABY_STEP_COMMIT;
        case MODE_BSTEP_UNDO_COMMIT:
            return BABY_STEP_UNDO_COMMIT;
#endif /* NETSNMP_NO_WRITE_SUPPORT */
        default:
            netsnmp_assert("unknown flag");
            break;
    }
    return 0;
}
/**  @} */

#else  /* NETSNMP_FEATURE_REMOVE_BABY_STEPS */
netsnmp_feature_unused(baby_steps);
#endif /* NETSNMP_FEATURE_REMOVE_BABY_STEPS */

