//
//    This file is part of Dire Wolf, an amateur radio packet TNC.
//
//    Copyright (C) 2016  John Langner, WB2OSZ
//
//    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 2 of the License, 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, see <http://www.gnu.org/licenses/>.
//


/*------------------------------------------------------------------
 *
 * File:	mheard.c
 *
 * Purpose:	Maintain a list of all stations heard.
 *
 * Description: This was added for IGate statistics and checking if a user is local
 *		but would also be useful for the AGW network protocol 'H' request.
 *
 *		This application has no GUI and is not interactive so
 *		I'm not sure what else we might do with the information.
 *
 *		Why mheard instead of just heard?  The KPC-3+ has an MHEARD command
 *		to list stations heard.  I guess that stuck in my mind.
 *		It should be noted that here "heard" refers to the AX.25 source station.
 *		Before printing the received packet, the "heard" line refers to who
 *		we heard over the radio.  This would be the digipeater with "*" after
 *		its name.
 *
 * Future Ideas: Someone suggested using SQLite to store the information
 *		so other applications could access it.
 *
 *------------------------------------------------------------------*/

#include "direwolf.h"

#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <stdlib.h>	
#include <string.h>	
#include <ctype.h>	

#include "textcolor.h"
#include "decode_aprs.h"
#include "ax25_pad.h"
#include "hdlc_rec2.h"		// for retry_t
#include "mheard.h"
#include "latlong.h"


// This is getting updated from two different threads so we need a critical region
// for adding new nodes.

static dw_mutex_t mheard_mutex;


// I think we can get away without a critical region for reading if we follow these
// rules:
//
// (1) When adding a new node, make sure it is complete, including next ptr,
//	before adding it to the list.
// (2) Update the start of list pointer last.
// (2) Nothing gets deleted.

// If we ever decide to start cleaning out very old data, all access would then
// need to use the mutex.


/*
 * Information for each station heard over the radio or from Internet Server.
 */

typedef struct mheard_s {

	struct mheard_s *pnext;			// Pointer to next in list.

	char callsign[AX25_MAX_ADDR_LEN];	// Callsign from the AX.25 source field.

	int count;				// Number of times heard.
						// We don't use this for anything.
						// Just something potentially interesting when looking at data dump.

	int chan;				// Most recent channel where heard.

	int num_digi_hops;			// Number of digipeater hops before we heard it.
						// over radio.  Zero when heard directly.

	time_t last_heard_rf;			// Timestamp when last heard over the radio.

	time_t last_heard_is;			// Timestamp when last heard from Internet Server.

	double dlat, dlon;			// Last position.  G_UNKNOWN for unknown.

	int msp;				// Allow message sender positon report.
						// When non zero, an IS>RF position report is allowed.
						// Then decremented.

						// What else would be useful?
						// The AGW protocol is by channel and returns
						// first heard in addition to last heard.
} mheard_t;







/*
 * The list could be quite long and we hit this a lot so use a hash table.
 */

#define MHEARD_HASH_SIZE 73	// Best if prime number.

static mheard_t *mheard_hash[MHEARD_HASH_SIZE];

static inline int hash_index(char *callsign) {
	int n = 0;
	char *p = callsign;

	while (*p != '\0') {
	  n += *p++;
	}
	return (n % MHEARD_HASH_SIZE);
}

static mheard_t *mheard_ptr(char *callsign) {
	int n = hash_index(callsign);
	mheard_t *p = mheard_hash[n];

	while (p != NULL) {
	  if (strcmp(callsign,p->callsign) == 0) return (p);
	  p = p->pnext;
	}
	return (NULL);
}
	  
	
static int mheard_debug = 0;


/*------------------------------------------------------------------
 *
 * Function:	mheard_init
 *
 * Purpose:	Initialization at start of application.
 *
 * Inputs:	debug		- Debug level.
 *
 * Description:	Clear pointer table.
 *		Save debug level for later use.
 *
 *------------------------------------------------------------------*/


