/*\
 *	DISTRIBUTION: HNMS v2.0
 *	FILE: hnmslib/session.c
 *
 *	Session layer of HNMP protocol.
 *
 *	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 <sys/types.h>
#include <netdb.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <signal.h>

#include "stdhnms.h"

/*\
 *  The envelope for an HNMP message.  This contains information
 *  necessary for the message to be transmitted by the lower layers.
 *  buf points to the message itself, in ASN.1 BER format.
\*/
static struct hnmp_envelope {
    unsigned int		timestamp_creation;
    unsigned int		timestamp_retrans;
    int				msg_id;
    int				len;
    char			*buf;
};
typedef struct hnmp_envelope	*Envelope;

/*\
 *  An HNMP session.  Non-server modules will only have one session --
 *  a conenction to the server.
 *  Each session includes HNMP connection information.  The window is
 *  an array of struct hnmp_envelope, not a pointer to one envelope.
 *  The size of the array is set by a parameter at startup.
\*/
static struct session {
    int				module_id;
    int				module_type;
    unsigned int		ip_address;
    unsigned int		udp_port;
    unsigned int		timestamp_out;
    unsigned int		timestamp_in;
    struct hnmp_envelope	*window;
    struct session		*next;
};
typedef struct session		*Session;

static Session			sessions = NULL;
static unsigned int		my_last_msg_id = 0;
static int			hnmp_sock = 0;
static struct sockaddr_in	hnmp_local_addr;
static struct sockaddr_in	hnmp_remote_addr;
static char			send_buf[HNMP_BUFSIZ];
static char			recv_buf[HNMP_BUFSIZ];
static char			ack_buf[HNMP_BUFSIZ];
static int			pws, pct, pai, pri, pmt, pmi;
static int			connections = 0;

/*\
 *  For server only.
\*/
static int			highest_module_id = 0;
static int			keybearer = 0;

/*\
 *  ISODE callback.
\*/
void advise()
{
    HNMS_debug(DEBUG_HNMP, "isode lib called advise()\n");
}

/*\
 *  Signal handler.
\*/
void HNMP_sighandler(sig, code, sc)
    int			sig;
    int			code;
    struct sigcontext	*sc;
{
    switch (sig) {
    case SIGHUP:
    case SIGQUIT:
	while (sessions)
	    HNMP_close(sessions->module_id);
	break;

    default:;
    }
}

