Address potential error state after good send

This change addresses the potential to "hide" the good
status of a send in the case where there are multiple
round-robin groups for a message type. The send algorithm
was modified to eliminate the potential edge case and
to report a successful result if the message was accepted
for delivery to at least one group.

Two issues identified by valgrind (use of a potentially
uninitialised variable in a jump) were also corrected.

Intermediate commits:
Signed-off-by: E. Scott Daniels <daniels@research.att.com>

Add error tag to msg

Missing ERR on failure message.

Signed-off-by: E. Scott Daniels <daniels@research.att.com>

Prevent NPE in symtab

Signed-off-by: E. Scott Daniels <daniels@research.att.com>

Adjust unit tests, fix valgrind inddicated issues

Unit tests were added to verify the new get rte function.
Valgrind indicated several places where a conditional was
using a potentially uninitialised variable; these were fixed.

Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: I7c0a49713ab3d6fbb8f66e3d286049762433dc58
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5d71d59..e7dc408 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,7 +35,7 @@
 cmake_minimum_required( VERSION 3.5 )
 
 set( major_version "1" )		# should be automatically populated from git tag later, but until CI process sets a tag we use this
-set( minor_version "4" )
+set( minor_version "5" )
 set( patch_level "0" )
 
 set( install_root "${CMAKE_INSTALL_PREFIX}" )
diff --git a/src/rmr/common/include/rmr_agnostic.h b/src/rmr/common/include/rmr_agnostic.h
index 9000c29..17a5158 100644
--- a/src/rmr/common/include/rmr_agnostic.h
+++ b/src/rmr/common/include/rmr_agnostic.h
@@ -53,6 +53,7 @@
 #define ENV_RTG_RAW "RMR_RTG_ISRAW"			// if > 0 we expect route table gen messages as raw (not sent from an RMr application)
 #define ENV_VERBOSE_FILE "RMR_VCTL_FILE"	// file where vlevel may be managed for some (non-time critical) functions
 #define ENV_NAME_ONLY "RMR_SRC_NAMEONLY"	// src in message is name only
+#define ENV_WARNINGS	"RMR_WARNINGS"		// if == 1 then we write some, non-performance impacting, warnings
 
 #define NO_FLAGS	0				// no flags to pass to a function
 
@@ -63,6 +64,9 @@
 
 #define CFL_MTC_ENABLED	0x01		// multi-threaded call is enabled
 
+									// context flags
+#define CTXFL_WARN		0x01		// ok to warn on stderr for some things that shouldn't happen
+
 									// msg buffer flags
 #define MFL_ZEROCOPY	0x01		// the message is an allocated zero copy message and can be sent.
 #define MFL_NOALLOC		0x02		// send should NOT allocate a new buffer before returning
diff --git a/src/rmr/common/src/rt_generic_static.c b/src/rmr/common/src/rt_generic_static.c
index d19c839..816e18a 100644
--- a/src/rmr/common/src/rt_generic_static.c
+++ b/src/rmr/common/src/rt_generic_static.c
@@ -178,8 +178,9 @@
 				memcpy( nb, buf, len );
 				*(nb+len) = '\n';			// insert \n and nil into the two extra bytes we allocated
 				*(nb+len+1) = 0;
-				free( buf );
 			}	
+
+			free( buf );
 		}
 	}
 
@@ -200,7 +201,7 @@
 	}
 
 	if( (rte = (rtable_ent_t *) malloc( sizeof( *rte ) )) == NULL ) {
-		fprintf( stderr, "rmr_add_rte: malloc failed for entry\n" );
+		fprintf( stderr, "[ERR] rmr_add_rte: malloc failed for entry\n" );
 		return NULL;
 	}
 	memset( rte, 0, sizeof( *rte ) );
@@ -225,7 +226,7 @@
 
 	rmr_sym_map( rt->hash, key, rte );							// add to hash using numeric mtype as key
 
-	if( DEBUG ) fprintf( stderr, "[DBUG] route table entry created: k=%lx groups=%d\n", key, nrrgroups );
+	if( DEBUG ) fprintf( stderr, "[DBUG] route table entry created: k=%llx groups=%d\n", (long long) key, nrrgroups );
 	return rte;
 }
 
diff --git a/src/rmr/common/src/symtab.c b/src/rmr/common/src/symtab.c
index e96af03..4820ebd 100644
--- a/src/rmr/common/src/symtab.c
+++ b/src/rmr/common/src/symtab.c
@@ -321,7 +321,10 @@
 	int hv;                 // hash value of key
 	uint64_t nkey;			// numeric key if class 0
 