void mheard_init (int debug) 
{
	int i;

	mheard_debug = debug;

	for (i = 0; i < MHEARD_HASH_SIZE; i++) {
	  mheard_hash[i] = NULL;
	}

/*
 * Mutex to coordinate adding new nodes.
 */
	dw_mutex_init(&mheard_mutex);

} /* end mheard_init */



/*------------------------------------------------------------------
 *
 * Function:	mheard_dump
 *
 * Purpose:	Print list of stations heard for debugging.
 *
 *------------------------------------------------------------------*/

/* convert some time in past to hours:minutes text format. */

static void age(char *result, time_t now, time_t t)
{
	int s, h, m;

	if (t == 0) {
	  strcpy (result, "-  ");
	  return;
	}

	s = (int)(now - t);
	m = s / 60;
	h = m / 60;
	m -= h * 60;

	sprintf (result, "%4d:%02d", h, m);
}

/* Convert latitude, longitude to text or - if not defined. */

static void latlon (char * result, double dlat, double dlon)
{
	if (dlat != G_UNKNOWN && dlon != G_UNKNOWN) {
	  sprintf (result, "%6.2f %7.2f", dlat, dlon);
	}
	else {
	  strcpy (result, "   -       -  ");
	}
}

/* Compare last heard time for use with qsort. */

#define MAXX(x,y) (((x)>(y))?(x):(y))
static int compar(const void *a, const void *b)
{
	mheard_t *ma = *((mheard_t **)a);
	mheard_t *mb = *((mheard_t **)b);

	time_t ta = MAXX(ma->last_heard_rf, ma->last_heard_is);
	time_t tb = MAXX(mb->last_heard_rf, mb->last_heard_is);

	return (tb - ta);
}

#define MAXDUMP 1000

static void mheard_dump (void)
{
	int i;
	mheard_t *mptr;
	time_t now = time(NULL);
	char stuff[80];
	char rf[16];		// hours:minutes
	char is[16];
	char position[40];
	mheard_t *station[MAXDUMP];
	int num_stations = 0;


/* Get linear array of node pointers so they can be sorted easily. */

	num_stations = 0;

	for (i = 0; i < MHEARD_HASH_SIZE; i++) {
	  for (mptr = mheard_hash[i]; mptr != NULL; mptr = mptr->pnext) {

	    if (num_stations < MAXDUMP) {
	      station[num_stations] = mptr;
	      num_stations++;
	    }
	    else {
	      text_color_set(DW_COLOR_ERROR);
	      dw_printf ("mheard_dump - max number of stations exceeded.\n");
	    }
	  }
	}

/* Sort most recently heard to the top then print. */

	qsort (station, num_stations, sizeof(mheard_t *), compar);

	text_color_set(DW_COLOR_DEBUG);

	dw_printf ("callsign  cnt chan hops    RF      IS    lat     long  msp\n");

	for (i = 0; i < num_stations; i++) {

	  mptr = station[i];

	  age (rf, now, mptr->last_heard_rf);
	  age (is, now, mptr->last_heard_is);
	  latlon (position, mptr->dlat, mptr->dlon);

	  snprintf (stuff, sizeof(stuff), "%-9s %3d   %d   %d  %7s %7s  %s  %d\n",
	    mptr->callsign, mptr->count, mptr->chan, mptr->num_digi_hops, rf, is, position, mptr->msp);
	  dw_printf ("%s", stuff);
	}

} /* end mheard_dump */


/*------------------------------------------------------------------
 *
 * Function:	mheard_save_rf
 *
 * Purpose:	Save information about station heard over the radio.
 *
 * Inputs:	chan	- Radio channel where heard.
 *
 *		A	- Exploded information from APRS packet.
 *
 *		pp	- Received packet object.
 *
 * 		alevel	- audio level.
 *
 *		retries	- Amount of effort to get a good CRC.
 *
 * Description:	Calling sequence was copied from "log_write."
 *		It has a lot more than what we currently keep but the
 *		hooks are there so it will be easy to capture additional
 *		information when the need arises.
 *
 *------------------------------------------------------------------*/