/*\
 *  Set up some structures for HNMP.
 *  If the module is the server, use the HNMP well-known port for
 *  listening.  Otherwise, ask the OS for a port.
 *
 *  Return 0 if successful, -1 otherwise.
\*/
int HNMP_init()
{
    Session		s, next;
    int			value, i, parm;
    struct servent	*service;
    int			len;

    pai = PARAM_get_int(oid_hnmsModuleHnmpAckInterval);
    pct = PARAM_get_int(oid_hnmsModuleHnmpConnTimeout);
    pws = PARAM_get_int(oid_hnmsModuleHnmpWinSize);
    pri = PARAM_get_int(oid_hnmsModuleHnmpRetransInterval);
    pmt = PARAM_get_int(oid_hnmsModuleType);
    pmi = PARAM_get_int(oid_hnmsModuleId);

    /*
     * If I am the server, set my module id to 0, and set the highest
     * module id I have allocated to be 0 (since I am initializing now,
     * I haven't allocated any module ids for other modules yet.
     *
     * If I am not the server, set my module id to -1, since I haven't
     * received a valid module id from the server yet.  The module
     * id of an unconnected non-server module is always -1.
     *
     * Also, reset my message id for hnmp messages that I generate,
     * and reset my list of peer modules.  If, for some reason,
     * I already have HNMP connections, clear all of them.
     */
    if (pmt == MODULE_SERVER)
	PARAM_set_int(oid_hnmsModuleId, 0);
    else
	PARAM_set_int(oid_hnmsModuleId, -1);
    PARAM_set_int(oid_hnmsModuleHnmpMsgs, 0);
    PARAM_set_int(oid_hnmsModuleHnmpRetransMsgs, 0);

    /*
     * We use only one socket for HNMP; all of our HNMP connections
     * are multiplexed into one socket.
     *
     * Get the socket for HNMP, set it for non-blocking I/O, and
     * enable it for re-use of local addresses.  Don't know if the
     * latter is really necessary.  Also enable sending of broadcast
     * messages.
     */
    hnmp_sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (hnmp_sock <= 0) {
	hnms_errno = HNMS_err_sock_open;
	return -1;
    }
    parm = 1;
    if (ioctl(hnmp_sock, FIONBIO, &parm) < 0) {
	close(hnmp_sock);
	hnms_errno = HNMS_err_sock_nbio;
	return -1;
    }
    parm = 1;
    if (setsockopt(hnmp_sock, SOL_SOCKET, SO_REUSEADDR, &parm, sizeof(int))
	< 0) {
	close(hnmp_sock);
	hnms_errno = HNMS_err_sock_reuse;
	return -1;
    }
    parm = 1;
    if (setsockopt(hnmp_sock, SOL_SOCKET, SO_BROADCAST, &parm, sizeof(int))
	< 0) {
	close(hnmp_sock);
	hnms_errno = HNMS_err_sock_bcast;
	return -1;
    }

    /*
     * Set address family, local address, local port.
     */
    bzero((char *) &hnmp_local_addr, sizeof(struct sockaddr_in));
    hnmp_local_addr.sin_family = AF_INET;
    hnmp_local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (pmt == MODULE_SERVER) {
	service = getservbyname("hnmp", "udp");
	if (service)
	    hnmp_local_addr.sin_port = service->s_port;
	else
	    hnmp_local_addr.sin_port = htons(HNMP_PORT);
    }
    else
	hnmp_local_addr.sin_port = htons(0);

    /*
     * If we don't use the HNMP port, bind() will choose one for us.
     */
    if (bind(hnmp_sock, &hnmp_local_addr, sizeof(struct sockaddr_in)) < 0) {
	close(hnmp_sock);
	hnms_errno = HNMS_err_sock_bind;
	return -1;
    }

    len = sizeof(struct sockaddr_in);
    getsockname(hnmp_sock, &hnmp_local_addr, &len);
    HNMS_debug(DEBUG_HNMP,
	     "HNMP listening on port %d\n", hnmp_local_addr.sin_port);

    /*
     * prepare for all the sendto()'s that are to come.
     */
    hnmp_remote_addr.sin_family = AF_INET;

    /*
     * Set a signal handler.
     */
    signal(SIGHUP, HNMP_sighandler);
    signal(SIGQUIT, HNMP_sighandler);

    connections = 0;
    PARAM_set_int(oid_hnmsModuleHnmpConnections, connections);

    return 0;
}

/*\
 *  This function must be used before any messages can be enqueued
 *  to a module.
 *  Create data structures for an HNMP connection with the module
 *  given its ip address, udp port, and module type.  If a port of 0
 *  is specified, the well-known HNMP port is used.  The module_type
 *  of the server is MODULE_SERVER.
 *
 *  If I am the server, return the module id I have assigned to the
 *  new module, otherwise return 0.
 *  Whether or not I am the server, return -1 if a connection is already
 *  open to that address/port.
\*/
int HNMP_open(ip_address, udp_port, module_type)
    const int		ip_address;
    const int		udp_port;
    const int		module_type;
{
    Session		s;
    Message		msg;
    char		*v_str;

    /*
     * If a session already exists at this ipaddr/port, don't open another.
     */
    for (s = sessions; s; s = s->next)
	if (s->ip_address == ip_address && s->udp_port == udp_port) {
	    hnms_errno = HNMS_err_conn_exists;
	    return -1;
	}

    /*
     * Allocate a new module struct and HNMP send window.
     */
    s = (Session)HNMS_alloc(1, sizeof *s);
    s->ip_address = ip_address;
    s->udp_port = udp_port;
    s->module_type = module_type;
    s->next = sessions;
    sessions = s;
    s->window = HNMS_alloc(pws, sizeof *s->window);

    /*
     * Set the timestamp_in to be the current time so HNMP_listen()
     * doesn't immediately close the connection due to connection
     * timeout.  Set the timestamp_out to be the current time since
     * there's no reason to immediately send a connection Ack.
     */
    s->timestamp_in = s->timestamp_out = get_int_time();

    pmt = PARAM_get_int(oid_hnmsModuleType);
    if (pmt == MODULE_SERVER) {
	/*
	 *  Allocate a new module id.  During the lifetime of the server,
	 *  old module ids are never re-used; new ones are assigned in
	 *  ever-increasing order.
	 */
	s->module_id = ++highest_module_id;
    }
    else
	s->module_id = 0;

    HNMS_debug(DEBUG_HNMP,
	     "HNMP opening connection to module %d\n", s->module_id);

    PEER_open(s->module_id);
    HNMP_open_callback(s->module_id);

    connections++;
    PARAM_set_int(oid_hnmsModuleHnmpConnections, connections);

    return s->module_id;
}