-	table = (Sym_tab *) vtable;
+	if( (table = (Sym_tab *) vtable) == NULL ) {
+		return NULL;
+	}
+
 	sym_tab = table->symlist;
 
 	if( class ) {
diff --git a/src/rmr/nng/include/rmr_nng_private.h b/src/rmr/nng/include/rmr_nng_private.h
index 4933f8b..9e93c9f 100644
--- a/src/rmr/nng/include/rmr_nng_private.h
+++ b/src/rmr/nng/include/rmr_nng_private.h
@@ -108,7 +108,8 @@
 static int uta_link2( endpoint_t* ep );
 static int rt_link2_ep( endpoint_t* ep );
 static int uta_epsock_byname( route_table_t* rt, char* ep_name, nng_socket* nn_sock );
-static int uta_epsock_rr( route_table_t *rt, uint64_t key, int group, int* more, nng_socket* nn_sock );
+static int uta_epsock_rr( rtable_ent_t* rte, int group, int* more, nng_socket* nn_sock );
+static rtable_ent_t* uta_get_rte( route_table_t *rt, int sid, int mtype, int try_alt );
 static inline int xlate_nng_state( int state, int def_state );
 
 
diff --git a/src/rmr/nng/src/rmr_nng.c b/src/rmr/nng/src/rmr_nng.c
index 3529c46..1b6e1f0 100644
--- a/src/rmr/nng/src/rmr_nng.c
+++ b/src/rmr/nng/src/rmr_nng.c
@@ -677,6 +677,11 @@
 	}
 	if( DEBUG ) fprintf( stderr, "[DBUG] default ip address: %s\n", ctx->my_ip );
 
+	if( (tok = getenv( ENV_WARNINGS )) != NULL ) {
+		if( *tok == '1' ) {
+			ctx->flags |= CTXFL_WARN;					// turn on some warnings (not all, just ones that shouldn't impact performance)
+		}
+	}
 
 
 	if( (interface = getenv( ENV_BIND_IF )) == NULL ) {
@@ -877,6 +882,7 @@
 	}
 
 	errno = 0;
+	state = 0;
 	while( chute->mbuf == NULL && ! errno ) {
 		if( seconds ) {
 			state = sem_timedwait( &chute->barrier, &ts );				// wait for msg or timeout
@@ -1004,6 +1010,7 @@
 		}
 	}
 
