/***********************************************************
	Copyright 1992 by Carnegie Mellon University

			  All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the name of CMU not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.  

CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
******************************************************************/

/* alarm.c: implement the alarm group of the RMON MIB */

#include "../defs.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <netinet/in.h>
#include <sys/socket.h>
#include <asn1.h>
#include <snmp_impl.h>
#include <snmp_api.h>
#include <snmp_client.h>
#include <string.h>
#include "../defaults.h"
#include "party.h"
#include "context.h"
#include "snmp_vars.h"
#include "snmp.h"
#include "m2m.h"
#include "snmp_vars_m2m.h"
#include "event.h"
#include "alarm.h"
#include "system.h"
#ifndef HAVE_RANDOM
#define random rand
#endif
#include "../defs.h"

static struct alarmEntry *alarmTab = (struct alarmEntry *)NULL;
static int32 alarmNextIndex = 1;

/* retrieve the given variable from the MIB.  Returns 0 on success,
** 1 if the request was asynchronously transmitted to another host,
** and another value on errors.
*/
static int
rmonGetValue(srcParty, srcPartyLen, dstParty, dstPartyLen,
	     context, contextLen, variable, variableLen, value, alarm)
    oid *srcParty, *dstParty, *context;
    int srcPartyLen, dstPartyLen, contextLen;
    oid *variable;
    int variableLen;                /* number of subids in variable */
    int32 *value;
    struct alarmEntry *alarm;
{
    oid bigVar[MAX_OID_LEN];
    int bigVarLen;
    u_char type;
    int len;
    u_short acl;
    int (*writeFunc)();
    u_char *var;
    struct packet_info pinfo, *pi = &pinfo;
    int noSuchObject;
    struct partyEntry *srcp, *dstp;
    struct contextEntry *cxp;
    struct snmp_session session;
    struct snmp_pdu *pdu;
    struct variable_list *varList;
    u_int32 addr;
    struct get_req_state *state;
    extern int snmp_input();
    
    /* whether it's local or non-local, I have to know about the
       parties and context */
    if (((srcp = party_getEntry(srcParty, srcPartyLen)) == NULL)
	|| ((dstp = party_getEntry(dstParty, dstPartyLen)) == NULL)
	|| ((cxp = context_getEntry(context, contextLen)) == NULL))
	return 2;
    
    addr = get_myaddr();
    if (bcmp(dstp->partyTAddress, &addr, 4)) {
	/* this is a different IP address, so it must be non-local */
	if (!alarm->ss) {
	    state = (struct get_req_state *)malloc(sizeof(struct get_req_state));
	    state->type = ALARM_GET_REQ;
	    state->info = (void *)alarm;
	    alarm->magic = state;
	    bzero((char *)&session, sizeof(struct snmp_session));
	    session.peername = SNMP_DEFAULT_PEERNAME;
	    session.version = SNMP_VERSION_2;
	    session.srcParty = srcParty;
	    session.srcPartyLen = srcPartyLen;
	    session.dstParty = dstParty;
	    session.dstPartyLen = dstPartyLen;
	    session.context = context;
	    session.contextLen = contextLen;
	    session.retries = 4;
	    session.timeout = 500000L;	/* one half second */
	    session.callback = snmp_input;
	    session.callback_magic = (void *)state;
	    alarm->ss = snmp_open(&session);
	    if (!alarm->ss) {
		ERROR("");
		return 3;
	    }
	}
	
	pdu = snmp_pdu_create(GET_REQ_MSG);
	bcopy(dstp->partyTAddress, (char *)&pdu->address.sin_addr.s_addr, 4);
	bcopy(dstp->partyTAddress + 4, &pdu->address.sin_port, 2);
	pdu->address.sin_family = AF_INET;
	varList = (struct variable_list *)malloc(sizeof(struct variable_list));
	
	varList->name = (oid *)malloc(variableLen * sizeof(oid));
	bcopy(variable, varList->name, variableLen * sizeof(oid));
	varList->name_length = variableLen;
	varList->type = ASN_NULL;
	varList->val_len = 0;
	varList->val.integer = (int32 *)NULL;
	varList->next_variable = (struct variable_list *)NULL;
	
	pdu->variables = varList;
	alarm->reqid = snmp_send(alarm->ss, pdu);
	return 1;	/* this means the request has been sent */
    }
    
