/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 *
 * U.S. Government Rights - Commercial software. Government users are subject
 * to the Sun Microsystems, Inc. standard license agreement and applicable
 * provisions of the FAR and its supplements.
 *
 *
 * This distribution may include materials developed by third parties. Sun,
 * Sun Microsystems, the Sun logo and Solaris are trademarks or registered
 * trademarks of Sun Microsystems, Inc. in the U.S. and other countries.
 *
 */
 
/*
 * Note: this file originally auto-generated by mib2c using :
 * mib2c.scalar.conf,v 1.5 2002/07/18 14:18:52 dts12 Exp $
 */

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <time.h>
#include "demo_module_10.h"

// Default scalar data value
static long exampleData = 0;

// Status of data collection
static u_long status = 0;

// Default delay time
static u_long delay_time = 2;

// Default alarm interval
static u_long alarm_time = 5;

// Data collection timestamp
char refreshTime[256];

// System date/time
time_t mytime;


/*
   The following code example demonstrates a module design that handles
   long running data collections so that their values can be polled by
   an SNMP manager. The example also shows how to implement objects that
   normally would block the agent as it waits for external events in such
   a way that the agent can continue responding to other requests while
   this implementation waits.

   This example uses the following features of SMA:

   - Setting the delegated member of the requests structure to 1 to indicate to the
     agent that this request should be delayed.  The agent queues this request
     to be handled later and then is available to handle other requests.  The
     agent is not blocked by this request.

   - Registering an SNMP alarm to update the results at a later time. 

   - Use of a status variable to communicate the status of a data collection to
     the polling SNMP manager.

   - Use of a refreshTime variable to return the date and time that the data collection
     completed.
*/

/*  Initialialization routine that is automatically called by the agent.
    The function name must match init_FILENAME() */

void
init_demo_module_10(void)
{
	/*
	 * the OID at which to register the exampleData variable.
	 */
	static oid longRunScalar_oid[] =
	    {1, 3, 6, 1, 4, 1, 42, 2, 2, 4, 4, 10, 1, 1, 0};

	/*
	 * the OID at which to register the status variable.
	 */
	static oid status_oid[] =
	    {1, 3, 6, 1, 4, 1, 42, 2, 2, 4, 4, 10, 1, 2, 0};
	
	/*
	 * the OID at which to register the refreshTime variable.
	 */
	static oid refreshTime_oid[] =
	    {1, 3, 6, 1, 4, 1, 42, 2, 2, 4, 4, 10, 1, 3, 0};

	

	netsnmp_handler_registration *myreg;

	/*
	 * A debugging statement.  Run the agent with -Ddemo_module_10 to see
	 * the output of this debugging statement in /var/log/snmpd.log, by
	 * default. Use the -L option to write debugging output to the
	 * screen.
	 */
		DEBUGMSGTL(("demo_module_10", "Initializing\n"));
	/*
	 * Creates a read-only registration handler named longRunScalar,
	 * which calls the get_longRunScalar function to service snmp
	 * requests for the longRunScalar_oid object.  The OID_LENGTH
	 * argument calculates the length of the longRunScalar_oid.
	 */
	myreg = netsnmp_create_handler_registration
		       ("longRunScalar",
			get_longRunScalar,
			longRunScalar_oid,
			OID_LENGTH(longRunScalar_oid),
			HANDLER_CAN_RONLY);
	/*
	 * Register the instance and handler.
	 *
	 */
	netsnmp_register_read_only_instance(myreg);

	/*
	 * Creates a read/write registration handler named getStatus,
	 * which calls the get_status function to service snmp
	 * requests for the status_oid object.  The OID_LENGTH
	 * argument calculates the length of the status_oid.
	 */
	
	myreg = netsnmp_create_handler_registration
		       ("delayed_instance_handler",
			delayed_instance_handler,
			status_oid,
			OID_LENGTH(status_oid),
			HANDLER_CAN_RWRITE);
	/*
	 * Register the instance and handler.
	 *
	 */
	netsnmp_register_instance(myreg);


	/*
	 * Creates a read-only registration handler named getTimestamp,
	 * which calls the get_timestamp function to service snmp
	 * get requests for the timestamp_oid object.  The OID_LENGTH
	 * argument calculates the length of the timestamp_oid.
	 */
	
	myreg = netsnmp_create_handler_registration
		       ("getTimestamp",
			get_timestamp,
			refreshTime_oid,
			OID_LENGTH(refreshTime_oid),
			HANDLER_CAN_RONLY);
	/*
	 * Register the instance and handler.
	 *
	 */
	netsnmp_register_instance(myreg);

}

#define DELAYED_INSTANCE_SET_NAME "test_delayed"

