/*      -*- linux-c -*-
 *
 * (C) Copyright IBM Corp. 2003, 2004
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  This
 * file and program are licensed under a BSD style license.  See
 * the Copying file included with the OpenHPI distribution for
 * full licensing terms.
 *
 * Authors:
 *      Renier Morales <renier@openhpi.org>
 *	David Judkovics <djudkovi@us.ibm.com>
 *
 */

#include <glib.h>
#include <string.h>

#include <oh_error.h>
#include <snmp_utils.h>

/**
 * snmp_get
 * @ss: a handle to the snmp session needed to make an
 * snmp transaction.
 * @objid: string containing the OID entry.
 * @value: the value received from snmp will be put in this union.
 *
 * Gets a single value indicated by the objectid using snmp.
 * In the case of multiple values being returned, the type in @value will be
 * ASN_NULL (0x05). Nothing else in @value will be filled in.
 *
 * Returns: 0 if successful, <0 if there was an error.
 **/
SaErrorT snmp_get( void *sessp,
		   const char *objid, 
                   struct snmp_value *value) 
{
        struct snmp_pdu *pdu;
        struct snmp_pdu *response;
	struct snmp_session *session;
	        
        oid anOID[MAX_OID_LEN];
        size_t anOID_len = MAX_OID_LEN;
        struct variable_list *vars;
	SaErrorT returncode = SA_OK;
        int status;
        
        /*
         * Create the PDU for the data for our request.
         */
        pdu = snmp_pdu_create(SNMP_MSG_GET);
        read_objid(objid, anOID, &anOID_len);
        snmp_add_null_var(pdu, anOID, anOID_len);

        /*
         * Send the Request out.
         */
        status = snmp_sess_synch_response(sessp, pdu, &response);

        /*
         * Process the response.
         */
        if (status == STAT_SUCCESS) {
		if(response->errstat == SNMP_ERR_NOERROR) {
               	 	vars = response->variables;
                	value->type = vars->type;
                	if (vars->next_variable != NULL) {
                		/* There are more values, set return type to null. */
                        	value->type = ASN_NULL;
			} else if ( !(CHECK_END(vars->type)) ) {
				/* This is one of the exception condition */
				returncode = SA_ERR_HPI_NOT_PRESENT;
				DBG("Warning: OID=%s gets snmp exception %d \n",objid, vars->type);

                    	} else if ( (vars->type == ASN_INTEGER) || 
				    (vars->type == ASN_COUNTER) || 
			    	    (vars->type == ASN_UNSIGNED) ) {
                        	value->integer = *(vars->val.integer);

                	} else { 
                        	value->str_len = vars->val_len;
                        	if (value->str_len >= MAX_ASN_STR_LEN)
                                		value->str_len = MAX_ASN_STR_LEN;
				if (value->str_len > 0)
                        	    memcpy(value->string, vars->val.string, value->str_len);						
						
                        	value->string[value->str_len] = '\0'; /* guarantee NULL terminated string */
				
                	}

		} else {
                        DBG("Error in packet %s\nReason: %s\n",
                            objid, snmp_errstring(response->errstat));
			returncode = errstat2hpi(response->errstat);
		}

        } else {
                value->type = (u_char)0x00; 
		session = snmp_sess_session(sessp);
                snmp_sess_perror("snmpget", session);
                DBG("OID %s, error status: %d\n",objid, status);
		returncode = snmpstat2hpi(status);
        }

        /* Clean up: free the response */
        if (response) snmp_free_pdu(response);

        return (returncode);
}

/**
 * snmp_set
 * @ss: a handle to the snmp session needed to make an snmp transaction.
 * @objid: string containing the OID to set.
 * @value: the value to set the oid with.
 *
 * Sets a value where indicated by the objectid
 * using snmp.
 *
 * Returns: 0 if Success, less than 0 if Failure.
 **/
SaErrorT snmp_set(
        void *sessp,
        char *objid,
        struct snmp_value value) 
{
        struct snmp_pdu *pdu;
        struct snmp_pdu *response;
	struct snmp_session *session;

        oid anOID[MAX_OID_LEN];
        size_t anOID_len = MAX_OID_LEN;
        void *dataptr = NULL;
        int status = 0;
	SaErrorT rtncode = 0;