    if (!has_access(GET_REQ_MSG, srcp->partyIndex, dstp->partyIndex,
		    cxp->contextIndex))
	return 5;
    if (!has_access(GET_RSP_MSG, dstp->partyIndex, srcp->partyIndex,
		    cxp->contextIndex))
	return 4;
    
    bcopy(srcParty, pi->srcParty, srcPartyLen * sizeof(oid));
    pi->srcPartyLength = srcPartyLen;
    bcopy(dstParty, pi->dstParty, dstPartyLen * sizeof(oid));
    pi->dstPartyLength = dstPartyLen;
    bcopy(context, pi->context, contextLen * sizeof(oid));
    pi->contextLength = contextLen;
    pi->srcp = srcp;
    pi->dstp = dstp;
    pi->cxp = cxp;
    
    pi->version = SNMP_VERSION_2;
    pi->pdutype = GET_REQ_MSG;
    /* rest of pi is not needed */
    
    bcopy((char *)variable, (char *)bigVar, variableLen * sizeof(oid));
    bigVarLen = variableLen;
    
    var = getStatPtr(bigVar, &bigVarLen, &type, &len, &acl, 1, &writeFunc, pi,
		     &noSuchObject);
    if (!var) {
	return 6;
    }
    
    if ((type != INTEGER) && (type != COUNTER) &&
	(type != TIMETICKS) && (type != GAUGE) &&
	(type != COUNTER64)) {
	return 7;
    }
    
    *value = *(int *)var;
    return 0;
}

/* add the time values t1 and t2, and store the sum in result.  This
** routine accounts for tv_usec overflow.
*/
static void
DoTimerAdd(result, t1, t2)
	struct timeval *result, *t1, *t2;
{
	result->tv_usec = t1->tv_usec + t2->tv_usec;
	result->tv_sec = t1->tv_sec + t2->tv_sec;
	while (result->tv_usec > 1000000L) {
		(result->tv_usec) -= 1000000L;
		(result->tv_sec)++;
	}
}

/* insert the given row into the alarm table, ordered by index */
static void
alarmInsertRow(alarm)
    struct alarmEntry *alarm;
{
    struct alarmEntry *current;
    struct alarmEntry *prev;

    for (current = alarmTab, prev = (struct alarmEntry *)NULL; current; current = current->next) {
	if (current->index > alarm->index) {
	    break;
	}
	prev = current;
    }

    /* put the new entry before "current" */
    alarm->next = current;
    if (prev) {
	prev->next = alarm;
    }
    else {
	/* this is first on the list */
	alarmTab = alarm;
    }
}

/* free the shadow space that was allocated to this row */
static void
alarmFreeShadow(alarm)
	struct alarmEntry *alarm;
{
    if (!alarm->shadow) {
	return;
    }

    free((char *)alarm->shadow);
    alarm->shadow = (struct alarmEntry *)NULL;
}

/* delete the given row from the alarm table, and free the memory
** associated with it.
*/
static void
alarmDeleteRow(alarm)
	struct alarmEntry *alarm;
{
    struct alarmEntry *temp;
    struct alarmEntry *prev = (struct alarmEntry *)NULL;
    
    for (temp = alarmTab; temp; temp = temp->next) {
	if (temp == alarm) {
	    /* this is the one to remove */
	    if (prev) {
		prev->next = temp->next;
	    }
	    else {
		/* this is the first on the list */
		alarmTab = temp->next;
	    }
	    break;
	}
	prev = temp;
    }
    
    /* KLF debugging */
    if (!temp) {
	printf("alarmDeleteRow: didn't find row (%d) in alarmTab\n",
	       alarm->index);
    }
    
    if (alarm->ss) {
	snmp_close(alarm->ss);
	free((char *)alarm->magic);
    }
    alarmFreeShadow(alarm);
    /* KLF alarmFreeEntries(alarm); */
    free((char *)alarm);
}

/* create a shadow structure for the given row, and copy the world-visible
** data into the shadow structure.  Returns 1 on success, 0 otherwise.
*/
static int
alarmShadowRow(alarm)
struct alarmEntry *alarm;
{
    int i = 0;
    
    if (alarm->shadow) {
	/* it's already been created */
	return 1;
    }
    
    alarm->shadow = (struct alarmEntry *)malloc(sizeof(struct alarmEntry));
    while ((!alarm->shadow) && (i++ < 5)) {
	eventFreeSpace();
	alarm->shadow = (struct alarmEntry *)malloc(sizeof(struct alarmEntry));
    }
    if (!alarm->shadow) {
	/* no more memory */
	return 0;
    }
    