int
delayed_instance_handler(netsnmp_mib_handler *handler,
                         netsnmp_handler_registration *reginfo,
                         netsnmp_agent_request_info *reqinfo,
                         netsnmp_request_info *requests)
{
	/*
	This handler is called to handle SNMP GET and SNMP SET
	requests for the my_delayed_oid object. If it is called to
	handle SNMP GET requests, the handler does not need to
	handle a GETNEXT if it is registered as an instance handler.
	Instance handlers only deliver one request at a time, so we
	do not need to loop over a list of requests. */

	DEBUGMSGTL(("demo_module_10", "Handler got request, mode = %d:\n",
                reqinfo->mode));

	switch (reqinfo->mode) {
	/*
	 * here we merely mention that we'll answer this request
         * later.  we don't actually care about the mode type in this
         * example, but for certain cases you may, so I'll leave in the
         * otherwise useless switch and case statements 
         */

	default:
        /*
         * Mark this variable as something that cannot be handled now
	 * by setting the delegated member of the requests structure
	 * to 1. The agent queues the request to be handled at a later
	 * time and continues responding to other client requests.
         *  
         */
        requests->delegated = 1;
	DEBUGMSGTL(("demo_module_10", "Delegated is %d\n",
                requests->delegated));
        /*
         * Register an alarm to update the results at a later
         * time.  Normally, we might have to query something else
         * (like an external request sent to a different network
         * or system socket, etc), but for this example we'll do
         * something really simply and just insert an alarm for a
         * certain period of time. 
         */
        snmp_alarm_register(alarm_time, /* seconds */
                            0,  /* dont repeat. */
	                    get_status,   /* the function */
	                                  /* to call */
                            /*
                             * Create a "cache" of useful
                             * information that can be retrieved
                             * at a later time.  This argument is
                             * passed back to the module in the callback 
                             * function for an alarm. 
                             */
                            (void *)
                            netsnmp_create_delegated_cache(handler,
                                                           reginfo,
                                                           reqinfo,
                                                           requests,
                                                           NULL));
        break;

    }

    return SNMP_ERR_NOERROR;
}


int
get_longRunScalar(netsnmp_mib_handler * handler,
	netsnmp_handler_registration * reginfo,
	netsnmp_agent_request_info * reqinfo,
	netsnmp_request_info * requests)
{
	/*
	 * This handler returns the data from the data collection, if the
	 * collection has finished.  If the collection has not finished.
	 * it returns an SNMP_ERR_NOACCESS error.
	 *
	 * This handler is never called for a getnext if it is registered as
	 * an instance. An instance handler only delivers one request at a
	 * time, so we do not need to loop over a list of requests.
	 */
	switch (reqinfo->mode) {
	case MODE_GET:
		// if collection done, return data; else return snmp error
		if (status == 2)
			snmp_set_var_typed_value(requests->requestvb,
			ASN_INTEGER, (u_char *) & exampleData,
			sizeof (exampleData) /* length in bytes */);
		else
			netsnmp_set_request_error(reqinfo, requests,
                                      SNMP_ERR_NOACCESS);
		break;
	default:
		/*
		 * We should never get here, so this is a really bad error.
		 */
		return (SNMP_ERR_GENERR);
	}
	return (SNMP_ERR_NOERROR);
}

int
get_timestamp(netsnmp_mib_handler * handler,
	netsnmp_handler_registration * reginfo,
	netsnmp_agent_request_info * reqinfo,
	netsnmp_request_info * requests)
{
	/*
	 * This handler returns the date and time of the current data
	 * collection. If the collection has not completed, it returns
	 * an SNMP_ERR_NOACCESS error.
	 *
	 * This handler is never called for a getnext if it is registered as
	 * an instance. An instance handler only delivers one request at a
	 * time, so we do not need to loop over a list of requests.
	 */
	DEBUGMSGTL(("demo_module_10", "get_timestamp CALLED\n"));

	switch (reqinfo->mode) {
	case MODE_GET:
		// A data collection is ready. Return its timestamp.
		if (status == 2)
			snmp_set_var_typed_value(requests->requestvb,
			    ASN_OCTET_STR, (u_char *)refreshTime,
			    sizeof (refreshTime));
		else
			// no data collection, so no timestamp available.
			netsnmp_set_request_error(reqinfo, requests,
                                      SNMP_ERR_NOACCESS);
		break;
	default:
		/*
		 * We should never get here, so this is a really bad error.
		 */
		return (SNMP_ERR_GENERR);
	}
	return (SNMP_ERR_NOERROR);
}