/*\
 *  Close the HNMP connection to the module specified by its module id.
\*/
void HNMP_close(module_id)
    const int		module_id;
{
    Session		s, prev;
    int			i;
    Message		msg;
    int			int_value;

    for (s = sessions; s; s = s->next)
	if (s->module_id == module_id)
	    break;
    if (!s)
	return;

    if (pmi == 0) {
	/*
	 * If I am the server, tell all UI modules that I am closing a
	 * connection.  Do not do this if I am closing the connection
	 * because its send window is full, as it will cause this routine
	 * to be corecursive with HNMP_Message_send().
	 */
	if (hnms_errno != HNMS_err_window_full) {
	}
	
       	/*
	 * If the target module is the keybearer, reclaim the key.
	 */
	if (keybearer == module_id)
	    keybearer = 0;
    }
    else
	PARAM_set_int(oid_hnmsModuleId, -1);

    for (s = sessions, prev = NULL; s; prev = s, s = s->next)
	if (s->module_id == module_id) {
	    if (prev)
		prev->next = s->next;
	    else
		sessions= s->next;
	    if (s->window) {
		for (i = 0; i < pws; i++)
		    if (s->window[i].buf)
			free(s->window[i].buf);
		free(s->window);
	    }
	    free(s);
	    break;
	}

    HNMS_debug(DEBUG_HNMP,
	     "HNMP closed connection to module %d\n", module_id);

    PEER_close(module_id);
    HNMP_close_callback(module_id);

    connections--;
    PARAM_set_int(oid_hnmsModuleHnmpConnections, connections);
}

/*\
 *  Send out HNMP messages that have been enqueued.
 *  Retransmit all outgoing HNMP messages that have not been
 *  acknowledged and are older than the PARAM_hnmp_retrans_interval.
\*/
int HNMP_send()
{
    Session		s;
    int			i;
    int			prm;
    unsigned int	current_time;

    current_time = get_int_time();

    for (s = sessions; s; s = s->next) {
	for (i= 0; i < pws; i++)
	    if (s->window[i].msg_id) {
		/*
		 * Transmit the message if it has passed the retramsmit
		 * timeout period.
		 */
		if (current_time - s->window[i].timestamp_retrans > pri) {
		    if (s->window[i].timestamp_retrans) {
			prm = PARAM_get_int(oid_hnmsModuleHnmpRetransMsgs);
			PARAM_set_int(oid_hnmsModuleHnmpRetransMsgs, ++prm);
		    }
		    s->window[i].timestamp_retrans = current_time;

		    hnmp_remote_addr.sin_addr.s_addr = htonl(s->ip_address);
		    hnmp_remote_addr.sin_port = htons(s->udp_port);

		    if (sendto(hnmp_sock, s->window[i].buf,
			       s->window[i].len, 0, &hnmp_remote_addr,
			       sizeof(struct sockaddr_in)) < 0) {
			hnms_errno = HNMS_err_sendto;
			HNMS_debug(DEBUG_ERRORS,
				 "HNMP port already in use.  Exiting.\n");
			exit(1);
		    }

		    HNMS_debug(DEBUG_HNMP, "[SEND msg %d to %x:%d]\n",
			     s->window[i].msg_id, s->ip_address, s->udp_port);
		}
	    }
    }
    return 0;
}