    bcopy((char *)alarm, (char *)alarm->shadow, sizeof(struct alarmEntry));
    
    return 1;
}

/* return a pointer to the given row in the alarmTab */
static struct alarmEntry *
alarmGetRow(context, contextLen, index)
    oid *context;
    int contextLen;
    int index;
{
	struct alarmEntry *alarm;

	for (alarm = alarmTab; alarm; alarm = alarm->next) {
		if (alarm->index == index
		    && alarm->contextLength == contextLen
		    && !bcmp(alarm->contextID, context,
			     contextLen * sizeof(oid))) {
			return alarm;
		}
	}

	return (struct alarmEntry *)NULL;
}

/* return a pointer to the given row in the alarmTab */
static struct alarmEntry *
alarmGetRowByIndex(index)
    int index;
{
    struct alarmEntry *alarm;

    for (alarm = alarmTab; alarm; alarm = alarm->next) {
	if (alarm->index == index) {
	    return alarm;
	}
    }

    return (struct alarmEntry *)NULL;
}

/* create a new row for the alarm table, with the given index.
** Create a shadow for the row.  Put default values into the shadow.
** Return a pointer to the new row.  This routine does not check that
** the index has not already been used, and does not make the row
** visible to a management station that is doing a walk of the table.
** It makes sure the index is in the valid range.
*/
static struct alarmEntry *
alarmNewRow(context, contextLen, index)
    oid *context;
    int contextLen;
    int index;
{
    struct alarmEntry *alarm;
    int i = 0;
    
    if ((index < 1) || (index > 65535)) {
	return (struct alarmEntry *)NULL;
    }
    
    alarm = (struct alarmEntry *)malloc(sizeof(struct alarmEntry));
    while ((!alarm) && (i++ < 5)) {
	eventFreeSpace();
	alarm = (struct alarmEntry *)malloc(sizeof(struct alarmEntry));
    }
    if (!alarm) {
	/* no more room */
	return (struct alarmEntry *)NULL;
    }
    
    bzero((char *)alarm, sizeof(struct alarmEntry));
    
    alarm->index = index;
    bcopy(context, alarm->contextID, contextLen * sizeof(oid));
    alarm->contextLength = contextLen;
    alarm->status = ENTRY_DESTROY;
    
    alarm->bitmask = ALARMTABINDEXMASK;
    
    alarmInsertRow(alarm);
    
    /* this will copy the index, status, and bitmask into the shadow area */
    if (alarmShadowRow(alarm) == 0) {
	/* weren't able to allocate space for the shadow area, so
	 ** remove the entry from the list.
	 */
	alarmDeleteRow(alarm);
	return (struct alarmEntry *)NULL;
    }
    
    /* add default entries to the shadow copy.  The variables that
     ** aren't defaulted are interval, variable, value, startupAlarm,
     ** risingThresh, fallingThresh, risingEventIndex, + fallingEventIndex,
     */
    alarm->shadow->status = ENTRY_NOTINSERVICE;
    alarm->shadow->sampleType = ALARM_DELTA_VALUE;
    
    alarm->shadow->bitmask |= (ALARMTABSAMPLETYPEMASK | ALARMTABSTATUSMASK);
    
    alarmNextIndex = random() & 0x0000ffff;
    while (alarmGetRowByIndex(alarmNextIndex)) {
	alarmNextIndex = random() & 0x0000ffff;
    }
    
    return alarm;
}

/* copy the data in the given row from the shadow copy into the world-
** visible copy, and get rid of the shadow copy.  If no shadow copy
** exists, just return.  If we are setting the row to invalid, delete it.
*/
static void
alarmCommitRow(alarm)
    struct alarmEntry *alarm;
{
    struct alarmEntry *nextPtr;
    u_int32 destAddr;
    
    if (!alarm->shadow) {
	return;
    }
    
    /* if this entry is being set to invalid, just delete it */
    if (alarm->shadow->status == ENTRY_DESTROY) {
	alarmDeleteRow(alarm);
	return;
    }
    
    /* if the row will no longer be valid, invalidate the value field */
    if (alarm->status == ENTRY_ACTIVE &&
	alarm->shadow->status != ENTRY_ACTIVE) {
	alarm->shadow->bitmask &= (~ALARMTABVALUEMASK
				   & ~ALARMTABREALVALUEMASK);
	alarm->shadow->value = 0;
	}
    