void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries)
{
	time_t now = time(NULL);
	char source[AX25_MAX_ADDR_LEN];
	int hops;
	mheard_t *mptr;

	ax25_get_addr_with_ssid (pp, AX25_SOURCE, source);

/*
 * How many digipeaters has it gone thru before we hear it?
 * We can count the number of digi addresses that are marked as "has been used."
 * This is not always accurate because there is inconsistency in digipeater behavior.
 * The base AX.25 spec seems clear in this regard.  The used digipeaters should
 * should accurately reflict the path taken by the packet.  Sometimes we see excess
 * stuff in there.  Even when you understand what is going on, it is still an ambiguous
 * situation.  Look for my rant in the User Guide.
 */

	hops = ax25_get_heard(pp) - AX25_SOURCE;
	
	mptr = mheard_ptr(source);
	if (mptr == NULL) {
	  int i;
/*
 * Not heard before.  Add it.
 */

	  if (mheard_debug) {
	    text_color_set(DW_COLOR_DEBUG);
	    dw_printf ("mheard_save_rf: %s %d - added new\n", source, hops);
	  }

	  mptr = calloc(sizeof(mheard_t),1);
	  strlcpy (mptr->callsign, source, sizeof(mptr->callsign));
	  mptr->count = 1;
	  mptr->chan = chan;
	  mptr->num_digi_hops = hops;
	  mptr->last_heard_rf = now;
	  mptr->dlat = G_UNKNOWN;
	  mptr->dlon = G_UNKNOWN;
	  
	  i = hash_index(source);

	  dw_mutex_lock (&mheard_mutex);
	  mptr->pnext = mheard_hash[i];	// before inserting into list.
	  mheard_hash[i] = mptr;
	  dw_mutex_unlock (&mheard_mutex);
	}
	else {

/*
 * Update existing entry.
 * The only tricky part here is that we might hear the same transmission
 * several times.  First direct, then thru various digipeater paths.
 * We are interested in the shortest path if heard very recently.
 */

	  if (hops > mptr->num_digi_hops && (int)(now - mptr->last_heard_rf) < 15) {

	    if (mheard_debug) {
	      text_color_set(DW_COLOR_DEBUG);
	      dw_printf ("mheard_save_rf: %s %d - skip because hops was %d %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard_rf) );
	    }
	  }
	  else {

	    if (mheard_debug) {
	      text_color_set(DW_COLOR_DEBUG);
	      dw_printf ("mheard_save_rf: %s %d - update time, was %d hops %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard_rf));
	    }

	    mptr->count++;
	    mptr->chan = chan;
	    mptr->num_digi_hops = hops;
	    mptr->last_heard_rf = now;
	  }
	}

	if (A->g_lat != G_UNKNOWN && A->g_lon != G_UNKNOWN) {
	  mptr->dlat = A->g_lat;
	  mptr->dlon = A->g_lon;
	}

	if (mheard_debug >= 2) {
	  int limit = 10;		// normally 30 or 60.  more frequent when debugging.
	  text_color_set(DW_COLOR_DEBUG);
	  dw_printf ("mheard debug, %d min, DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d\n", limit, mheard_count(0,limit), mheard_count(2,limit), mheard_count(8,limit));
	}

	if (mheard_debug) {
	  mheard_dump ();
	}

} /* end mheard_save_rf */



/*------------------------------------------------------------------
 *
 * Function:	mheard_save_is
 *
 * Purpose:	Save information about station heard via Internet Server.
 *
 * Inputs:	ptext	- Packet in monitoring text form as sent by the Internet server.
 *
 *				  Any trailing CRLF should have been removed.
 *				  Typical examples:
 *
 *				KA1BTK-5>APDR13,TCPIP*,qAC,T2IRELAND:=4237.62N/07040.68W$/A=-00054 http://aprsdroid.org/
 *				N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:<IGATE,MSG_CNT=0,LOC_CNT=0
 *				K1RI-2>APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmA<Ct3_ sT010/002g005t045r000p023P020h97b10148
 *				KC1BOS-2>T3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile
 *
 *				  Notice how the final address in the header might not
 *				  be a valid AX.25 address.  We see a 9 character address
 *				  (with no ssid) and an ssid of two letters.
 *
 *				  The "q construct"  ( http://www.aprs-is.net/q.aspx ) provides
 *				  a clue about the journey taken but I don't think we care here.
 *
 *				  All we should care about here is the the source address.
 *
 * Description:
 *
 *------------------------------------------------------------------*/

void mheard_save_is (char *ptext)
{
	packet_t pp;
	time_t now = time(NULL);
	char source[AX25_MAX_ADDR_LEN];
	mheard_t *mptr;

/*
 * Try to parse it into a packet object.
 * This will contain "q constructs" and we might see an address
 * with two alphnumeric characters in the SSID so we must use
 * the non-strict parsing.
 *
 * Bug:  Up to 8 digipeaters are allowed in radio format.
 * There is a potential of finding a larger number here.
 */
	pp = ax25_from_text(ptext, 0);

	if (pp == NULL) {
	  if (mheard_debug) {
	    text_color_set(DW_COLOR_ERROR);
	    dw_printf ("mheard_save_is: Could not parse message from server.\n");
	    dw_printf ("%s\n", ptext);
	  }
	  return;
	}

	ax25_get_addr_with_ssid (pp, AX25_SOURCE, source);

	mptr = mheard_ptr(source);
	if (mptr == NULL) {
	  int i;
/*
 * Not heard before.  Add it.
 */

	  if (mheard_debug) {
	    text_color_set(DW_COLOR_DEBUG);
	    dw_printf ("mheard_save_is: %s - added new\n", source);
	  }

	  mptr = calloc(sizeof(mheard_t),1);
	  strlcpy (mptr->callsign, source, sizeof(mptr->callsign));
	  mptr->count = 1;
	  mptr->last_heard_is = now;
	  mptr->dlat = G_UNKNOWN;
	  mptr->dlon = G_UNKNOWN;

	  i = hash_index(source);

	  dw_mutex_lock (&mheard_mutex);
	  mptr->pnext = mheard_hash[i];	// before inserting into list.
	  mheard_hash[i] = mptr;
	  dw_mutex_unlock (&mheard_mutex);
	}
	else {

/* Already there.  UPdate last heard from IS time. */

	  if (mheard_debug) {
	    text_color_set(DW_COLOR_DEBUG);
	    dw_printf ("mheard_save_is: %s - update time, was %d seconds ago.\n", source, (int)(now - mptr->last_heard_rf));
	  }
	  mptr->count++;
	  mptr->last_heard_is = now;
	}

	// Is is desirable to save any location in this case?
	// I don't think it would help.
	// The whole purpose of keeping the location is for message sending filter.
	// We wouldn't want to try sending a message to the station if we didn't hear it over the radio.
	// On the other hand, I don't think it would hurt.
	// The filter always includes a time since last heard over the radi.

	if (mheard_debug >= 2) {
	  int limit = 10;		// normally 30 or 60
	  text_color_set(DW_COLOR_DEBUG);
	  dw_printf ("mheard debug, %d min, DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d\n", limit, mheard_count(0,limit), mheard_count(2,limit), mheard_count(8,limit));
	}

	if (mheard_debug) {
	  mheard_dump ();
	}

	ax25_delete (pp);

} /* end mheard_save_is */


/*------------------------------------------------------------------
 *
 * Function:	mheard_count
 *
 * Purpose:	Count local stations for IGate statistics report like this:
 *
 *			<IGATE,MSG_CNT=1,LOC_CNT=25
 *
 * Inputs:	max_hops	- Include only stations heard with this number of 
 *				  digipeater hops or less.  For reporting, we might use:
 *
 *					0 for DIR_CNT (heard directly)
 *					IGate transmit path for LOC_CNT.
 *						e.g. 3 for WIDE1-1,WIDE2-2
 *					8 for RF_CNT.
 *
 *		time_limit	- Include only stations heard within this many minutes.
 *				  Typically 30 or 60.
 *
 * Returns:	Number to be used in the statistics report.
 *
 * Description:	Look for discussion here:  http://www.tapr.org/pipermail/aprssig/2016-June/045837.html
 *
 *		Lynn KJ4ERJ:
 *	
 *			For APRSISCE/32, "Local" is defined as those stations to which messages 
 *			would be gated if any are received from the APRS-IS.  This currently 
 *			means unique stations heard within the past 30 minutes with at most two 
 *			used path hops.
 *
 *			I added DIR_CNT and RF_CNT with comma delimiters to APRSISCE/32's IGate 
 *			status.  DIR_CNT is the count of unique stations received on RF in the 
 *			past 30 minutes with no used hops.  RF_CNT is the total count of unique 
 *			stations received on RF in the past 30 minutes.
 *
 *		Steve K4HG:
 *
 *			The number of hops defining local should match the number of hops of the
 *			outgoing packets from the IGate. So if the path is only WIDE, then local
 *			should only be stations heard direct or through one hop. From the beginning
 *			I was very much against on a standardization of the outgoing IGate path,
 *			hams should be free to manage their local RF network in a way that works
 *			for them. Busy areas one hop may be best, I lived in an area where three was
 *			a much better choice. I avoided as much as possible prescribing anything
 *			that might change between locations.
 *
 *			The intent was how many stations are there for which messages could be IGated.
 *			IGate software keeps an internal list of the 'local' stations so it knows
 *			when to IGate a message, and this number should be the length of that list.
 *			Some IGates have a parameter for local timeout, 1 hour was the original default,
 *			so if in an hour the IGate has not heard another local packet the station is
 *			dropped from the local list. Messages will no longer be IGated to that station
 *			and the station count would drop by one. The number should not just continue to rise.
 *
 *
 *------------------------------------------------------------------*/

int mheard_count (int max_hops, int time_limit)
{
	time_t since = time(NULL) - time_limit * 60;
	int count = 0;
	int i;
	mheard_t *p;

	for (i = 0; i < MHEARD_HASH_SIZE; i++) {
	  for (p = mheard_hash[i]; p != NULL; p = p->pnext) {
	    if (p->last_heard_rf >= since && p->num_digi_hops <= max_hops) {
	      count++;
	    }
	  }
	}

	if (mheard_debug == 1) {
	  text_color_set(DW_COLOR_DEBUG);
	  dw_printf ("mheard_count(<= %d digi hops, last %d minutes) returns %d\n", max_hops, time_limit, count);
	}

	return (count);

} /* end mheard_count */



/*------------------------------------------------------------------
 *
 * Function:	mheard_was_recently_nearby
 *
 * Purpose:	Determine whether given station was heard recently on the radio.
 *
 * Inputs:	role		- "addressee" or "source" if debug out is desired.
 *				  Otherwise empty string.
 *
 *		callsign	- Callsign for station.
 *
 *		time_limit	- Include only stations heard within this many minutes.
 *				  Typically 30 or 60.
 *
 *		max_hops	- Include only stations heard with this number of
 *				  digipeater hops or less.  For reporting, we might use:
 *
 *		dlat, dlon, km	- Include only stations within distance of location.
 *				  Not used if G_UNKNOWN is supplied.
 *
 * Returns:	1 for true, 0 for false.
 *
 *------------------------------------------------------------------*/

int mheard_was_recently_nearby (char *role, char *callsign, int time_limit, int max_hops, double dlat, double dlon, double km)
{
	mheard_t *mptr;
	time_t now;
	int heard_ago;


	if (role != NULL && strlen(role) > 0) {

	  text_color_set(DW_COLOR_INFO);
	  if (dlat != G_UNKNOWN && dlon != G_UNKNOWN && km != G_UNKNOWN) {
	    dw_printf ("Was message %s %s heard in the past %d minutes, with %d or fewer digipeater hops, and within %.1f km of %.2f %.2f?\n", role, callsign, time_limit, max_hops, km, dlat, dlon);
	  }
	  else {
	    dw_printf ("Was message %s %s heard in the past %d minutes, with %d or fewer digipeater hops?\n", role, callsign, time_limit, max_hops);
	  }
	}

	mptr = mheard_ptr(callsign);

	if (mptr == NULL || mptr->last_heard_rf == 0) {

	  if (role != NULL && strlen(role) > 0) {
	    text_color_set(DW_COLOR_INFO);
	    dw_printf ("No, we have not heard %s over the radio.\n", callsign);
	  }
	  return (0);
	}

	now = time(NULL);
	heard_ago = (int)(now - mptr->last_heard_rf) / 60;

	if (heard_ago > time_limit) {

	  if (role != NULL && strlen(role) > 0) {
	    text_color_set(DW_COLOR_INFO);
	    dw_printf ("No, %s was last heard over the radio %d minutes ago with %d digipeater hops.\n", callsign, heard_ago, mptr->num_digi_hops);
	  }
	  return (0);
	}

	if (mptr->num_digi_hops > max_hops) {

	  if (role != NULL && strlen(role) > 0) {
	    text_color_set(DW_COLOR_INFO);
	    dw_printf ("No, %s was last heard over the radio with %d digipeater hops %d minutes ago.\n", callsign, mptr->num_digi_hops, heard_ago);
	  }
	  return (0);
	}

// Apply physical distance check?

	if (dlat != G_UNKNOWN && dlon != G_UNKNOWN && km != G_UNKNOWN && mptr->dlat != G_UNKNOWN && mptr->dlon != G_UNKNOWN) {

	  double dist = ll_distance_km (mptr->dlat, mptr->dlon, dlat, dlon);

	  if (dist > km) {

	    if (role != NULL && strlen(role) > 0) {
	      text_color_set(DW_COLOR_INFO);
	      dw_printf ("No, %s was %.1f km away although it was %d digipeater hops %d minutes ago.\n", callsign, dist, mptr->num_digi_hops, heard_ago);
	    }
	    return (0);
	  }
	  else {
	    if (role != NULL && strlen(role) > 0) {
	      text_color_set(DW_COLOR_INFO);
	      dw_printf ("Yes, %s last heard over radio %d minutes ago, %d digipeater hops.  Last location %.1f km away.\n", callsign, heard_ago, mptr->num_digi_hops, dist);
	    }
	    return (1);
	  }
	}

// Passed all the tests.

	if (role != NULL && strlen(role) > 0) {
	  text_color_set(DW_COLOR_INFO);
	  dw_printf ("Yes, %s last heard over radio %d minutes ago, %d digipeater hops.\n", callsign, heard_ago, mptr->num_digi_hops);
	}

	return (1);

} /* end mheard_was_recently_nearby */



/*------------------------------------------------------------------
 *
 * Function:	mheard_set_msp
 *
 * Purpose:	Set the "message sender position" count for specified station.
 *
 * Inputs:	callsign	- Callsign for station which sent the "message."
 *
 *		num		- Number of position reports to allow.  Typically 1.
 *
 *------------------------------------------------------------------*/

void mheard_set_msp (char *callsign, int num)
{
	mheard_t *mptr;

	mptr = mheard_ptr(callsign);

	if (mptr != NULL) {

	  mptr->msp = num;

	  if (mheard_debug) {
	    text_color_set(DW_COLOR_INFO);
	    dw_printf ("MSP for %s set to %d\n", callsign, num);
	  }
	}
	else {
	  text_color_set(DW_COLOR_ERROR);
	  dw_printf ("Internal error: Can't find %s to set MSP.\n", callsign);
	}

} /* end mheard_set_msp */


/*------------------------------------------------------------------
 *
 * Function:	mheard_get_msp
 *
 * Purpose:	Get the "message sender position" count for specified station.
 *
 * Inputs:	callsign	- Callsign for station which sent the "message."
 *
 * Returns:	The cound for the specified station.
 *		0 if not found.
 *
 *------------------------------------------------------------------*/

int mheard_get_msp (char *callsign)
{
	mheard_t *mptr;

	mptr = mheard_ptr(callsign);

	if (mptr != NULL) {

	  if (mheard_debug) {
	    text_color_set(DW_COLOR_INFO);
	    dw_printf ("MSP for %s is %d\n", callsign, mptr->msp);
	  }
	  return (mptr->msp);	// Should we have a time limit?
	}

	return (0);

} /* end mheard_get_msp */



/* end mheard.c */