/*\
 *  Create and send an HNMP Ack message to the given address and port,
 *  with the given IMPLICIT INTEGER value.  Unlike other Message
 *  types, Acks are not enqueued through the send window.  Return 0,
 *  or -1 if there was an error.
\*/
static int HNMP_Ack_send(value, ip_address, udp_port)
    const int		value;
    const unsigned int	ip_address;
    const unsigned int	udp_port;
{
    Message		msg;
    PS			ps1;
    PE			*pe1;
    int			len, rval;

    msg = Message_create(HNMP_ACK);
    /*
     * Fill in the header fields that were not filled in by
     * Message_create().
     */
    msg->data->un.ack->parm = value;
    msg->to__module__id = -1;
    msg->msg__timestamp->parm = get_int_time();
    msg->msg__id = 0;

    /*
     * Encode the message structs into a presentation element
     * and get its encoded length.
     */
    rval = encode_HNMP_Message(&pe1, 1, 0, NULLVP, msg);
    len = ps_get_abs(pe1);

    /*
     * Get a presentation stream and associate a character buffer
     * with the stream.  Serialize the presentation element into the
     * buffer.  We can then do away with the presentation element
     * and the presentation stream.  Use a separate buffer from the
     * rest of HNMP, or the pe2ps will trash it.
     */
    if ((ps1 = ps_alloc(str_open)) == NULLPS) {
	pe_free(ps1);
	Message_free(msg);
	hnms_errno = HNMS_err_presentation_stream;
	return -1;
    }
    bzero(ack_buf, HNMP_BUFSIZ);
    if (str_setup(ps1, ack_buf, HNMP_BUFSIZ, 1) == NOTOK) {
	pe_free(pe1);
	ps_free(ps1);
	Message_free(msg);
	hnms_errno = HNMS_err_presentation_stream;
	return -1;
    }
    rval = pe2ps(ps1, pe1);
    pe_free(pe1);
    ps_free(ps1);

    /*
     * Send the message immediately -- we don't enqueue Ack messages.
     */
    hnmp_remote_addr.sin_addr.s_addr = htonl(ip_address);
    hnmp_remote_addr.sin_port = htons(udp_port);
    if (sendto(hnmp_sock, ack_buf, len, 0, &hnmp_remote_addr,
	       sizeof(struct sockaddr_in)) < 0) {
	hnms_errno = HNMS_err_sendto;
	HNMS_debug(DEBUG_HNMP, "HNMP port already in use.  Exiting.\n");
	exit(1);
    }
    Message_free(msg);

    return 0;
}

