/*\
 *	DISTRIBUTION: HNMS v2.0
 *	FILE: hnmsd/io.c
 *
 *	HNMS IO module.
 *
 *	1993 Jude George
 *	NAS Facility, NASA Ames Research Center
 *
 *	Copyright (c) 1994 Jude George
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 1, or (at your option)
 *	any later version.
 *
 *	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.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
\*/

#include <stdio.h>

#include "stdhnms.h"

/*
 *  The chunksize must be a power of two.
 */
#define ANNOUNCE_CHUNKSIZE	1024

/*\
 *  The announce cache is not indexed.  The status cache is indexed
 *  by HNMS id.
\*/
struct announce_entry {
    unsigned int		ip_address;
    unsigned int		timestamp;
};
typedef struct announce_entry	*ACE;

struct status_entry {
    unsigned int		ip_address;
    unsigned int		timestamp;
};
typedef struct status_entry	SCE;

static ACE			announce_cache = (void *)0;
static int			announce_entries = 0;
static SCE			status_cache[MAX_OBJECTS + 1];
static unsigned int		nonresponsive_timeout, unreachable_timeout;
static unsigned int		ramp_time;

/*\
 *  Filter out agents I don't want.
\*/
static int IO_agent_filter(ip_address)
    unsigned int	ip_address;
{
    char		*cp1, *cp2;
    static char		buf[STRBUFLEN];
    static char		filter_addr_str[STRBUFLEN];
    static char		mask_str[STRBUFLEN];
    int			ok;
    unsigned int	filter_addr, filter_mask;

    if (!ip_address)
	return 0;
    strcpy(buf, PARAM_get_str(oid_hnmsModuleIoAgentFilter));
    cp1 = buf;

    ok = 0;
    do {
	/* another wierd sun cast..I wish I knew how the SGI did it - thorpej */
#ifdef __sparc
	cp2 = (char *)strchr((char *)cp1, (int)':');
#else
	cp2 = strchr(cp1, (int)':');
#endif
	if (cp2)
	    *cp2 = 0;
	filter_addr = 0;
	bzero(filter_addr_str, STRBUFLEN);
	bzero(mask_str, STRBUFLEN);
	if (sscanf(cp1, "%[^_]_%s", filter_addr_str, mask_str) != 2)
	    break;
	filter_addr = inet_addr(filter_addr_str);
	filter_mask = inet_addr(mask_str);
	if ((ip_address & filter_mask) == filter_addr) {
	    ok = 1;
	    break;
	}
	if (cp2)
	    cp1 = cp2 + sizeof(char);
    } while (cp2);

    return ok;
}

/*\
 *  Add an IP address to the announce cache.
\*/
int IO_set_announce_cache(ip_address)
    const unsigned int	ip_address;
{
    int			n;
    unsigned int	current_time;

    if (!ip_address)
	return -1;

    if (!IO_agent_filter(ip_address))
	return -1;

    for (n = 0; n < announce_entries; n++)
	if (announce_cache[n].ip_address == ip_address)
	    return -1;

    current_time = get_int_time();

    /*
     * Add a new row, resizing the cache if necessary.
     */
    if (announce_entries % ANNOUNCE_CHUNKSIZE == 0)
	announce_cache = (ACE)HNMS_realloc
	    (announce_cache, (announce_entries + ANNOUNCE_CHUNKSIZE) *
	     sizeof(*announce_cache));
    announce_cache[announce_entries].ip_address = ip_address;
    announce_cache[announce_entries].timestamp = 0;
    announce_entries++;
    return 0;
}

/*\
 *  If discovery is turned on, check the announce cache for new IP
 *  addresses;  make new Ipaddr objects.
\*/
int IO_make_ipaddrs()
{
    int			n;
    int			id;
    char		*cp;
    int			rval;
    unsigned int	current_time, pcc;
    static unsigned int	last_check_time = 0;

    pcc = PARAM_get_int(oid_hnmsModuleIoCacheCheckInterval);
    current_time = get_int_time();
    if (current_time - last_check_time >= pcc) {
	last_check_time = current_time;
	for (n = 0; n < announce_entries; n++)
	    if (announce_cache[n].timestamp == 0) {
		announce_cache[n].timestamp = current_time;
		rval = OBJ_add(0, OBJ_agent, ipaddr_int2str
			       (announce_cache[n].ip_address));
		id = ABS(rval);
		OBJ_set(id, oid_hnmsAgentIpaddr,
			&announce_cache[n].ip_address,
			0, MIB_ipaddr, 0, 0, &current_time);
		cp = PARAM_get_str(oid_hnmsModuleIoDefSnmpCommunity);
		OBJ_set(id, oid_hnmsAgentCommunity, cp, strlen(cp),
			MIB_octetstring, 0, 0, &current_time);
	    }
    }
}