        /*
         * Create the PDU for the data for our request.
         */
        pdu = snmp_pdu_create(SNMP_MSG_SET);
        read_objid(objid, anOID, &anOID_len);

        rtncode = 0; /* Default - All is OK */

        switch (value.type)
        {
                case ASN_INTEGER:
		case ASN_UNSIGNED:
                case ASN_COUNTER:
			dataptr = &value.integer;
                        break;
                case ASN_OCTET_STR:
			dataptr = value.string;
                        break;
                default:
                        rtncode = SA_ERR_HPI_INVALID_PARAMS;
                        CRIT("datatype %c not yet supported by snmp_set()\n", value.type);
                        break;
        }

        if (rtncode == 0) {

		/*
		 * Set the data to send out
		 */
                /* Old code - int rc = snmp_add_var(pdu, objid, objid_len, datatype, dataptr)      */
		/*            was missing checking for rc, so there was no OID and no data was     */
		/*            included in the package.                                             */
		/*            Since snmp_add_var() converts input data to string then call         */
		/*            snmp_pdu_add_variable(), we stick with add_variable() for efficiency */
		snmp_pdu_add_variable(pdu, anOID, anOID_len, value.type, dataptr, value.str_len);

        	/*
         	* Send the Request out.
         	*/
        	status = snmp_sess_synch_response(sessp, pdu, &response);

        	/*
         	* Process the response.
         	*/
                if (status == STAT_SUCCESS) 
                                rtncode = errstat2hpi(response->errstat);
                else {
			session = snmp_sess_session(sessp);
			snmp_sess_perror("snmpset", session);
			rtncode = snmpstat2hpi(status);;
		}
		
        	/* Clean up: free the response */
        	if (response) snmp_free_pdu(response);
	}

        return rtncode;
}

/**
 * snmp_get2: Gets a single value indicated by the objectid
 * using snmp.
 * @handle: a handle to the snmp session needed to make an
 * snmp transaction.
 * @objid: string containing the OID entry.
 * @value: the value received from snmp will be put in this union.
 *
 * In the case of multiple values being returned, the type in 'value' will be
 * ASN_NULL (0x05). Nothing else in 'value' will be filled in.
 * Use snmp_get_all for doing gets that return multiple values.
 *
 * Return value: Returns 0 if successful, <0 if there was an error.
 **/
SaErrorT snmp_get2(void *sessp, 
	           oid *objid,
	           size_t objid_len,
                   struct snmp_value *value) 
{
        struct snmp_pdu *pdu;
        struct snmp_pdu *response;
        struct snmp_session *session;
        struct variable_list *vars;
	SaErrorT returncode = SA_OK;
	int i;
        int status;
        
        /*
         * Create the PDU for the data for our request.
         */
        pdu = snmp_pdu_create(SNMP_MSG_GET);

        snmp_add_null_var(pdu, objid, objid_len);

        /*
         * Send the Request out.
         */
        status = snmp_sess_synch_response(sessp, pdu, &response);

        /*
         * Process the response.
         */
        if (status == STAT_SUCCESS) {
		if (response->errstat == SNMP_ERR_NOERROR) {

	               vars = response->variables;
 	               value->type = vars->type;

	                if (vars->next_variable != NULL) {
				/* If there are more values, set return type to null. */
       	                 	value->type = ASN_NULL;
			} else if ( !(CHECK_END(vars->type)) ) {
				/* This is one of the exception condition */
				returncode = SA_ERR_HPI_NOT_PRESENT;
				DBG("snmp exception %d \n",vars->type);

                    	} else if ( (vars->type == ASN_INTEGER) || 
				    (vars->type == ASN_COUNTER) || 
			    	    (vars->type == ASN_UNSIGNED) ) {

				value->integer = *(vars->val.integer);

                	} else {

                        	value->str_len = vars->val_len;

                        	if (value->str_len >= MAX_ASN_STR_LEN)
                                		value->str_len = MAX_ASN_STR_LEN;

                        	else value->string[value->str_len] = '\0';
                        
                        	memcpy(value->string, vars->val.string, value->str_len);
                       
                	}

			/* display data */
#ifdef DEBUG
			if (CHECK_END(vars->type)) { 
				fprintf(stderr, "*** snmp_get2 ******************************************\n");
				fprint_variable(stderr, 
						vars->name, 
						vars->name_length, 
						vars);
				fprintf(stderr, "********************************************************\n");
			}  else {
				CRIT("snmp_get2(): No idea.Unknown Type: %X", vars->type);
				fprint_variable(stderr, 
						vars->name, 
						vars->name_length, 
						vars);
			}		
#endif
		} else {
                       CRIT("Error, Reason: %s", 
				snmp_errstring(response->errstat));
			fprintf(stderr, "objid: ");
			for(i = 0; i<objid_len; i++ )
				fprintf(stderr, "%d.", (int)objid[i]);
			fprintf(stderr, "\n");
			returncode = errstat2hpi(response->errstat);		}
        } else {
		session = snmp_sess_session(sessp);
		snmp_sess_perror("snmpget", session);
		returncode = snmpstat2hpi(status);

        }