void
get_status(unsigned int clientreg, void *clientarg)
{
  /*
   * This function returns the value of the status variable.
   * If the value of status is 0 (no collection is running), it
   * calles collect_data to start one.
   *  
   */
	
  /*
   * Extract the cache from the passed argument.
   */
    netsnmp_delegated_cache *cache = (netsnmp_delegated_cache *) clientarg;
    netsnmp_request_info *requests;
    netsnmp_agent_request_info *reqinfo;
    u_long         *delay_time_cache = NULL;

    /*
     * Make sure the cache created earlier is still
     * valid.  If not, the request timed out for some reason and we
     * do not need to keep processing things.  Should never happen, but
     * this double checks. 
     */
    cache = netsnmp_handler_check_cache(cache);

    if (!cache) {
        snmp_log(LOG_ERR, "illegal call to return delayed response\n");
        return;
    }

    /*
     * Re-establish the previous pointers,
     */
    reqinfo = cache->reqinfo;
    requests = cache->requests;

    DEBUGMSGTL(("demo_module_10",
                "continuing delayed request, mode = %d\n",
                cache->reqinfo->mode));
    
    /*
     * Set delegated to zero to indicate that the request is no longer
     * delegated and answer the query.
     */
    requests->delegated = 0;

    switch (cache->reqinfo->mode) {
        /*
         * Registering as an instance means we do not need to deal with
         * GETNEXT processing, so we do not handle it here at all.
         * 
         * However, since the instance handler already reset the mode
         * back to GETNEXT from the GET mode, we need to do the
         * same thing in both cases.
	 *
         */
	case MODE_GET:
            // no collection running, start one.
	    if (status == 0)
		    collect_data();
	    snmp_set_var_typed_value(requests->requestvb,
	        ASN_INTEGER, (u_char *) & status,
		    sizeof (status) /* length in bytes */);
		break;
	case MODE_SET_RESERVE1:
	    /*
	     * check type 
	     */
	    if (requests->requestvb->type != ASN_INTEGER) {
		    /*
		     * If not an integer, return SNMP error. 
		     */
		    netsnmp_set_request_error(reqinfo, requests,
                                      SNMP_ERR_WRONGTYPE);
		    /*
		     * Free cache. It is no longer needed. 
		     */
		    netsnmp_free_delegated_cache(cache);
		    return;
	    }
	    break;
   	case MODE_SET_RESERVE2:
		    /*
		     * Store old value for UNDO support in the future. 
		     */
		    memdup((u_char **) & delay_time_cache,
			(u_char *) & delay_time, sizeof(delay_time));

		    /*
		     * malloc failed 
		     */
		    if (delay_time_cache == NULL) {
			    netsnmp_set_request_error(reqinfo, requests,
                                      SNMP_ERR_RESOURCEUNAVAILABLE);
			    netsnmp_free_delegated_cache(cache);
			    return;
		    }

		    /*
		     * Add our temporary information to the request itself.
		     * This is then retrivable later.  The free function
		     * passed auto-frees it when the request is later
		     * deleted.  
		     */
		    netsnmp_request_add_list_data(requests,
                                      netsnmp_create_data_list
                                      (DELAYED_INSTANCE_SET_NAME,
                                       delay_time_cache, free));
		    break;
	case MODE_SET_ACTION:
		// get status integer from request
		// if status == 0, start data collection, else return error
		if (*(requests->requestvb->val.integer) == 0) {
			status = *(requests->requestvb->val.integer);
		        collect_data();
		} else
			netsnmp_set_request_error(reqinfo, requests,
			    SNMP_ERR_WRONGTYPE);
		break;
	case MODE_SET_UNDO:
	    /*
	     * A failure occurred. Reset to the
	     * previously value by extracting the previosuly
	     * stored information from the request.
	     */
	    delay_time =
		*((u_long *) netsnmp_request_get_list_data(requests,
                                                       DELAYED_INSTANCE_SET_NAME));
	    break;
	case MODE_SET_COMMIT:
		break;
	case MODE_SET_FREE:
	    /*
	     * The only thing to do here is free the old memdup'ed
	     * value, but it's auto-freed by the datalist recovery, so
	     * we don't have anything to actually do here 
	     */
	    break;
    }

    /*
     * free the information cache 
     */
    netsnmp_free_delegated_cache(cache);
		
}

void collect_data()
{
    /*
     * This function starts a data collection.
     */
		status = 1; // set collection pending flag
		sleep(delay_time); // simulate long-running collection
		// get timestamp for this data collection
		mytime = time(NULL);
		strftime (refreshTime, sizeof(refreshTime), "%c", localtime(&mytime));
		exampleData = 325;
		status = 2; // set collection complete flag
		return;
		
}