/*\
 *  Set the timestamp of an Ipaddr in the status cache, given the
 *  IP address of the Ipaddr.
\*/
int IO_set_status_cache(ip_address)
    unsigned int	ip_address;
{
    int			n;

    for (n = 1; n <= MAX_OBJECTS; n++)
	if (status_cache[n].ip_address == ip_address) {
	    status_cache[n].timestamp = get_int_time();
	    break;
	}
    return 0;
}

/*\
 *  Make an entry in the status cache for an Ipaddr object.
\*/
static void IO_make_status_cache_entry(hnms_id)
    int			hnms_id;
{
    int			class = 0;
    int			ip_address = 0;

    OBJ_get(hnms_id, oid_hnmsObjClass, &class, 0, 0, 0, 0);
    if (class != OBJ_ipaddr)
	return;

    OBJ_get(hnms_id, oid_hnmsObjIpaddr, &ip_address, 0, 0, 0, 0);
    status_cache[hnms_id].ip_address = ip_address;
}

/*\
 *  For an Ipaddr, set it's status to REACHABLE if I've heard from it,
 *  or reduce it's status if I haven't heard from it and it has passed
 *  it's timeout.
\*/
static void IO_set_ipaddr_status(hnms_id)
    const int		hnms_id;
{
    int			class;
    unsigned int	current_time;
    unsigned int	time_dif;
    int			status;
    static unsigned int	start_time = 0;

    if (status_cache[hnms_id].timestamp == 0)
	return;

    current_time = get_int_time();

    /*
     * Don't set any status values while I am starting up (allow
     * 180 seconds to get into steady-state, enough time for all
     * the pings to have kicked in).
     */
    if (start_time == 0)
	start_time = current_time;
    else if (current_time - start_time <= ramp_time)
	return;

    OBJ_get(hnms_id, oid_hnmsObjClass, &class, 0, 0, 0, 0);
    if (class != OBJ_ipaddr)
	return;

    time_dif = (unsigned int)(current_time - status_cache[hnms_id].timestamp);
    if (time_dif >= unreachable_timeout) {
	status = STATUS_UNREACHABLE;
	OBJ_set(hnms_id, oid_hnmsObjReachStatus,
		&status, 0, MIB_integer, 0, &current_time);
    }
    else if (time_dif >= nonresponsive_timeout) {
	status = STATUS_NONRESPONSIVE;
	OBJ_set(hnms_id, oid_hnmsObjReachStatus,
		&status, 0, MIB_integer, 0, &current_time);
    }
    else {
	status = STATUS_REACHABLE;
	OBJ_set(hnms_id, oid_hnmsObjReachStatus,
		&status, 0, MIB_integer, 0, &current_time);
    }

}

/*\
 *  Set the reachability status of Ipaddr objects based on the last time
 *  we heard from them.
\*/
int IO_assign_status()
{
    int			pcc;
    unsigned int	current_time;
    static unsigned int	last_check_time = 0;

    pcc = PARAM_get_int(oid_hnmsModuleIoCacheCheckInterval);
    current_time = get_int_time();
    if (last_check_time == 0)
	bzero(status_cache, (MAX_OBJECTS + 1) * sizeof(SCE));
    if ((unsigned int)(current_time - last_check_time) >= pcc) {
	last_check_time = current_time;
	OBJ_iterate(IO_make_status_cache_entry, (caddr_t)0);
    }
    nonresponsive_timeout = PARAM_get_int(oid_hnmsModuleIoNonresponsive);
    unreachable_timeout = PARAM_get_int(oid_hnmsModuleIoUnreachable);
    ramp_time = PARAM_get_int(oid_hnmsModuleIoRampTime);
    OBJ_iterate(IO_set_ipaddr_status, (caddr_t)0);
    return 0;
}