    /* set up the intervalAdd variable */
    if (alarm->shadow->sampleType == ALARM_ABSOLUTE_VALUE) {
	alarm->shadow->intervalAdd.tv_sec = alarm->shadow->interval;
    } else {
	/* this is a delta value */
	alarm->shadow->intervalAdd.tv_sec = alarm->shadow->interval / 2;
	if ((alarm->shadow->interval & 1) == 1) {
	    /* alarm->shadow->interval is odd; therefore, another half second
	     ** must be added to its update time
	     */
	    alarm->shadow->intervalAdd.tv_usec = 500000;
	}
    }
    
    nextPtr = alarm->next;
    bcopy((char *)alarm->shadow, (char *)alarm, sizeof(struct alarmEntry));
    
    if (alarm->next != nextPtr) {
	/* KLF debugging */
	printf("alarmCommitRow(%d): next pointer was different\n",
	       alarm->index);
	alarm->next = nextPtr;
    }
    
    ((u_char *)&destAddr)[0] = (u_char)alarm->contextID[9];
    ((u_char *)&destAddr)[1] = (u_char)alarm->contextID[10];
    ((u_char *)&destAddr)[2] = (u_char)alarm->contextID[11];
    ((u_char *)&destAddr)[3] = (u_char)alarm->contextID[12];

    alarm->srcPartyLength
	= alarm->dstPartyLength = alarm->contextLength= MAX_NAME_LEN;
    ms_party_init(destAddr, alarm->srcPartyID, &(alarm->srcPartyLength),
		  alarm->dstPartyID, &(alarm->dstPartyLength),
                    alarm->contextID, &(alarm->contextLength));
#if 0
    bcopy((char *)alarm->contextID, (char *)alarm->srcPartyID,
	  alarm->contextLength * sizeof(oid));
    bcopy((char *)alarm->contextID, (char *)alarm->dstPartyID,
	  alarm->contextLength * sizeof(oid));
    alarm->srcPartyLength = alarm->contextLength;
    alarm->dstPartyLength = alarm->contextLength;
    alarm->srcPartyID[8] = 3;
    alarm->dstPartyID[8] = 3;
    alarm->srcPartyID[13] = alarm->contextID[13] * 2;
    alarm->dstPartyID[13] = (alarm->contextID[13] * 2) - 1;
#endif

    alarmFreeShadow(alarm);
    
    /* note that alarmTimer() will be called within 1/2 second of this
     ** entry becoming valid, and will do the startup alarm processing
     ** then.  This is close enough to "when this entry is first set
     ** to valid" for me.
     */
}

/* compare the new value against the old one, and send a rising or
** falling alarm if necessary.
*/
static void
alarmProcessValue(alarm, oldValue, newValue)
	struct alarmEntry *alarm;
	int32 oldValue;
	int32 newValue;
{
    if ((alarm->bitmask & ALARMTABVALUEMASK) == 0) {
	/* this is the first sample */
	if ((newValue >= alarm->risingThresh) &&
	    ((alarm->startupAlarm == ALARM_STARTUP_RISING) ||
	     (alarm->startupAlarm == ALARM_STARTUP_RISING_OR_FALLING))) {
	    /* send rising alarm */
	    eventGenerate(alarm->risingEventIndex, EVENT_TYPE_STARTUP_RISING,
			  (void *)alarm);
	}
	else if ((newValue <= alarm->fallingThresh) &&
		 ((alarm->startupAlarm == ALARM_STARTUP_FALLING) ||
		  (alarm->startupAlarm == ALARM_STARTUP_RISING_OR_FALLING))) {
	    /* send falling alarm */
	    eventGenerate(alarm->fallingEventIndex, EVENT_TYPE_STARTUP_FALLING,
			  (void *)alarm);
	}
    }
    else if ((newValue >= alarm->risingThresh) &&
	     (oldValue < alarm->risingThresh) && !alarm->cantSendRising) {
	/* send rising alarm */
	eventGenerate(alarm->risingEventIndex, EVENT_TYPE_RISING,
		      (void *)alarm);
	alarm->cantSendFalling = FALSE;
	alarm->cantSendRising = TRUE;
    }
    else if ((newValue <= alarm->fallingThresh) &&
	     (oldValue > alarm->fallingThresh) && !alarm->cantSendFalling) {
	/* send falling alarm */
	eventGenerate(alarm->fallingEventIndex, EVENT_TYPE_FALLING,
		      (void *)alarm);
	alarm->cantSendRising = FALSE;
	alarm->cantSendFalling = TRUE;
    }
}