        /* Clean up: free the response */
	sc_free_pdu(&response); 

        return (returncode);
}


/**
 * snmp_set2: Gets a single value indicated by the objectid
 * using snmp.
 * @handle: a handle to the snmp session needed to make an
 * snmp transaction.
 * @objid: string containing the OID entry.
 * @value: the value received from snmp will be put in this union.
 *
 * In the case of multiple values being returned, the type in 'value' will be
 * ASN_NULL (0x05). Nothing else in 'value' will be filled in.
 * Use snmp_get_all for doing gets that return multiple values.
 *
 * Return value: Returns 0 if successful, -1 if there was an error.
 **/
SaErrorT snmp_set2(void *sessp, 
	           oid *objid,
	           size_t objid_len,
                   struct snmp_value *value) 
{
	struct snmp_pdu *pdu;
	struct snmp_pdu *response;
        struct variable_list *vars;
	struct snmp_session *session;
        void *dataptr = NULL;
        int status = 0;
	SaErrorT rtncode = SA_OK; /* Default - All is OK */

        /*
         * Create the PDU for the data for our request.
         */
        pdu = snmp_pdu_create(SNMP_MSG_SET);

        switch (value->type)
        {
                case ASN_INTEGER:
		case ASN_UNSIGNED:
                case ASN_COUNTER:
			dataptr = &value->integer;
                        break;
                case ASN_OCTET_STR:
			dataptr = value->string;
                        break;
                default:
                        rtncode = SA_ERR_HPI_INVALID_PARAMS;
                        CRIT("datatype %c not yet supported by snmp_set2()", 
				value->type);
                        break;
        }

        if (rtncode == SA_OK) {

		/*
		 * Set the data to send out
		 */
                /* Old code - snmp_add_var(pdu, objid, objid_len, datatype, dataptr); */
                //int retcode = snmp_add_var(pdu, objid, objid_len, datatype, dataptr);
		snmp_pdu_add_variable(pdu, objid, objid_len, value->type, dataptr, value->str_len);

        	/*
         	* Send the Request out.
         	*/
        	status = snmp_sess_synch_response(sessp, pdu, &response);
        	/*
         	* Process the response.
         	*/
		if (status == STAT_SUCCESS) {
               	 	vars = response->variables;
			if (response->errstat == SNMP_ERR_NOERROR) {
				/* display data */
#ifdef DEBUG
				fprintf(stderr, "*** snmp_set2 ******************************************\n");
					if (CHECK_END(response->variables->type)) {
						fprint_variable(stderr, 
								response->variables->name,
								response->variables->name_length,
								response->variables);  
					}  else 
						fprintf(stderr, "snmp_set2(): No idea.\n");
				fprintf(stderr, "********************************************************\n");
#endif
				if (!(CHECK_END(response->variables->type)) ) {
                                	/* This is one of the exception condition */
				        rtncode = SA_ERR_HPI_NOT_PRESENT;
                                	CRIT("snmp exception %d \n",vars->type);
				}
			} else {
                        	CRIT("snmp_set2: Error in packet, Reason: %s",
						snmp_errstring(response->errstat));
                                rtncode = errstat2hpi(response->errstat);

			}                
        	} else {
			session = snmp_sess_session(sessp);
                       	snmp_sess_perror("snmpset", session);
                       	rtncode = snmpstat2hpi(status);

        	}

        	/* Clean up: free the response */
        	if (response) snmp_free_pdu(response);
	
	}