+	state = 0;
 	errno = 0;
 	while( chute->mbuf == NULL && ! errno ) {
 		if( seconds ) {
diff --git a/src/rmr/nng/src/rtable_nng_static.c b/src/rmr/nng/src/rtable_nng_static.c
index 875cfe6..da4101b 100644
--- a/src/rmr/nng/src/rtable_nng_static.c
+++ b/src/rmr/nng/src/rtable_nng_static.c
@@ -244,7 +244,7 @@
 /*
 	Make a round robin selection within a round robin group for a route table
 	entry. Returns the nanomsg socket if there is a rte for the message
-	type, and group is defined. Socket is returned via pointer in the parm
+	key, and group is defined. Socket is returned via pointer in the parm
 	list (nn_sock).
 
 	The group is the group number to select from.
@@ -267,8 +267,8 @@
 		Both of these, in the grand scheme of things, is minor compared to the
 		overhead of grabbing a lock on each call.
 */
-static int uta_epsock_rr( route_table_t *rt, uint64_t key, int group, int* more, nng_socket* nn_sock ) {
-	rtable_ent_t* rte;			// matching rt entry
+static int uta_epsock_rr( rtable_ent_t *rte, int group, int* more, nng_socket* nn_sock ) {
+	//rtable_ent_t* rte;			// matching rt entry
 	endpoint_t*	ep;				// seected end point
 	int  state = FALSE;			// processing state
 	int dummy;
@@ -286,25 +286,19 @@
 		return FALSE;
 	}
 
-	if( rt == NULL ) {
+	if( rte == NULL ) {
 		*more = 0;
 		return FALSE;
 	}
 
-	if( (rte = rmr_sym_pull( rt->hash, key )) == NULL ) {
-		*more = 0;
-		//if( DEBUG ) fprintf( stderr, ">>>> rte not found for type = %lu\n", key );
-		return FALSE;
-	}
-
 	if( group < 0 || group >= rte->nrrgroups ) {
-		//if( DEBUG ) fprintf( stderr, ">>>> group out of range: key=%lu group=%d max=%d\n", key, group, rte->nrrgroups );
+		//if( DEBUG ) fprintf( stderr, ">>>> group out of range: group=%d max=%d\n", group, rte->nrrgroups );
 		*more = 0;
 		return FALSE;
 	}
 
 	if( (rrg = rte->rrgroups[group]) == NULL ) {
-		//if( DEBUG ) fprintf( stderr, ">>>> rrg not found for type key=%lu\n", key );
+		//if( DEBUG ) fprintf( stderr, ">>>> rrg not found for group \n", group );
 		*more = 0; 					// groups are inserted contig, so nothing should be after a nil pointer
 		return FALSE;
 	}
@@ -316,8 +310,7 @@
 			//if( DEBUG ) fprintf( stderr, ">>>> nothing allocated for the rrg\n" );
 			return FALSE;
 
-		case 1:				// exactly one, no rr to deal with and more is not possible even if fanout > 1
-			//*nn_sock = rrg->epts[0]->nn_sock;
+		case 1:				// exactly one, no rr to deal with
 			ep = rrg->epts[0];
 			//if( DEBUG ) fprintf( stderr, ">>>> _rr returning socket with one choice in group \n" );
 			state = TRUE;
@@ -353,4 +346,30 @@
 	return state;
 }
 
+/*
+	Finds the rtable entry which matches the key. Returns a nil pointer if
+	no entry is found. If try_alternate is set, then we will attempt 
+	to find the entry with a key based only on the message type.
+*/
+static inline rtable_ent_t*  uta_get_rte( route_table_t *rt, int sid, int mtype, int try_alt ) {
+	uint64_t key;			// key is sub id and mtype banged together
+	rtable_ent_t* rte;		// the entry we found
+
+	if( rt == NULL || rt->hash == NULL ) {
+		return NULL;
+	}
+
+	key = build_rt_key( sid, mtype );											// first try with a 'full' key
+	if( ((rte = rmr_sym_pull( rt->hash, key )) != NULL)  ||  ! try_alt ) {		// found or not allowed to try the alternate, return what we have
+		return rte;
+	}
+
+	if( sid != UNSET_SUBID ) {								// not found, and allowed to try alternate; and the sub_id was set
+		key = build_rt_key( UNSET_SUBID, mtype );			// rebuild key
+		rte = rmr_sym_pull( rt->hash, key );				// see what we get with this
+	}
+
+	return rte;
+}
+
 #endif
diff --git a/src/rmr/nng/src/sr_nng_static.c b/src/rmr/nng/src/sr_nng_static.c
index 4a71826..c04690d 100644
--- a/src/rmr/nng/src/sr_nng_static.c
+++ b/src/rmr/nng/src/sr_nng_static.c
@@ -1,4 +1,4 @@
-// : vi ts=4 sw=4 noet :
+ // : vi ts=4 sw=4 noet 2
 /*
 ==================================================================================
 	Copyright (c) 2019 Nokia
@@ -629,15 +629,15 @@
 
 */
 static  rmr_mbuf_t* mtosend_msg( void* vctx, rmr_mbuf_t* msg, int max_to ) {
+	rtable_ent_t*	rte;			// the route table entry which matches the message key
 	nng_socket	nn_sock;			// endpoint socket for send
 	uta_ctx_t*	ctx;
 	int			group;				// selected group to get socket for
 	int			send_again;			// true if the message must be sent again
 	rmr_mbuf_t*	clone_m;			// cloned message for an nth send
 	int		 	sock_ok;			// got a valid socket from round robin select
-	uint64_t	 key;				// mtype or sub-id/mtype sym table key
-	int			altk_ok = 0;		// set true if we can lookup on alternate key if mt/sid lookup fails
 	char*		d1;
+	int			ok_sends = 0;		// track number of ok sends
 
 	if( (ctx = (uta_ctx_t *) vctx) == NULL || msg == NULL ) {		// bad stuff, bail fast
 		errno = EINVAL;												// if msg is null, this is their clue
@@ -662,51 +662,78 @@
 		max_to = ctx->send_retries;		// convert to retries
 	}
 
+	if( (rte = uta_get_rte( ctx->rtable, msg->sub_id, msg->mtype, TRUE )) == NULL ) {		// find the entry which matches subid/type allow fallback to type only key
+		if( ctx->flags & CTXFL_WARN ) {
+			fprintf( stderr, "[WARN] no endpoint for mtype=%d sub_id=%d\n", msg->mtype, msg->sub_id );
+		}
+		msg->state = RMR_ERR_NOENDPT;
+		errno = ENXIO;										// must ensure it's not eagain
+		msg->tp_state = errno;
+		return msg;											// caller can resend (maybe) or free
+	}
+
 	send_again = 1;											// force loop entry
 	group = 0;												// always start with group 0
-
-	key = build_rt_key( msg->sub_id, msg->mtype );			// route table key to find the entry
-	if( msg->sub_id != UNSET_SUBID ) {
-		altk_ok = 1; 										// if caller's sub-id doesn't hit with mtype, allow mtype only key for retry
-	}
 	while( send_again ) {
-		sock_ok = uta_epsock_rr( ctx->rtable, key, group, &send_again, &nn_sock );		// round robin sel epoint; again set if mult groups
-		if( DEBUG ) fprintf( stderr, "[DBUG] send msg: type=%d again=%d group=%d len=%d sock_ok=%d ak_ok=%d\n",
-				msg->mtype, send_again, group, msg->len, sock_ok, altk_ok );
+		sock_ok = uta_epsock_rr( rte, group, &send_again, &nn_sock );								// select endpt from rr group and set again if more groups
 
-		if( ! sock_ok ) {
-			if( altk_ok ) {											// we can try with the alternate (no sub-id) key
-				altk_ok = 0;
-				key = build_rt_key( UNSET_SUBID, msg->mtype );		// build with just the mtype and try again
-				send_again = 1;										// ensure we don't exit the while
-				continue;
-			}
-
-			msg->state = RMR_ERR_NOENDPT;
-			errno = ENXIO;											// must ensure it's not eagain
-			msg->tp_state = errno;
-			return msg;												// caller can resend (maybe) or free
-		}
+		if( DEBUG ) fprintf( stderr, "[DBUG] mtosend_msg: flgs=0x%04x type=%d again=%d group=%d len=%d sock_ok=%d\n",
+				msg->flags, msg->mtype, send_again, group, msg->len, sock_ok );
 
 		group++;
 
-		if( send_again ) {
-			clone_m = clone_msg( msg );								// must make a copy as once we send this message is not available
-			if( DEBUG ) fprintf( stderr, "[DBUG] msg cloned: type=%d len=%d\n", msg->mtype, msg->len );
-			msg->flags |= MFL_NOALLOC;								// send should not allocate a new buffer
-			msg = send_msg( ctx, msg, nn_sock, max_to );			// do the hard work, msg should be nil on success
-			/*
-			if( msg ) {
-				// error do we need to count successes/errors, how to report some success, esp if last fails?
-			}
-			*/
+		if( sock_ok ) {													// with an rte we _should_ always have a socket, but don't bet on it
+			if( send_again ) {
+				clone_m = clone_msg( msg );								// must make a copy as once we send this message is not available
+				if( clone_m == NULL ) {
+					msg->state = RMR_ERR_SENDFAILED;
+					errno = ENOMEM;
+					msg->tp_state = errno;
+					if( ctx->flags & CTXFL_WARN ) {
+						fprintf( stderr, "[WARN] unable to clone message for multiple rr-group send\n" );
+					}
+					return msg;
+				}
 
-			msg = clone_m;											// clone will be the next to send
+				if( DEBUG ) fprintf( stderr, "[DBUG] msg cloned: type=%d len=%d\n", msg->mtype, msg->len );
+				msg->flags |= MFL_NOALLOC;								// keep send from allocating a new message; we have a clone to use
+				msg = send_msg( ctx, msg, nn_sock, max_to );			// do the hard work, msg should be nil on success
+	
+				if( msg != NULL ) {										// returned message indicates send error of some sort
+					rmr_free_msg( msg );								// must ditchone; pick msg so we don't have to unfiddle flags
+					msg = clone_m;
+				} else {
+					ok_sends++;
+					msg = clone_m;										// clone will be the next to send
+				}
+			} else {
+				msg = send_msg( ctx, msg, nn_sock, max_to );			// send the last, and allocate a new buffer; drops the clone if it was
+				if( DEBUG ) {
+					if( msg == NULL ) {
+						fprintf( stderr, "[DBUG] mtosend_msg:  send returned nil message!\n" );		
+					}
+				}
+			}
 		} else {
-			msg = send_msg( ctx, msg, nn_sock, max_to );			// send the last, and allocate a new buffer; drops the clone if it was
+			if( ctx->flags & CTXFL_WARN ) {
+				fprintf( stderr, "[WARN] invalid socket for rte, setting no endpoint err: mtype=%d sub_id=%d\n", msg->mtype, msg->sub_id );
+			}
+			msg->state = RMR_ERR_NOENDPT;
+			errno = ENXIO;
 		}
 	}
 
+	if( msg ) {							// call functions don't get a buffer back, so a nil check is required
+		msg->flags &= ~MFL_NOALLOC;		// must return with this flag off
+		if( ok_sends ) {				// multiple rr-groups and one was successful; report ok
+			msg->state = RMR_OK;
+		}
+	
+		if( DEBUG ) fprintf( stderr, "[DBUG] final send stats: ok=%d group=%d state=%d\n\n", ok_sends, group, msg->state );
+	
+		msg->tp_state = errno;
+	}
+
 	return msg;									// last message caries the status of last/only send attempt
 }
 
diff --git a/test/mbuf_api_static_test.c b/test/mbuf_api_static_test.c
index 1ee2858..936c43a 100644
--- a/test/mbuf_api_static_test.c
+++ b/test/mbuf_api_static_test.c
@@ -152,10 +152,18 @@
 	c = rmr_get_meid( mbuf, NULL );			// should allocate and return c
 	errors += fail_if( c == NULL, "get meid with nil dest pointer (did not allocate a buffer)" );
 	errors += fail_if( strcmp( c, "test-meid" ) != 0, "did not get expected meid from mbuffer" );
+	if( c ) {
+		free( c );
+		c = NULL;
+	}
 
 	c = rmr_get_meid( mbuf, c );
 	errors += fail_if( c == NULL, "get meid with a dest pointer returned no pointer" );
 	errors += fail_if( strcmp( c, "test-meid" ) != 0, "did not get expected meid from mbuffer" );
+	if( c ) {
+		free( c );
+		c = NULL;
+	}
 
 
 	// --- test transaction field  access functions ---------------------------------------------------
@@ -195,6 +203,7 @@
 		i = strcmp( ptr, src_buf );
 		errors += fail_not_equal( i, 0, "get xaction did not fetch expected string cmp return (a) was not 0" );
 		free( ptr );
+		ptr = NULL;
 	}
 	
 	errno = 999;
@@ -231,6 +240,7 @@
 	errors += fail_if( i == RMR_OK, "(rv) attempt to copy string to xact with large source buffer" );
 
 
+	rmr_free_msg( mbuf );
 
 	// ------------ trace data tests ----------------------------------------------------------------
 	// CAUTION: to support standalone mbuf api tests, the underlying buffer reallocation functions are NOT used
diff --git a/test/rmr_nng_test.c b/test/rmr_nng_test.c
index c9d9a6a..fb12ec2 100644
--- a/test/rmr_nng_test.c
+++ b/test/rmr_nng_test.c
@@ -96,46 +96,46 @@
 int main() {
 	int errors = 0;
 
-	fprintf( stderr, "<INFO> starting tool tests\n" );
+	fprintf( stderr, "\n<INFO> starting tool tests\n" );
 	errors += tools_test();
 	fprintf( stderr, "<INFO> error count: %d\n", errors );
 
-	fprintf( stderr, "<INFO> starting ring tests (%d)\n", errors );
+	fprintf( stderr, "\n<INFO> starting ring tests (%d)\n", errors );
 	errors += ring_test();
 	fprintf( stderr, "<INFO> error count: %d\n", errors );
 
-	fprintf( stderr, "<INFO> starting symtab tests\n" );
+	fprintf( stderr, "\n<INFO> starting symtab tests\n" );
 	errors += symtab_test( );
 	fprintf( stderr, "<INFO> error count: %d\n", errors );
 
-	fprintf( stderr, "<INFO> starting rtable tests\n" );
+	fprintf( stderr, "\n<INFO> starting rtable tests\n" );
 	errors += rt_test();				// route table tests
 	fprintf( stderr, "<INFO> error count: %d\n", errors );
 
-	fprintf( stderr, "<INFO> starting RMr API tests\n" );
+	fprintf( stderr, "\n<INFO> starting RMr API tests\n" );
 	errors += rmr_api_test();
 
-	fprintf( stderr, "<INFO> run RMr API tests with src name only env var set\n" );
+	fprintf( stderr, "\n<INFO> run RMr API tests with src name only env var set\n" );
 	setenv( "RMR_SRC_NAMEONLY", "1", 1 );
 	errors += rmr_api_test();
 	fprintf( stderr, "<INFO> error count: %d\n", errors );
 
-	fprintf( stderr, "<INFO> starting wormhole tests\n" );
+	fprintf( stderr, "\n<INFO> starting wormhole tests\n" );
 	errors += worm_test();				// test wormhole funcitons
 	fprintf( stderr, "<INFO> error count: %d\n", errors );
 
-	fprintf( stderr, "<INFO> starting send/receive tests\n" );
+	fprintf( stderr, "\n<INFO> starting send/receive tests\n" );
 	errors += sr_nng_test();				// test the send/receive static functions
 	fprintf( stderr, "<INFO> error count: %d\n", errors );
 
-	fprintf( stderr, "<INFO> starting mbuf api tests\n" );
+	fprintf( stderr, "\n<INFO> starting mbuf api tests\n" );
 	errors +=  mbuf_api_test( );
 	fprintf( stderr, "<INFO> error count: %d\n", errors );
 
 	if( errors == 0 ) {
-		fprintf( stderr, "<PASS> all tests were OK\n" );
+		fprintf( stderr, "<PASS> all tests were OK\n\n" );
 	} else {
-		fprintf( stderr, "<FAIL> %d modules reported errors\n", errors );
+		fprintf( stderr, "<FAIL> %d modules reported errors\n\n", errors );
 	}
 
 	return !!errors;
diff --git a/test/rt_nano_static_test.c b/test/rt_nano_static_test.c
index 49cff8a..d56f01f 100644
--- a/test/rt_nano_static_test.c
+++ b/test/rt_nano_static_test.c
@@ -231,7 +231,10 @@
 		}
 	}
 
-	uta_fib( "no-suhch-file" );			// drive some error checking for coverage
+	buf = uta_fib( "no-suhch-file" );			// drive some error checking for coverage
+	if( buf ) {
+		free( buf );
+	}
 
 	rt = uta_rt_init( );										// get us a route table
 	state = uta_link2( NULL );
diff --git a/test/rt_static_test.c b/test/rt_static_test.c
index 13a216b..6d416ef 100644
--- a/test/rt_static_test.c
+++ b/test/rt_static_test.c
@@ -79,6 +79,19 @@
 }
 
 /*
+	Builds a route table key.
+*/
+static uint64_t build_key( uint32_t mtype, uint32_t sid ) {
+	uint64_t k;
+
+	k = (uint64_t) sid << 32;
+	k += mtype;
+
+fprintf( stderr, "<INFO> build key: %x %x --> %llx\n", (int) mtype, (int) sid, (long long) k );
+	return k;
+}
+
+/*
 	This is the main route table test. It sets up a very specific table
 	for testing (not via the generic setup function for other test
 	situations).
@@ -87,7 +100,8 @@
 	uta_ctx_t* ctx;			// context needed to test load static rt
 	route_table_t* rt;		// route table
 	route_table_t* crt;		// cloned route table
-	rtable_ent_t*	rte;	// entry in the table
+	rtable_ent_t*	rte;	// route table entries from table
+	rtable_ent_t*	rte2;
 	endpoint_t*	ep;			// endpoint added
 	int more = 0;			// more flag from round robin
 	int errors = 0;			// number errors found
@@ -99,9 +113,9 @@
 	int value;
 	int alt_value;
 	ei_t	entries[50];	// end point information
-	int		gcounts[5];		// number of groups in this set
-	int		ecounts[5];		// number of elements per group
-	int		mtypes[5];		// msg type for each group set
+	int		gcounts[7];		// number of groups in this set
+	int		ecounts[7];		// number of elements per group
+	uint64_t	mtypes[7];	// mtype/sid 'key' in the modern RMR world
 	char*	tok;
 	char*	nxt_tok;
 	int		enu = 0;
@@ -113,40 +127,62 @@
 	setenv( "ENV_VERBOSE_FILE", ".ut_rmr_verbose", 1 );			// allow for verbose code in rtc to be driven
 	i = open( ".ut_rmr_verbose", O_RDWR | O_CREAT, 0644 );
 	if( i >= 0 ) {
-		write( 1, "2\n", 2 );
+		write( i, "2\n", 2 );
 		close( i );
 	}
 
-	gcounts[0] = 1;			// build entry info -- this is hackish, but saves writing another parser
+							
+	/*
+		The hacky code below calls the necessary rmr functions to create a route table
+		as though the following were read and parsed by the rmr functions. (This tests
+		the individual funcitons and avoids writing another parser, so it's not pretty.)
+	
+		mse | 0 | 0 | yahoo.com:4561,localhost:4562
+		mse | 1 | 0 | localhost:4560,localhost:4568,localhost:4569; localhost:4561,localhost:4562
+		mse | 2 | 0 | localhost:4563,localhost:4564
+		mse | 3 | 0 | localhost:4565
+		mse | 3 | 11 | locahost:5511
+		mse | 3 | -1 | localhost:5500
+	*/
+	gcounts[0] = 1;							// first entry has 1 group with 2 endpoints; message type 0, sid 0
 	ecounts[0] = 2;
-	mtypes[0] = 0;
+	mtypes[0] = build_key( 0, 0 );			// mtype is now a key of mtype/sid
 	entries[enu].group = 0; entries[enu].ep_name = "yahoo.com:4561"; enu++;		// use a dns resolvable name to test that
-	entries[enu].group = 0; entries[enu].ep_name = "localhost:4562"; enu++;	// rest can default to some dummy ip
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4562"; enu++;		// rest can default to some dummy ip
 
-	gcounts[1] = 2;
-	ecounts[1] = 3;
-	mtypes[1] = 1;
-	entries[enu].group = 0; entries[enu].ep_name = "localhost:4561"; enu++;
+	gcounts[1] = 2;				// 2 groups
+	ecounts[1] = 3;				// first has 3 endpoints
+	mtypes[1] = build_key( 1, 0 );
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4560"; enu++;
 	entries[enu].group = 0; entries[enu].ep_name = "localhost:4568"; enu++;
 	entries[enu].group = 0; entries[enu].ep_name = "localhost:4569"; enu++;
 
-	gcounts[2] = 0;		// 0 groups means use same rte, this is the next gropup
-	ecounts[2] = 2;
-	mtypes[2] = 1;
+	gcounts[2] = 0;					// 0 means use same rte, this is the next group for the entry
+	ecounts[2] = 2;					// 2 endpoints
+	mtypes[2] = 999;				// ignored when appending to previous entry
 	entries[enu].group = 1; entries[enu].ep_name = "localhost:4561"; enu++;
 	entries[enu].group = 1; entries[enu].ep_name = "localhost:4562"; enu++;
 
-	gcounts[3] = 1;		// 0 groups means use same rte, this is the next gropup
-	ecounts[3] = 2;
-	mtypes[3] = 2;
+	gcounts[3] = 1;					// next entry has 1 group
+	ecounts[3] = 2;					// with 2 enpoints
+	mtypes[3] = build_key( 2, 0 );
 	entries[enu].group = 0; entries[enu].ep_name = "localhost:4563"; enu++;
 	entries[enu].group = 0; entries[enu].ep_name = "localhost:4564"; enu++;
 
-	gcounts[4] = 1;		// 0 groups means use same rte, this is the next gropup
+	gcounts[4] = 1;					// three entries for mt==3 with different sids
 	ecounts[4] = 1;
-	mtypes[4] = 3;
-	entries[enu].group = 0; entries[enu].ep_name = "localhost:4565"; enu++;
+	mtypes[4] = build_key( 3, 0 );
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:5500"; enu++;
 
+	gcounts[5] = 1;
+	ecounts[5] = 1;
+	mtypes[5] = build_key( 3, 11 );
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:5511"; enu++;
+
+	gcounts[6] = 1;
+	ecounts[6] = 1;
+	mtypes[6] = build_key( 3, -1 );
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:5512"; enu++;
 
 
 	rt = uta_rt_init( );										// get us a route table
@@ -178,6 +214,9 @@
 		}
 	}
 