/* update a delta counter and send an alarm if necessary */
static void
alarmUpdateDelta(alarm, realValue)
    struct alarmEntry *alarm;
    int32 realValue;
{
    int32 oldValue;

    if ((alarm->bitmask & ALARMTABREALVALUEMASK) == 0) {
	/* lastRealValue hasn't been initialized, so do it */
	alarm->lastRealValue = realValue;
	alarm->bitmask |= ALARMTABREALVALUEMASK;
	alarm->lastDeltaValue = 0;
	return;
    }

    /* this is the normal case */
    oldValue = alarm->value;
    alarm->value = alarm->lastDeltaValue + (realValue - alarm->lastRealValue);

    alarmProcessValue(alarm, oldValue, alarm->value);

    alarm->bitmask |= ALARMTABVALUEMASK;
    alarm->lastDeltaValue = realValue - alarm->lastRealValue;
    alarm->lastRealValue = realValue;
}

/* update an absolute value counter and send an alarm if necessary */
static void
alarmUpdateAbs(alarm, value)
    struct alarmEntry *alarm;
    int32 value;
{
    int32 oldValue;

    oldValue = alarm->value;
    alarm->value = value;

    alarmProcessValue(alarm, oldValue, value);

    alarm->bitmask |= ALARMTABVALUEMASK;
}

/* search the alarm table for entries whose interval has expired.  Record
** the value for the variable and take action if necessary.
*/
Export void
alarmTimer(now)
	struct timeval *now;
{
    struct alarmEntry *alarm;
    struct alarmEntry *next;
    int error;
    int32 value;
    
    for (alarm = alarmTab; alarm; alarm = next) {
	next = alarm->next;
	if (alarm->status == ENTRY_DESTROY) {
	    alarmDeleteRow(alarm);
	    continue;
	}

	if (alarm->status != ENTRY_ACTIVE) {
	    /* pretend these don't exist */
	    continue;
	}
	
	if (timercmp(now, &alarm->update, <)) {
	    continue;
	}
	
	error = rmonGetValue(alarm->srcPartyID, alarm->srcPartyLength,
			     alarm->dstPartyID, alarm->dstPartyLength,
			     alarm->contextID, alarm->contextLength,
			     alarm->variable, alarm->variableLen,
			     &value, alarm);
	if (error == 1) {
	    /* the request was sent out asynchronously.  snmp_input() will
	    ** call one of the alarmUpdate routines.
	    */
	    DoTimerAdd(&alarm->update, now, &alarm->intervalAdd);
	    continue;
	}
	
	if (error) {
	    /* send objectUnavailable alarm event */
	    eventGenerate(alarm->unavailableEventIndex,
			  EVENT_TYPE_UNAVAILABLE, (void*)alarm);
	    alarmDeleteRow(alarm);
	    continue;
	}

	if (alarm->sampleType == ALARM_DELTA_VALUE) {
	    alarmUpdateDelta(alarm, value);
	}
	else {
	    alarmUpdateAbs(alarm, value);
	}

	DoTimerAdd(&alarm->update, now, &alarm->intervalAdd);
    }
}

/* process the response to a Get request */
Export int
alarmGetResponse(pdu, state, op, session)
    struct snmp_pdu *pdu;
    struct get_req_state *state;
    int op;
    struct snmp_session *session;
{
    struct alarmEntry *alarm = (struct alarmEntry *)state->info;
    struct variable_list *vp;

    if ((alarm->ss != session) || (alarm->reqid != pdu->reqid)) {
	return 1;
    }
    
    if (op == TIMED_OUT) {
	/* got an error, so send an inform and delete
	** the alarm entry
	*/
	eventGenerate(alarm->unavailableEventIndex,
		      EVENT_TYPE_UNAVAILABLE, (void*)alarm);
	alarm->status = ENTRY_DESTROY;
	return 1;
    }	