/*\
 *  Serialize an HNMP message in Message structure format into a character
 *  buffer in ASN.1 BER (basic encoding rules) format.  The character
 *  buffer is placed in the HNMP send window, and the Message structures
 *  are free()'d.  This function calls HNMP_send once, for the first
 *  (and hopefully only) attempt to transmit the message.
 *
 *  to_module_id should be the destination module id.  The msg-id and
 *  msg-timestamp fields are filled in automatically.
 *
 *  Return 0 if the message was successfully placed in the send window,
 *  -1 otherwise.  In any case, the message and all structures within it
 *  are free()'d.
\*/
int HNMP_Message_enqueue(msg, to_module_id)
    Message		msg;
    const int		to_module_id;
{
    Session		s;
    PS			ps1;
    PE			*pe1;
    int			len, i, window_index, rval;

    /*
     * If this is an Ack message, return an error; we don't enqueue
     * Ack messages.
     */
    if (msg->data->offset == HNMP_ACK) {
	hnms_errno = HNMS_err_bad_msg_type;
	return -1;
    }

    /*
     * Find the module that this message is being sent to.
     */
    for (s = sessions; s; s = s->next)
	if (s->module_id == to_module_id)
	    break;
    if (!s) {
	Message_free(msg);
	hnms_errno = HNMS_err_session_not_found;
	return -1;
    }

    /*
     * Fill in the header fields that were not filled in by
     * Message_create().
     */
    msg->to__module__id = to_module_id;
    msg->msg__timestamp->parm = get_int_time();
    msg->msg__id = ++my_last_msg_id;

    /*
     * Encode the message structs into a presentation element
     * and get its encoded length.  If it is too long, don't enqueue
     * it.  If there are no errors, malloc a character buffer
     * of that size.
     */
    rval = encode_HNMP_Message(&pe1, 1, 0, NULLVP, msg);
    len = ps_get_abs(pe1);
    if (len > HNMP_BUFSIZ) {
	pe_free(pe1);
	Message_free(msg);
	hnms_errno = HNMS_err_stream_too_long;
	return -1;
    }

    /*
     * Get a presentation stream and associate the character buffer
     * with the stream.  Serialize the presentation element into the
     * buffer.  We can then do away with the presentation element
     * and the presentation stream.
     */
    if ((ps1 = ps_alloc(str_open)) == NULLPS) {
	pe_free(ps1);
	hnms_errno = HNMS_err_presentation_stream;
	return -1;
    }
    bzero(send_buf, HNMP_BUFSIZ);
    if (str_setup(ps1, send_buf, HNMP_BUFSIZ, 1) == NOTOK) {
	pe_free(pe1);
	ps_free(ps1);
	hnms_errno = HNMS_err_presentation_stream;
	return -1;
    }
    rval = pe2ps(ps1, pe1);
    pe_free(pe1);
    ps_free(ps1);

    /*
     * Find a space for the message in the send window.
     */
    window_index = -1;
    for (i = 0; i < pws; i++) {
	if (!s->window[i].msg_id) {
	    window_index = i;
	    break;
	}
	else
	    if (s->window[i].msg_id == msg->msg__id) {
		hnms_errno = HNMS_err_reuse_msg_id;
		HNMS_debug(DEBUG_HNMP, "HNMP_Message_enqueue");
	    }
    }

    /*
     * Place the message in the first available window space, and set
     * its timestamp_retrans to 0 so it gets sent immediately when
     * HNMP_send() is called.  If there is no space, consider the
     * connection broken.
     */
    if (window_index >= 0) {
	s->window[window_index].msg_id = msg->msg__id;
	s->window[window_index].timestamp_creation = get_int_time();
	s->window[window_index].timestamp_retrans = 0;
	s->window[window_index].buf = HNMS_alloc(1, len);
	bcopy(send_buf, s->window[window_index].buf, len);
	s->window[window_index].len = len;
	HNMS_debug(DEBUG_HNMP, "HNMP: [ENQ msg %d (%s) to mod %d] ",
		   my_last_msg_id, hnmp_msg_strs[msg->data->offset],
		   s->module_id);
	Message_free(msg);
	HNMP_send();
	PARAM_set_int(oid_hnmsModuleHnmpMsgs, my_last_msg_id);
	return 0;
    }
    else {
	Message_free(msg);
	hnms_errno = HNMS_err_window_full;
	HNMP_close(to_module_id);
	return -1;
    }
}