+	// ----- end hacking together a route table ---------------------------------------------------
+
+
 	crt = uta_rt_clone( rt );								// clone only the endpoint entries
 	errors += fail_if_nil( crt, "cloned route table" );
 	if( crt ) {
@@ -216,31 +255,70 @@
 	//alt_value = uta_epsock_byname( rt, "localhost:4562" );			// we might do a memcmp on the two structs, but for now nothing
 	//errors += fail_if_equal( value, alt_value, "app1/app2 sockets" );
 
+
+	// --- test that the get_rte function finds expected keys, and retries to find 'bad' sid attempts for valid mtypes with no sid
+	rte = uta_get_rte( rt, 0, 1, TRUE );			// s=0 m=1 is defined, so this should return a pointer
+	errors += fail_if_nil( rte, "get_rte did not return a pointer when s=0 m=1 true given" );
+
+	rte = uta_get_rte( rt, 0, 1, FALSE );			// the retry shouldn't apply, but ensure it does the righ thing
+	errors += fail_if_nil( rte, "get_rte did not return a pointer when s=0 m=1 false given" );
+
+	rte = uta_get_rte( rt, 1000, 1, FALSE );		// s=1000 does not exist for any msg type; should return nil as not allowed to drop sid
+	errors += fail_not_nil( rte, "get_rte returned a pointer when s=1000 m=1 false given" );
+
+	rte = uta_get_rte( rt, 1000, 1, TRUE );			// this should also fail as there is no mt==1 sid==-1 defined
+	errors += fail_not_nil( rte, "get_rte returned a pointer when s=1000 m=1 true given" );
+
+	rte = uta_get_rte( rt, 0, 3, TRUE );			// mtype sid combo does exist; true/false should not matter
+	errors += fail_if_nil( rte, "get_rte did not return a pointer when s=0 m=3 true given" );
+
+	rte2 = uta_get_rte( rt, 11, 3, TRUE );			// same mtype as before, different (valid) group, rte should be different than before
+	errors += fail_if_nil( rte2, "get_rte did not return a pointer when s=11 m=3 true given" );
+	errors += fail_if_true( rte == rte2, "get_rte for mtype==3 and different sids (0 and 11) returned the same rte pointer" );
+
+	rte2 = uta_get_rte( rt, 0, 3, FALSE );			// since the mtype/sid combo exists, setting false should return the same as before
+	errors += fail_if_nil( rte2, "get_rte did not return a pointer when s=0 m=3 false given" );
+	errors += fail_if_false( rte == rte2, "get_rte did not return same pointer when mtype/sid combo given with different true/false" );
+
+	rte = uta_get_rte( rt, 12, 3, FALSE );			// this combo does not exist and should fail when alt-key is not allowed (false)
+	errors += fail_not_nil( rte, "get_rte returned a pointer for s=12, m=3, false" );
+
+	rte = uta_get_rte( rt, 12, 3, TRUE );			// this should return the entry for the 3/-1 combination 
+	errors += fail_if_nil( rte, "get_rte did not return a pointer for s=12, m=3, true" );
+
 	alt_value = -1;
-	for( i = 0; i < 10; i++ ) {										// round robin return value should be different each time
-		value = uta_epsock_rr( rt, 1, 0, &more, &nn_sock );			// msg type 1, group 1
-		errors += fail_if_equal( value, alt_value, "round robiin sockets with multiple end points" );
-		errors += fail_if_false( more, "more for mtype==1" );
-		alt_value = value;
+	rte = uta_get_rte( rt, 0, 1, FALSE );			// get an rte for the next loop
+	if( rte ) {
+		for( i = 0; i < 10; i++ ) {									// round robin return value should be different each time
+			value = uta_epsock_rr( rte, 0, &more, &nn_sock );			// msg type 1, group 1
+			errors += fail_if_equal( value, alt_value, "round robiin sockets with multiple end points" );
+			errors += fail_if_false( more, "more for mtype==1" );
+			alt_value = value;
+		}
 	}
 
 	more = -1;
-	for( i = 0; i < 10; i++ ) {							// this mtype has only one endpoint, so rr should be same each time
-		value = uta_epsock_rr( rt, 3, 0, NULL, &nn_sock );		// also test ability to deal properly with nil more pointer
-		if( i ) {
-			errors += fail_not_equal( value, alt_value, "round robin sockets with one endpoint" );
-			errors += fail_not_equal( more, -1, "more value changed in single group instance" );
+	rte = uta_get_rte( rt, 0, 3, FALSE );				// get an rte for the next loop
+	if( rte ) {
+		for( i = 0; i < 10; i++ ) {								// this mtype has only one endpoint, so rr should be same each time
+			value = uta_epsock_rr( rte, 0, NULL, &nn_sock );		// also test ability to deal properly with nil more pointer
+			if( i ) {
+				errors += fail_not_equal( value, alt_value, "round robin sockets with one endpoint" );
+				errors += fail_not_equal( more, -1, "more value changed in single group instance" );
+			}
+			alt_value = value;
 		}
-		alt_value = value;
 	}
 
-	value = uta_epsock_rr( rt, 9, 0, &more, &nn_sock );			// non-existant message type; should return false (0)
-	errors += fail_not_equal( value, 0, "socket for bad mtype was valid" );
+	rte = uta_get_rte( rt, 11, 3, TRUE );
+	state = uta_epsock_rr( rte, 22, NULL, NULL );
+	errors += fail_if_true( state, "uta_epsock_rr returned bad (non-zero) state when given nil socket pointer" );
 
 	uta_rt_clone( NULL );								// verify null parms don't crash things
 	uta_rt_drop( NULL );
-	uta_epsock_rr( NULL, 1, 0, &more, &nn_sock );		// drive null case for coverage
+	uta_epsock_rr( NULL, 0,  &more, &nn_sock );			// drive null case for coverage
 	uta_add_rte( NULL, 99, 1 );
+	uta_get_rte( NULL, 0, 1000, TRUE );
 
 	fprintf( stderr, "[INFO] test: adding end points with nil data; warnings expected\n" );
 	uta_add_ep( NULL, NULL, "foo", 1 );
@@ -258,25 +336,26 @@
 	}
 
 	uta_rt_drop( rt );
+	rt = NULL;
 
 	if( (ctx = (uta_ctx_t *) malloc( sizeof( uta_ctx_t ) )) != NULL ) {
 		memset( ctx, 0, sizeof( *ctx ) );
 
 		if( (seed_fname = getenv( "RMR_SEED_RT" )) != NULL ) {
-			if( ! (fail_if_nil( rt, "pointer to rt for load test" )) ) {
-				errors++;
-				read_static_rt( ctx, 0 );
-				unsetenv( "RMR_SEED_RT" );			// unset to test the does not exist condition
-				read_static_rt( ctx, 0 );
-			} else {
-				fprintf( stderr, "<FAIL> cannot gen rt for load test\n" );
-			}
-		} else {
-			read_static_rt( ctx, 0 );		// not defined, just drive for that one case
+			read_static_rt( ctx, 0 );
+			rt = ctx->rtable;
+			errors += fail_if_nil( rt, "read seed table didn't generate a rtable pointer in context" );
+			unsetenv( "RMR_SEED_RT" );				// remove for next test
 		}
+
+		read_static_rt( ctx, 0 );			// drive for not there coverage
 	}
 
-	uta_fib( "no-suhch-file" );			// drive some error checking for coverage
+
+	buf = uta_fib( "no-suhch-file" );			// drive some error checking for coverage
+	if( buf ) {
+		free( buf );
+	}
 
 
 	ep = (endpoint_t *) malloc( sizeof( *ep ) );
@@ -286,12 +365,6 @@
 	state = uta_link2( ep );
 	errors += fail_if_true( state, "link2 did not return false when given nil pointers" );
 
-	state = uta_epsock_rr( rt, 122, 0, NULL, NULL );
-	errors += fail_if_true( state, "uta_epsock_rr returned bad state when given nil socket pointer" );
-
-	rt = uta_rt_init( );										// get us a route table
-	state = uta_epsock_rr( rt, 0, -1, NULL, &nn_sock );
-	errors += fail_if_true( state, "uta_epsock_rr returned bad state (true) when given negative group number" );
 
 	return !!errors;			// 1 or 0 regardless of count
 }
diff --git a/test/sr_nng_static_test.c b/test/sr_nng_static_test.c
index 294207c..7c44432 100644
--- a/test/sr_nng_static_test.c
+++ b/test/sr_nng_static_test.c
@@ -219,6 +219,5 @@
 	rtc( NULL );				// coverage test with nil pointer
 	rtc( ctx );
 
-
 	return !!errors;
 }
diff --git a/test/test_nng_em.c b/test/test_nng_em.c
index 88f5966..8c6a9ca 100644
--- a/test/test_nng_em.c
+++ b/test/test_nng_em.c
@@ -320,10 +320,10 @@
 	return return_value;
 }
 static int em_nng_recv(nng_socket s, void * v, size_t * t, int i ) {
-
 	return return_value;
 }
 static int em_nng_send( nng_socket s, void* m, int l, int f ) {
+	free( m );					// we must ditch the message as nng does (or reuses)
 	return return_value;
 }
 