    if (pdu->errstat == SNMP_ERR_NOERROR) {
	/* send the variable to an update routine */
	vp = pdu->variables;
	if (vp && ((vp->type == INTEGER) || (vp->type == COUNTER) ||
		   (vp->type == TIMETICKS) || (vp->type == GAUGE) ||
		   (vp->type == COUNTER64))) {
	    if (alarm->sampleType == ALARM_DELTA_VALUE) {
		alarmUpdateDelta(alarm, *vp->val.integer);
	    }
	    else {
		alarmUpdateAbs(alarm, *vp->val.integer);
	    }
	}
    }
    else {
	/* got an error, so send an inform and delete
	** the alarm entry
	*/
	eventGenerate(alarm->unavailableEventIndex,
		      EVENT_TYPE_UNAVAILABLE,(void*) alarm);
	alarm->status = ENTRY_DESTROY;
    }
    return 1;
}

/*
 * If statP is non-NULL, the referenced object is at that location.
 * If statP is NULL and alarm is non-NULL, the instance (row) exists, but not
 * this variable.
 * If statP is NULL and alarm is NULL, then neither this instance nor the
 * variable exists.
 */
/* return TRUE on success and FALSE on failure */
static int
write_alarmtab(action, var_val, var_val_type, var_val_len, statP,
		name, name_len)
    int action;			/* IN - RESERVE1, RESERVE2, COMMIT, or FREE */
    u_char *var_val;	/* IN - input or output buffer space */
    u_char var_val_type;	/* IN - type of input buffer */
    int var_val_len;	/* IN - input and output buffer len */
    u_char *statP;		/* IN - pointer to local statistic */
    oid *name;			/* IN - pointer to name requested */
    int name_len;		/* IN - number of sub-ids in the name */
{
    register int index;
    register int variable;
    register struct alarmEntry *alarm;
    int size;
    int32 int_value;
    oid oid_value[MAX_OID_LEN];
    u_char string_value[MAX_OWNER_STR_LEN];
    int buffersize = 1000;
    int contextlen;
    oid *context;
    
    /* .1.3.6.1.6.3.2.1.1.2.1.X.cxlen.context.index */
    
    contextlen = name[12];
    if (name_len < (13 + contextlen))
	return SNMP_ERR_NOCREATION;
    context = name + 13;
    index = name[13 + contextlen];
    alarm = alarmGetRow(context, contextlen, index);
    
    switch (action) {
      case RESERVE1:
	if (!alarm) {
	    alarm = alarmNewRow(context, contextlen, index);
	    if (!alarm) {
		/* no memory for row */
		return SNMP_ERR_RESOURCEUNAVAILABLE;
	    }
	}
	else {
	    /* we have a row, but some vars will change.  Remember
	     ** the current numbers.
	     */
	    if (alarmShadowRow(alarm) == 0) {
		/* not enough memory available */
		return SNMP_ERR_RESOURCEUNAVAILABLE;
	    }
	}
	break;
      case RESERVE2:
	if (!alarm) {
	    /* this should have been created in the RESERVE1 phase */
	    return SNMP_ERR_GENERR;
	}
	break;
      case COMMIT:
	if (!alarm) {
	    return SNMP_ERR_GENERR;
	}
	alarmCommitRow(alarm);
	return SNMP_ERR_NOERROR;
      case FREE:
	if (!alarm) {
	    return SNMP_ERR_GENERR;
	}
	if (alarm->status == ENTRY_DESTROY) {
	    /* this row did not exist before we began this RESERVE/FREE
	     ** cycle, so delete it now.
	     */
	    alarmDeleteRow(alarm);
	}
	else {
	    /* the row existed before, so just get rid of the shadow
	     ** copy.
	     */
	    alarmFreeShadow(alarm);
	}
	return SNMP_ERR_NOERROR;
    }
    
    variable = name[11];
    
    /* interval, variable, sampleType, startupAlarm, risingThresh,
     ** fallingThresh, risingEventIndex, fallingEventIndex,
     ** and status are the user-writable variables in this table.
     */
    switch (variable) {
      case ALARMTABVARIABLE:
	if (action == RESERVE1) {
	    /* make sure it's an oid */
	    if (var_val_type != ASN_OBJECT_ID) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    size = sizeof(oid_value) / sizeof(oid);
	    if (!asn_parse_objid(var_val, &buffersize, &var_val_type,
				oid_value, &size)) {
		return SNMP_ERR_WRONGENCODING;
				}
#if 0
	    /* this assures that the variable exists and resolves
	     ** to an integer value
	     */
	    if (rmonGetValue(oid_value, size, (int32 *)&int_value) != 0) {
		return SNMP_ERR_INCONSISTENTVALUE;
	    }
#endif
	    /* There should also be a check here that the setter
	     ** can read all variables in the MIB.  Since all
	     ** communities have read access to all variables,
	     ** this test is punted.
	     */
	    bcopy((char *)oid_value, (char *)alarm->shadow->variable,
		  size * sizeof(oid));
	    alarm->shadow->variableLen = size;
	    alarm->shadow->bitmask |= ALARMTABVARIABLEMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABINTERVAL:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    alarm->shadow->interval = int_value;
	    alarm->shadow->bitmask |= ALARMTABINTERVALMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABSAMPLETYPE:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    if ((int_value < ALARM_ABSOLUTE_VALUE) ||
		(int_value > ALARM_DELTA_VALUE)) {
		return SNMP_ERR_WRONGLENGTH;
		}
	    alarm->shadow->sampleType = int_value;
	    alarm->shadow->bitmask |= ALARMTABSAMPLETYPEMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABSTARTUPALARM:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    if ((int_value < ALARM_STARTUP_RISING) ||
		(int_value > ALARM_STARTUP_RISING_OR_FALLING)) {
		return SNMP_ERR_WRONGVALUE;
		}
	    alarm->shadow->startupAlarm = int_value;
	    alarm->shadow->bitmask |= ALARMTABSTARTUPALARMMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABRISINGTHRESH:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    alarm->shadow->risingThresh = int_value;
	    alarm->shadow->bitmask |= ALARMTABRISINGTHRESHMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABFALLINGTHRESH:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    alarm->shadow->fallingThresh = int_value;
	    alarm->shadow->bitmask |= ALARMTABFALLINGTHRESHMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABRISINGINDEX:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    if ((int_value < 0) || (int_value > 65535)) {
		return SNMP_ERR_WRONGVALUE;
	    }
	    alarm->shadow->risingEventIndex = int_value;
	    alarm->shadow->bitmask |= ALARMTABRISINGINDEXMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABFALLINGINDEX:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    if ((int_value < 0) || (int_value > 65535)) {
		return SNMP_ERR_WRONGVALUE;
	    }
	    alarm->shadow->fallingEventIndex = int_value;
	    alarm->shadow->bitmask |= ALARMTABFALLINGINDEXMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABUNAVAILABLEINDEX:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    if ((int_value < 0) || (int_value > 65535)) {
		return SNMP_ERR_WRONGVALUE;
	    }
	    alarm->shadow->unavailableEventIndex = int_value;
	    alarm->shadow->bitmask |= ALARMTABUNAVAILABLEINDEXMASK;
	}
	else if (action == RESERVE2) {
	    /* not allowed to change this if the entry is valid */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->status == ENTRY_ACTIVE)) {
		return SNMP_ERR_INCONSISTENTVALUE;
		}
	}
	break;
      case ALARMTABSTATUS:
	if (action == RESERVE1) {
	    if (var_val_type != ASN_INTEGER) {
		return SNMP_ERR_WRONGTYPE;
	    }
	    if (!asn_parse_int(var_val, &buffersize, &var_val_type,
			      &int_value, sizeof(int_value))) {
		return SNMP_ERR_WRONGENCODING;
			      }
	    if (int_value < ENTRY_ACTIVE
		|| int_value > ENTRY_DESTROY
		|| int_value == ENTRY_NOTREADY
		|| int_value == ENTRY_CREATEANDGO) {
		return SNMP_ERR_WRONGVALUE;
	    }
	    
	    if (int_value == ENTRY_CREATEANDWAIT) {
		if (alarm->status != ENTRY_DESTROY) {
		    /* this is an entry that already existed; not
		     ** allowed to set it to underCreation
		     */
		    return SNMP_ERR_INCONSISTENTVALUE;
		}
		int_value = ENTRY_NOTINSERVICE;
	    }
	    
	    alarm->shadow->status = int_value;
	    alarm->shadow->bitmask |= ALARMTABSTATUSMASK;
	}
	else if (action == RESERVE2) {
	    /* when the entry is first created, the value field is not
	     ** valid
	     */
	    if ((alarm->shadow->status == ENTRY_ACTIVE) &&
		(alarm->shadow->bitmask !=
		 (ALARMTABCOMPLETEMASK & ~ALARMTABVALUEMASK))) {
		return SNMP_ERR_INCONSISTENTVALUE;
		 }
	}
	break;
      default:
	return SNMP_ERR_GENERR;
    }
    
    return SNMP_ERR_NOERROR;
}