/*\
 *  Retrieve and process an HNMP UDP datagram waiting at the HNMP socket.
\*/
static void HNMP_recv()
{
    unsigned int	ip_address, udp_port;
    int			remote_len;
    Session		s;
    PS			ps1;
    PE			pe1;
    OID			oid;
    Message		msg;
    char		*cp;
    int			class;
    int			i, rval;
    int			new_id;

    remote_len = sizeof(struct sockaddr_in);

    /*
     * Retrieve the message into a buffer.
     */
    bzero(recv_buf, HNMP_BUFSIZ);
    if ((rval = recvfrom(hnmp_sock, recv_buf, HNMP_BUFSIZ, 0,
			 &hnmp_remote_addr, &remote_len)) < 0)
	HNMS_debug(DEBUG_ERRORS, "HNMP recvfrom\n");
    if (rval == HNMP_BUFSIZ)
	HNMS_debug(DEBUG_ERRORS, "HNMP recvfrom\n");

    ip_address = ntohl(hnmp_remote_addr.sin_addr.s_addr);
    udp_port = ntohs(hnmp_remote_addr.sin_port);

    /*
     * Get a presentation stream, and associate the HNMP buffer with
     * the presentation stream.
     */
    if ((ps1 = ps_alloc(str_open)) == NULLPS) {
	hnms_errno = HNMS_err_presentation_stream;
	HNMS_debug(DEBUG_HNMP, "HNMP_recv (ps_alloc)");
	return;
    }
    if (str_setup(ps1, recv_buf, HNMP_BUFSIZ, 1) == NOTOK) {
	hnms_errno = HNMS_err_presentation_stream;
	HNMS_debug(DEBUG_HNMP, "HNMP_recv (str_setup)");
	ps_free(ps1);
	return;
    }

    /*
     * Decode the presentation element into an HNMP Message structure.
     */
    pe1 = ps2pe(ps1);
    rval = decode_HNMP_Message(pe1, 1, NULLCP, NULLVP, &msg);
    ps_free(ps1);
    if (rval == NOTOK) {
	hnms_errno = HNMS_err_presentation_stream;
	HNMS_debug(DEBUG_HNMP, "HNMP_recv (decode)");
	return;
    }
    pe_free(pe1);

    /*
     * If the message has a different HNMP community than myself,
     * discard it.
     */
    cp = qb2str(msg->community);
    if (strcmp(cp, PARAM_get_str(oid_hnmsModuleHnmsCommunity))) {
	free(cp);
	Message_free(msg);
	return;
    }
    free(cp);

    /*
     * Check my module list.  If the message is from an unknown
     * module-id, discard the message, unless it's a Hello from
     * an unconnected module and I am the server.  In that case,
     * send back a Welcome.
     *
     * If the message is from a known module-id but has a different
     * IP address or UDP port than what I expect that module to have,
     * discard the message.
     */
    for (s = sessions; s; s = s->next)
	if (s->module_id == msg->from__module__id)
	    break;
    if (!s) {
	if ((msg->data->offset == HNMP_HELLO && pmi == 0)) {
	    HNMS_debug(DEBUG_HNMP,
		       "HNMP: [RECV msg %d (%s) from %x:%d mod %d]\n",
		       msg->msg__id, hnmp_msg_strs[msg->data->offset],
		       ip_address, udp_port, s ? s->module_id : -1);
	    HNMP_Ack_send(msg->msg__id, ip_address, udp_port);
	    new_id = HNMP_open(ip_address, udp_port, msg->from__module__type);
	    HNMP_Message_process(msg);
	    Message_free(msg);
	    return;
	}
	else {
	    HNMS_debug(DEBUG_HNMP,
		       "HNMP: discarding msg from unknown module at %x:%d\n",
		       ip_address, udp_port);
	    Message_free(msg);
	    return;
	}
    }
    else if (s->ip_address != ip_address || s->udp_port != udp_port) {
	HNMS_debug(DEBUG_HNMP,
		 "HNMP: discarding msg from %d at new ipaddr/port %x:%d\n",
		 ip_address, udp_port);
	Message_free(msg);
	return;
    }

    /*
     * If the message is not an Ack, print it now.
     */
    if (msg->data->offset != HNMP_ACK)
	HNMS_debug(DEBUG_HNMP, "HNMP: [RECV msg %d (%s) from %x:%d mod %d]\n",
		 msg->msg__id, hnmp_msg_strs[msg->data->offset],
		 ip_address, udp_port, s ? s->module_id : -1);

    /*
     * Take care of some message-specific operations.  The following
     * Messages are acted on by HNMP at the session layer: Ack, Hello,
     * Welcome, Redirect, Goodbye, GetKey, SendKey.  All messages except
     * Ack, however, are passed up to the module code for module-specific
     * actions.
     */
    switch (msg->data->offset) {
    case HNMP_ACK:
	/*
	 * If this is an Ack 0, reset the connection timestamp
	 * for the session).  If this is an Ack with an integer value,
	 * clear the message that's been Ack'd.
	 */
	if (msg->data->un.ack->parm == 0)
	    s->timestamp_in = get_int_time();
	else {
	    for (i = 0; i < pws; i++)
		if (s->window[i].msg_id == msg->data->un.ack->parm) {
		    s->window[i].msg_id = 0;
		    s->window[i].timestamp_creation = 0;
		    s->window[i].timestamp_retrans = 0;
		    s->window[i].len = 0;
		    if (s->window[i].buf) {
			free(s->window[i].buf);
			s->window[i].buf = NULL;
		    }
		}
	}
	break;

    case HNMP_HELLO:
	break;

    case HNMP_WELCOME:
	/*
	 * If I am not the server, and I receive a Welcome from the server,
	 * and I do not yet have a module-id, I will take the module-id
	 * assigned by this Welcome.
	 */
	if (msg->from__module__id == 0 && pmi == -1) {
	    pmi = msg->to__module__id;
	    PARAM_set_int(oid_hnmsModuleId, msg->to__module__id);
	}
	break;

    case HNMP_GOODBYE:
	HNMP_close(msg->from__module__id);
	break;

    case HNMP_REDIRECT:
	break;

    case HNMP_GET_KEY:
    case HNMP_SEND_KEY:
	break;

    default:;
    }
    /*
     * If the message is not an Ack:
     *	1) Send an Ack for the message.
     *	2) Hand the message off to the module code, which will process it.
     */
    if (msg->data->offset != HNMP_ACK) {
	HNMP_Ack_send(msg->msg__id, ip_address, udp_port);
	HNMP_Message_process(msg);
    }
    Message_free(msg);
}