        return rtncode;

}


/**
 * snmp_getn_bulk: Builds and sends a SNMP_BET_BULK pdu using snmp 
 * @sessp: snmp session
 * @bulk_objid: string containing the OID entry.
 * @bulk_objid_len: len of OID string 
 * @bulk_pdu: pointer to the PDU to be created
 * @bulk_response: pointer to the response for this PDU request
 * @num_repetitions: max OIDs for this request. 
 *                   Note: this is the max requested. Actual number returned can be less
 *                   than maximum.  That depends on the target snmp agent. 
 *         
 * Return value: status from snmp_sess_synch_response(). See snmp_client.h of net-snmp library.
 *	STAT_SUCCESS
 *	STAT_ERROR
 *	STAT_TIMEOUT
 *
 *      Additionally, if status == STAT_SUCCESS, consumer of this routine needs to check for exception
 *      conditions.
 *      Possible exception conditions are SNMP_ENDOFMIBVIEW, SNMP_NOSUCHOBJECT, SNMP_NOSUCHINSTANCE 
 **/
int snmp_getn_bulk( void *sessp, 
		    oid *bulk_objid, 
		    size_t bulk_objid_len,
		    struct snmp_pdu *bulk_pdu, 
		    struct snmp_pdu **bulk_response,
		    int num_repetitions )
{
	int status;
	
	bulk_pdu = snmp_pdu_create(SNMP_MSG_GETBULK);
 	
	bulk_pdu->non_repeaters = 0; 
	
	bulk_pdu->max_repetitions = num_repetitions;

	snmp_add_null_var(bulk_pdu, bulk_objid, bulk_objid_len);
	
	/* Send the Request out.*/
	status = snmp_sess_synch_response(sessp, bulk_pdu, bulk_response);

	/*
	 * Return the status.  Consumer of this util has to process the response.
	 */
	return(status);

}

void sc_free_pdu(struct snmp_pdu **p) 
{
	if (*p) {
		snmp_free_pdu(*p);
		*p = NULL;
	}
}

SaErrorT errstat2hpi(long pdu_errstat) 
{
	SaErrorT hpicode = SA_OK;
	
	switch(pdu_errstat) {
		case SNMP_ERR_NOERROR:
			hpicode = SA_OK;
			break;
		case SNMP_ERR_TOOBIG:
		case SNMP_ERR_BADVALUE:
		case SNMP_ERR_WRONGTYPE:
		case SNMP_ERR_WRONGLENGTH:
		case SNMP_ERR_WRONGENCODING:
		case SNMP_ERR_WRONGVALUE:
		case SNMP_ERR_COMMITFAILED:
		case SNMP_ERR_UNDOFAILED:
		case SNMP_ERR_INCONSISTENTVALUE:
			hpicode = SA_ERR_HPI_INVALID_DATA;
			break;
		case SNMP_ERR_READONLY:
		case SNMP_ERR_NOTWRITABLE:
			hpicode = SA_ERR_HPI_READ_ONLY;
			break;		
		case SNMP_ERR_NOSUCHNAME:
			hpicode = SA_ERR_HPI_NOT_PRESENT;
			break;
		case SNMP_ERR_NOACCESS:
		case SNMP_ERR_AUTHORIZATIONERROR:
		case SNMP_ERR_INCONSISTENTNAME:
		case SNMP_ERR_NOCREATION:
			hpicode = SA_ERR_HPI_INVALID_PARAMS;
			break;
		case SNMP_ERR_RESOURCEUNAVAILABLE:
			hpicode = SA_ERR_HPI_OUT_OF_SPACE;
			break;
		case SNMP_ERR_GENERR:
		default:
			hpicode = SA_ERR_HPI_UNKNOWN;
			break;		
	}
		
	return(hpicode);
}

SaErrorT snmpstat2hpi(int snmpstat)
{
	SaErrorT hpicode = SA_OK;
	
	switch(snmpstat)
	{
		case STAT_SUCCESS:
			hpicode = SA_OK;
			break;
		case STAT_TIMEOUT:
			hpicode = SA_ERR_HPI_TIMEOUT;
			break;
		case STAT_ERROR:
		default:
			hpicode = SA_ERR_HPI_ERROR;
			break;
	}
	
	return(hpicode);

}