Export u_char *
var_alarmnextindex(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that
				 ** points here
				 */
    register oid *name;		/* IN/OUT - input name requested,
				 ** output name found
				 */
    register int *length;	/* IN/OUT - length of input and output oid's */
    int exact;		/* IN - TRUE if an exact match was requested. */
    int *var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set
					     ** variable, otherwise 0
					     */
{
    int result;

    *write_method = (int (*)())NULL;
    result = compare(name, *length, vp->name, (int)vp->namelen);
    if ((exact && (result != 0)) || (!exact && (result >= 0)))
	return NULL;

    bcopy((char *)vp->name, (char *)name,
	  (int)vp->namelen * sizeof(oid));
    *length = vp->namelen;
    *var_len = sizeof(int32);
    
    switch (vp->magic) {
      case ALARMNEXTINDEX:
	return (u_char *)&alarmNextIndex;
      default:
	ERROR("");
    }

    return NULL;
}
    
/* respond to requests for variables in the alarm table */
Export u_char *
var_alarmtab(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that
				     ** points here
									*/
    register oid *name;		/* IN/OUT - input name requested,
				 ** output name found
				 */
    register int *length;	/* IN/OUT - length of input and output oid's */
    int exact;		/* IN - TRUE if an exact match was requested. */
    int *var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set
					     ** variable, otherwise 0
					     */
{
    oid newname[MAX_NAME_LEN];
    int result;
    int mask;
    struct alarmEntry *alarm;

    mask = 1 << (vp->magic - 1);
    bcopy((char *)vp->name, (char *)newname,
	  (int)vp->namelen * sizeof(oid));
    *write_method = write_alarmtab;
    
    /* .1.3.6.1.6.3.2.1.1.2.1.X.cxlen.context.index */
    
    /* find "next" process */
    for (alarm = alarmTab; alarm; alarm = alarm->next) {
	if ((alarm->bitmask & mask) == 0) {
	    /* this variable isn't available for inspection */
	    continue;
	}
	newname[12] = (oid)alarm->contextLength;
	bcopy(alarm->contextID, newname + 13,
	      alarm->contextLength * sizeof(oid));
	newname[13 + alarm->contextLength] = (oid)alarm->index;
	result = compare(name, *length,
			 newname, 14 + alarm->contextLength);
	if ((exact && (result == 0)) || (!exact && (result < 0)))
	    break;
    }
    if (!alarm) {
	return NULL;
    }
    
    
    bcopy((char *)newname, (char *)name,
	  (int)(14 + alarm->contextLength) * sizeof(oid));
    *length = 14 + alarm->contextLength;
    *var_len = sizeof(int32);
    
    switch (vp->magic) {
      case ALARMTABVARIABLE:
	*var_len = alarm->variableLen * sizeof(oid);
	return (u_char *)alarm->variable;
      case ALARMTABINTERVAL:
	return (u_char *)&alarm->interval;
      case ALARMTABSAMPLETYPE:
	return (u_char *)&alarm->sampleType;
      case ALARMTABVALUE:
	*write_method = (int (*)())NULL;
	return (u_char *)&alarm->value;
      case ALARMTABSTARTUPALARM:
	return (u_char *)&alarm->startupAlarm;
      case ALARMTABRISINGTHRESH:
	return (u_char *)&alarm->risingThresh;
      case ALARMTABFALLINGTHRESH:
	return (u_char *)&alarm->fallingThresh;
      case ALARMTABRISINGINDEX:
	return (u_char *)&alarm->risingEventIndex;
      case ALARMTABFALLINGINDEX:
	return (u_char *)&alarm->fallingEventIndex;
      case ALARMTABUNAVAILABLEINDEX:
	return (u_char *)&alarm->fallingEventIndex;
      case ALARMTABSTATUS:
	if (alarm->status == ENTRY_NOTINSERVICE){
	    if (alarm->bitmask !=
		(ALARMTABCOMPLETEMASK & ~ALARMTABVALUEMASK)){
		long_return = ENTRY_NOTREADY;
		return (u_char *)&long_return;
	    }
	}
	return (u_char *)&alarm->status;
      default:
	ERROR("");
    }
    
    return NULL;
}