/*\
 *  Check to see if there's anything waiting at the hnmp_sock.
 *  Return 1 if there is, 0 otherwise.  Return -1 if there was an error.
\*/
static int HNMP_ready()
{
    struct timeval	select_time;
    fd_set		hnmp_read_ready;
    int			ready_descs;

    select_time.tv_sec = 0;
    select_time.tv_usec = 0;
    FD_ZERO(&hnmp_read_ready);
    FD_SET(hnmp_sock, &hnmp_read_ready);

    if ((ready_descs = select(getdtablesize(), &hnmp_read_ready, 0,
			      0, &select_time)) < 0) {
	hnms_errno = HNMS_err_sock_select;
	return -1;
    }

    return ready_descs;
}

/*\
 *  Maintain HNMP sessions.
\*/
int HNMP_sessions()
{
    unsigned int	current_time;
    Session		s;

    pai = PARAM_get_int(oid_hnmsModuleHnmpAckInterval);
    pct = PARAM_get_int(oid_hnmsModuleHnmpConnTimeout);
    pws = PARAM_get_int(oid_hnmsModuleHnmpWinSize);
    pri = PARAM_get_int(oid_hnmsModuleHnmpRetransInterval);
    pmt = PARAM_get_int(oid_hnmsModuleType);
    pmi = PARAM_get_int(oid_hnmsModuleId);

    current_time = get_int_time();

    for (s = sessions; s; s = s->next) {
	/*
	 * Close the connection if I haven't received a connection Ack
	 * the module within the connection timeout period.
	 */
	if (current_time - s->timestamp_in >= pct) {
	    HNMP_close(s->module_id);
	    hnms_errno = HNMS_err_conn_timeout;
	    return 0;
	}
	/*
	 * Send an Ack to the module if I haven't sent a connection Ack
	 * in the Ack interval period.
	 */
	if (current_time - s->timestamp_out >= pai) {
	    s->timestamp_out = current_time;
	    HNMP_Ack_send(0, s->ip_address, s->udp_port);
	}
    }
    return 0;
}

/*\
 *  Receive and process any waiting HNMP messages.  Don't process
 *  more than 100 messages at a time, let's give CPU to other tasks.
\*/
int HNMP_listen()
{
    int			n, rval;

    n = 0;
    while (rval = HNMP_ready()) {
	if (n++ > 100)
	    break;
	if (rval == -1)
	    return -1;
	HNMP_recv();
    }
    return 0;
}

/*\
 *  For debugging.  Print some useful information about the HNMP windows.
\*/
int HNMP_printstats()
{
    Session	s;
    int		i, n;

    if (!sessions)
	printf("[*** no connections ***]");
    else {
	printf("[*** hnmp windows");
	for (s = sessions; s; s = s->next) {
	    n = 0;
	    for (i = 0; i < pws; i++)
		if (s->window[i].msg_id)
		    n++;
	    printf(" %d:%d", s->module_id, n);
	}
	printf(" ***]\n");
    }
    return 0;
}