@@ -365,13 +365,11 @@
 */
 static void em_nng_free( void* p, size_t l ) {
 	if( p ) {
-		//fprintf( stderr, ">>>>> not freed: %p\n", p );
 		free( p );
 	}
 }
 static void em_nng_msg_free( void* p ) {
 	if( p ) {
-		//fprintf( stderr, ">>>>> not freed: %p\n", p );
 		free( p );
 	}
 }
@@ -542,6 +540,7 @@
 }
 
 static void em_nn_freemsg( void* ptr ) {
+	free( ptr );
 	return;
 }
 
diff --git a/test/tools_static_test.c b/test/tools_static_test.c
index 9e156ec..64da983 100644
--- a/test/tools_static_test.c
+++ b/test/tools_static_test.c
@@ -107,12 +107,15 @@
 	hname = uta_h2ip( "192.168.1.2" );
 	errors += fail_not_equal( strcmp( hname, "192.168.1.2" ), 0, "h2ip did not return IP address when given address" );
 	errors += fail_if_nil( hname, "h2ip did not return a pointer" );
+	free( hname );
 
 	hname = uta_h2ip( "yahoo.com" );
 	errors += fail_if_nil( hname, "h2ip did not return a pointer" );
+	free( hname );
 
 	hname = uta_h2ip( "yahoo.com:1234" );							// should ignore the port
 	errors += fail_if_nil( hname, "h2ip did not return a pointer" );
+	free( hname );
 
 	// ------------ rtg lookup test -------------------------------------------------------------
 	ctx.rtg_port = 0;
@@ -188,6 +191,7 @@
 
 	ip = get_default_ip( if_list );
 	if( ip ) {
+		free( ip );
 	} else {
 		errors += fail_if_nil( ip, "get_defaul_ip returned nil pointer when valid pointer expected" );
 	}
@@ -203,6 +207,8 @@
 		} else {
 			errors += fail_if_nil( if_list, "mk_ip_list with a specific interface name returned a nil list" );
 		}
+
+		free( ip );
 	}
 
 // -------------------------------------------------------------------------------------------------
diff --git a/test/unit_test.ksh b/test/unit_test.ksh
index f74e438..214f94d 100755
--- a/test/unit_test.ksh
+++ b/test/unit_test.ksh
@@ -335,6 +335,8 @@
 replace_flags=1				# replace ##### in gcov for discounted lines
 run_nano_tests=0
 
+export RMR_WARNING=1		# turn on warnings
+
 while [[ $1 == "-"* ]]
 do
 	case $1 in