Allow endpoint selection based on meid in message 59/1959/1 1.13.0
authorE. Scott Daniels <daniels@research.att.com>
Wed, 4 Dec 2019 20:20:16 +0000 (15:20 -0500)
committerE. Scott Daniels <daniels@research.att.com>
Fri, 6 Dec 2019 20:06:43 +0000 (15:06 -0500)
This change allows the route table to specify message types which
should route based on the MEID (managed equipment ID) in the message
buffer rather than a round-robin list. The special group entry of
%meid in the routing table causes this, and requires the route
generator to suppy RMR with a map of endpoints which currently
manage each ME.

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

19 files changed:
CHANGES
CMakeLists.txt
doc/src/generic_ps.im
doc/src/man/rmr.7.xfm
doc/src/rtd/README
docs/config-deploy.rst
docs/overview.rst
src/rmr/common/include/rmr_agnostic.h
src/rmr/common/src/mbuf_api.c
src/rmr/common/src/rt_generic_static.c
src/rmr/common/src/rtc_static.c
src/rmr/nng/src/rtable_nng_static.c
src/rmr/nng/src/sr_nng_static.c
test/mbuf_api_static_test.c
test/rmr_nng_test.c
test/rt_static_test.c
test/sr_nng_static_test.c
test/test_gen_rt.c [new file with mode: 0644]
test/unit_test.ksh

diff --git a/CHANGES b/CHANGES
index 87fbddc..890a1a2 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,14 +2,19 @@
 API and build change  and fix summaries. Doc correctsions
 and/or changes are not mentioned here; see the commit messages.
 
-2019 November 14; version 1.11.1
+2019 December 6; version 1.13.0
+       Add ability to route messages based on the MEID in a message combined
+       with the message type/subscription-ID.
+
+
+2019 November 14; version 1.11.1 (Amber)
        Fix bug in payload reallocation function; correct length of payload
        was not always copied.
 
 2019 November 13; version 1.12.1
        New message type constants added to support A1.
 
-2019 November 4; version 1.11.0
+2019 November 4; version 1.11.0 (Amber)
        Version bump to move away from the 1.10.* to distinguish between
        release A and the trial.
 
index d2e5b13..6f5a78c 100644 (file)
@@ -35,8 +35,8 @@ project( rmr LANGUAGES C )
 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 "12" )
-set( patch_level "1" )
+set( minor_version "13" )
+set( patch_level "0" )
 
 set( install_root "${CMAKE_INSTALL_PREFIX}" )
 set( install_inc "include/rmr" )
index 9020b91..bd11587 100644 (file)
@@ -63,9 +63,9 @@
 
        .dv beg_dlist .bd $1 $2
        .dv end_dlist .ed
-       .dv ditem .cc 2 .di $1 ^:
-       .dv di .cc 2 .di $1 ^:
-       .dv li .cc 2 .li
+       .dv ditem .cc 2 .sp .5 .di $1 ^:
+       .dv di .cc 2 .sp .5 .di $1 ^:
+       .dv li .cc 2 .sp .5 .li
        .dv item .cc 2 .li
        
        .dv proto_start .sp 1 .cc .5i .st 9 .sf Courier-bold .nf
index 2f2e47d..ae49977 100644 (file)
@@ -53,7 +53,7 @@ response relationship when needed.
 
 
 &h3(The Route Table)
-The library is supplied with a route table which maps message numbers to
+The library must be given a route table which maps message numbers to
 endpoint groups such that each time a message of type T is sent, the message
 is delivered to one member of each group associated with T.
 For example, message type 2 might route to two different groups where
@@ -66,6 +66,99 @@ belong to which groups, and which groups accept which message types.
 Once understood, the route table generator publishes a table that is ingested
 by RMr and used for mapping messages to end points.
 
+.sp
+The following is a simple route table which causes message types 0 through 9 to
+be routed to specific applications:
+
+&ex_start
+newrt|start
+   mse|0|-1| %meid
+   mse|1|-1|app10:4560,app11:4560
+   mse|2|-1|app12:4560
+   mse|3|-1|app14:4560
+   mse|4|-1|app18:4560
+   mse|5|-1|app01:4560
+   mse|6|-1|app02:4560
+   mse|7|-1|app03:4560
+   mse|8|-1|app04:4560
+   mse|9|-1|app05:4560
+newrt|end
+&ex_end
+&space
+The special endpoint "%meid" indicates that the message type (0 in this case) is
+to be routed to the endpoint which has been listed as the "owner" for the meid
+appearing in the message.
+MEID ownership is communicated to RMR using the same Route Table Manager interface
+and by supplying a "table" such as the one below:
+
+&ex_start
+meid_map | start
+   mme_ar | control1 | meid000 meid001 meid002 meid003 meid004 meid005
+   mme_ar | control2 | meid100 meid101 meid102 meid103 
+meid_map | end | 2
+&ex_end
+
+This table indicates that the application (endpoint) &ital(control1) "owns" 6 MEIDs
+and &ital(control2) owns 4.  
+When message type 0 is sent, the MEID in the message will be used to select the 
+endpoint via this table. 
+
+&space
+The MEID table will update the existing owner relationships, and add new ones; it
+is necessary to send only the changes with the add/replace (mme_ar) entries in
+the table. 
+When necessary, MEIDs can be deleted by adding an &cw(mme_del) record to the table.
+The following example illustrates how this might look:
+
+&ex_start
+meid_map | start
+   mme_ar | control1 | meid000 meid001 meid002 meid003 meid004 meid005
+   mme_ar | control2 | meid100 meid101 meid102 meid103 
+   mme_del| meid200 meid401
+meid_map | end | 1
+&ex_end
+
+&h3(Route Table Syntax)
+The following illustrates the syntax for both the route table.
+
+&space
+&ex_start
+newrt | start
+mse | <message-type>[,<sender-endpoint>] | <sub-id> <roud-robin-grp>[;<round-robin-grp>]...
+newrt | end
+&ex_end
+&space
+A round robin group is one or more endpoints from which one will be selected to receive
+the message. 
+When multiple endpoints are given in a group, they must be separated with a comma.
+An endpoint is the IP address and port (e.g. 192.158.4.30:8219) or DNS name and port of the
+application that should receive the message type.
+If multiple round-robin groups are given, they must be separated by a semicolon, and 
+
+&h3(MEID Map Syntax)
+The MEID map is similar to the route table. 
+Entries are used to add or replace the ownership of one or more MEIDs (mme_ar) or to 
+delete one or more MEIDs (mme_del).
+The following is the syntax for the MEID map.
+
+&space
+&ex_start
+meid_map | start
+mme_ar | <owner-endpoint> | <meid> [<meid>...]
+mme_del | <meid> [<meid>...]
+meid_map | end | <count>
+&ex_end
+
+&space
+The <count> on the end record indicates the number of mme_ar and mme_del records
+which were sent; if the count does not match the whole map is refused and dropped.
+The <owner-endpoint> is the endpoint which should receive the message when a message
+is routed based on the MEID it contains.
+A MEID may be "owned" by only one endpoint, and if supplied multiple times, the last
+observed relationship is used.
+Each of the lists of MEIDs are blank separated.
+
+
 &h3(Environment)
 To enable configuration of the library behaviour outside of direct user application
 control, RMr supports a number of environment variables which provide information
@@ -73,6 +166,7 @@ to the library.
 The following is a list of the various environment variables, what they control
 and the defaults which RMr uses if undefined.
 
+&space
 .** the list of environment vars supported
 .im &{lib}/man/env_var_list.im
 
index c7dd309..b491113 100644 (file)
@@ -6,7 +6,9 @@ it is not good practice, the generated .rst files must be checked into
 the repo and committed with changes to the source.  
 
 The command 'make all' should be all that is needed to build the 
-rtd documentation.
+rtd documentation. Follow that with 'make publish' to actually move
+the .rst files into the docs directory at the root; only the changed
+files are moved.
 
 Rationale
 Documentation is just code, and by maintaining the documentation as
index 1b56489..d6ae082 100644 (file)
@@ -71,7 +71,10 @@ RMR_SEED_RT
   used for debugging, testing, or if no route table 
   generator process is being used to supply the route table. 
   If not defined, no static table is used and RMr will not 
-  report *ready* until a table is received. 
+  report *ready* until a table is received. The static route 
+  table may contain both the route table (between newrt 
+  start and end records), and the MEID map (between meid_map 
+  start and end records) 
  
 RMR_SRC_ID 
    
index 65360bf..d382e69 100644 (file)
@@ -44,7 +44,7 @@ request response, or call response relationship when needed.
 The Route Table 
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
  
-The library is supplied with a route table which maps message 
+The library must be given a route table which maps message 
 numbers to endpoint groups such that each time a message of 
 type T is sent, the message is delivered to one member of 
 each group associated with T. For example, message type 2 
@@ -57,6 +57,113 @@ accept which message types. Once understood, the route table
 generator publishes a table that is ingested by RMr and used 
 for mapping messages to end points. 
  
+The following is a simple route table which causes message 
+types 0 through 9 to be routed to specific applications: 
+:: 
+  
+ newrt|start
+    mse|0|-1| %meid
+    mse|1|-1|app10:4560,app11:4560
+    mse|2|-1|app12:4560
+    mse|3|-1|app14:4560
+    mse|4|-1|app18:4560
+    mse|5|-1|app01:4560
+    mse|6|-1|app02:4560
+    mse|7|-1|app03:4560
+    mse|8|-1|app04:4560
+    mse|9|-1|app05:4560
+ newrt|end
+The special endpoint "%meid" indicates that the message type 
+(0 in this case) is to be routed to the endpoint which has 
+been listed as the "owner" for the meid appearing in the 
+message. MEID ownership is communicated to RMR using the same 
+Route Table Manager interface and by supplying a "table" such 
+as the one below: 
+:: 
+  
+ meid_map | start
+    mme_ar | control1 | meid000 meid001 meid002 meid003 meid004 meid005
+    mme_ar | control2 | meid100 meid101 meid102 meid103 
+ meid_map | end | 2
+This table indicates that the application (endpoint) 
+*control1* "owns" 6 MEIDs and *control2* owns 4. When message 
+type 0 is sent, the MEID in the message will be used to 
+select the endpoint via this table. 
+The MEID table will update the existing owner relationships, 
+and add new ones; it is necessary to send only the changes 
+with the add/replace (mme_ar) entries in the table. When 
+necessary, MEIDs can be deleted by adding an mme_del record 
+to the table. The following example illustrates how this 
+might look: 
+:: 
+  
+ meid_map | start
+    mme_ar | control1 | meid000 meid001 meid002 meid003 meid004 meid005
+    mme_ar | control2 | meid100 meid101 meid102 meid103 
+    mme_del| meid200 meid401
+ meid_map | end | 1
+Route Table Syntax 
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
+The following illustrates the syntax for both the route 
+table. 
+:: 
+  
+ newrt | start
+ mse | <message-type>[,<sender-endpoint>] | <sub-id> <roud-robin-grp>[;<round-robin-grp>]...
+ newrt | end
+A round robin group is one or more endpoints from which one 
+will be selected to receive the message. When multiple 
+endpoints are given in a group, they must be separated with a 
+comma. An endpoint is the IP address and port (e.g. 
+192.158.4.30:8219) or DNS name and port of the application 
+that should receive the message type. If multiple round-robin 
+groups are given, they must be separated by a semicolon, and 
+MEID Map Syntax 
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
+The MEID map is similar to the route table. Entries are used 
+to add or replace the ownership of one or more MEIDs (mme_ar) 
+or to delete one or more MEIDs (mme_del). The following is 
+the syntax for the MEID map. 
+:: 
+  
+ meid_map | start
+ mme_ar | <owner-endpoint> | <meid> [<meid>...]
+ mme_del | <meid> [<meid>...]
+ meid_map | end | <count>
+The <count> on the end record indicates the number of mme_ar 
+and mme_del records which were sent; if the count does not 
+match the whole map is refused and dropped. The 
+<owner-endpoint> is the endpoint which should receive the 
+message when a message is routed based on the MEID it 
+contains. A MEID may be "owned" by only one endpoint, and if 
+supplied multiple times, the last observed relationship is 
+used. Each of the lists of MEIDs are blank separated. 
 Environment 
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
  
@@ -68,6 +175,7 @@ variables, what they control and the defaults which RMr uses
 if undefined. 
  
  
 RMR_ASYNC_CONN 
    
   Allows the asynch connection mode to be turned off (by 
@@ -108,7 +216,10 @@ RMR_SEED_RT
   used for debugging, testing, or if no route table 
   generator process is being used to supply the route table. 
   If not defined, no static table is used and RMr will not 
-  report *ready* until a table is received. 
+  report *ready* until a table is received. The static route 
+  table may contain both the route table (between newrt 
+  start and end records), and the MEID map (between meid_map 
+  start and end records) 
  
 RMR_SRC_ID 
    
index a20e2e7..df4a316 100644 (file)
@@ -44,12 +44,19 @@ typedef struct uta_ctx  uta_ctx_t;
 #define QUOTE_DEF(a) QUOTE(a)  // allow a #define value to be quoted (e.g. QUOTE(MAJOR_VERSION) )
 
 
+#define RT_SIZE                        10009   // primary entries in route table (prime) meids hash through this so larger than expected # meids
+                                                               // space deginations in the hash table
+#define RT_MT_SPACE    0               // (integer) message type as the key
+#define RT_NAME_SPACE  1               // enpoint name/address is the key
+#define RT_ME_SPACE    2               // message id is the key
+
 #define RMR_MSG_VER    3                       // message version this code was designed to handle
 
                                                                                        // environment variable names we'll suss out
 #define ENV_BIND_IF "RMR_BIND_IF"                      // the interface to bind to for both normal comma and RTG (0.0.0.0 if missing)
 #define ENV_RTG_PORT "RMR_RTG_SVC"                     // the port we'll listen on for rtg connections
 #define ENV_SEED_RT    "RMR_SEED_RT"                   // where we expect to find the name of the seed route table
+#define ENV_SEED_MEMAP "RMR_SEED_MEMAP"        // where we expect to find the name of the seed route table
 #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
@@ -197,7 +204,7 @@ typedef struct {
        uint64_t key;                   // key used to reinsert this entry into a new symtab
        int     refs;                           // number of symtabs which reference the entry
        int mtype;                              // the message type for this list
-       int     nrrgroups;                      // number of rr groups to send to
+       int     nrrgroups;                      // number of rr groups to send to (if 0, the meid in a message determines endpoint)
        rrgroup_t**     rrgroups;       // one or more set of endpoints to round robin messages to
 } rtable_ent_t;
 
@@ -207,6 +214,7 @@ typedef struct {
 typedef struct {
        void*   hash;                   // hash table.
        int             updates;                // counter of update records received
+       int             mupdates;               // counter of meid update records received
 } route_table_t;
 
 /*
index f730958..c879d7c 100644 (file)
                EINVAL id poitner, buf or buf header are bad.
                EOVERFLOW if the bytes given would have overrun
 
+       We have been told that the meid will be a string, so we enforce that even if
+       the user is copying in bytes.  We will add a 0 byte at len+1 when len is less
+       than the field size, or as the last byte (doing damage to their string) if
+       the caller didn't play by the rules. If they pass a non-nil terminated set
+       of bytes which are the field length, we'll indicate truncation.
+
 */
 extern int rmr_bytes2meid( rmr_mbuf_t* mbuf, unsigned char const* src, int len ) {
        uta_mhdr_t* hdr;
@@ -66,11 +72,20 @@ extern int rmr_bytes2meid( rmr_mbuf_t* mbuf, unsigned char const* src, int len )
        if( len > RMR_MAX_MEID ) {
                len = RMR_MAX_MEID;
                errno = EOVERFLOW;
-       }
+       } 
 
        hdr = (uta_mhdr_t *) mbuf->header;
        memcpy( hdr->meid, src, len );
 
+       if( len == RMR_MAX_MEID ) {
+               if( *(hdr->meid+len-1) != 0 ) {
+                       *(hdr->meid+len-1) = 0;
+                       errno = EOVERFLOW;
+               }
+       } else {
+               *(hdr->meid+len) = 0;
+       }
+
        return len;
 }
 
index d65acdf..5a2b1fa 100644 (file)
@@ -55,6 +55,7 @@ typedef struct thing_list {
        int nalloc;
        int nused;
        void** things;
+       const char** names;
 } thing_list_t;
 
 // ---- debugging/testing -------------------------------------------------------------------------
@@ -75,7 +76,26 @@ static void ep_stats( void* st, void* entry, char const* name, void* thing, void
                (*counter)++;
        }
 
-       fprintf( stderr, "[DBUG] RMR sends: target=%s open=%d\n", ep->name, ep->open );
+       fprintf( stderr, "[DBUG] RMR rt endpoint: target=%s open=%d\n", ep->name, ep->open );
+}
+
+/*
+       Called to count meid entries in the table. The meid points to an 'owning' endpoint
+       so we can list what we find
+*/
+static void meid_stats( void* st, void* entry, char const* name, void* thing, void* vcounter ) {
+       int*    counter;
+       endpoint_t* ep;
+
+       if( (ep = (endpoint_t *) thing) == NULL ) {
+               return;
+       }
+
+       if( (counter = (int *) vcounter) != NULL ) {
+               (*counter)++;
+       }
+
+       fprintf( stderr, "[DBUG] RMR meid=%s owner=%s open=%d\n", name, ep->name, ep->open );
 }
 
 /*
@@ -94,14 +114,14 @@ static void ep_counts( void* st, void* entry, char const* name, void* thing, voi
                id = "missing";
        }
 
-       fprintf( stderr, "[INFO] RMR sends: ts=%lld src=%s target=%s open=%d succ=%lld fail=%lld (hard=%lld soft=%lld)\n", 
-               (long long) time( NULL ), 
-               id, 
-               ep->name, 
-               ep->open, 
-               ep->scounts[EPSC_GOOD], 
-               ep->scounts[EPSC_FAIL] + ep->scounts[EPSC_TRANS], 
-               ep->scounts[EPSC_FAIL], 
+       fprintf( stderr, "[INFO] RMR sends: ts=%lld src=%s target=%s open=%d succ=%lld fail=%lld (hard=%lld soft=%lld)\n",
+               (long long) time( NULL ),
+               id,
+               ep->name,
+               ep->open,
+               ep->scounts[EPSC_GOOD],
+               ep->scounts[EPSC_FAIL] + ep->scounts[EPSC_TRANS],
+               ep->scounts[EPSC_FAIL],
                ep->scounts[EPSC_TRANS]   );
 }
 
@@ -125,7 +145,7 @@ static void rte_stats( void* st, void* entry, char const* name, void* thing, voi
        mtype = rte->key & 0xffff;
        sid = (int) (rte->key >> 32);
 
-       fprintf( stderr, "[DBUG] rte: key=%016lx mtype=%4d sid=%4d nrrg=%2d refs=%d\n", rte->key, mtype, sid, rte->nrrgroups, rte->refs );
+       fprintf( stderr, "[DBUG] RMR rte: key=%016lx mtype=%4d sid=%4d nrrg=%2d refs=%d\n", rte->key, mtype, sid, rte->nrrgroups, rte->refs );
 }
 
 /*
@@ -141,13 +161,20 @@ static void  rt_stats( route_table_t* rt ) {
 
        counter = (int *) malloc( sizeof( int ) );
        *counter = 0;
-       fprintf( stderr, "[DBUG] rtstats:\n" );
-       rmr_sym_foreach_class( rt->hash, 1, ep_stats, counter );                // run endpoints in the active table
-       fprintf( stderr, "[DBUG] %d endpoints\n", *counter );
+       fprintf( stderr, "[DBUG] RMR route table stats:\n" );
+       fprintf( stderr, "[DBUG] RMR route table endpoints:\n" );
+       rmr_sym_foreach_class( rt->hash, RT_NAME_SPACE, ep_stats, counter );            // run endpoints (names) in the active table
+       fprintf( stderr, "[DBUG] RMR rtable: %d known endpoints\n", *counter );
+
+       fprintf( stderr, "[DBUG] RMR route table entries:\n" );
+       *counter = 0;
+       rmr_sym_foreach_class( rt->hash, RT_MT_SPACE, rte_stats, counter );                     // run message type entries
+       fprintf( stderr, "[DBUG] RMR rtable: %d mt entries in table\n", *counter );
 
+       fprintf( stderr, "[DBUG] RMR route table meid map:\n" );
        *counter = 0;
-       rmr_sym_foreach_class( rt->hash, 0, rte_stats, counter );               // run entries
-       fprintf( stderr, "[DBUG] %d entries\n", *counter );
+       rmr_sym_foreach_class( rt->hash, RT_ME_SPACE, meid_stats, counter );            // run meid space
+       fprintf( stderr, "[DBUG] RMR rtable: %d meids in map\n", *counter );
 
        free( counter );
 }
@@ -204,7 +231,6 @@ static char* clip( char* buf ) {
 static char* ensure_nlterm( char* buf ) {
        char*   nb = NULL;
        int             len = 1;
-       
 
        nb = buf;
        if( buf == NULL || (len = strlen( buf )) < 2 ) {
@@ -219,7 +245,7 @@ static char* ensure_nlterm( char* buf ) {
                                memcpy( nb, buf, len );
                                *(nb+len) = '\n';                       // insert \n and nil into the two extra bytes we allocated
                                *(nb+len+1) = 0;
-                       }       
+                       }
 
                        free( buf );
                }
@@ -249,16 +275,21 @@ static rtable_ent_t* uta_add_rte( route_table_t* rt, uint64_t key, int nrrgroups
        rte->refs = 1;
        rte->key = key;
 
-       if( nrrgroups <= 0 ) {
+       if( nrrgroups < 0 ) {           // zero is allowed as %meid entries have no groups
                nrrgroups = 10;
        }
 
-       if( (rte->rrgroups = (rrgroup_t **) malloc( sizeof( rrgroup_t * ) * nrrgroups )) == NULL ) {
-               fprintf( stderr, "rmr_add_rte: malloc failed for rrgroup array\n" );
-               free( rte );
-               return NULL;
+       if( nrrgroups ) {
+               if( (rte->rrgroups = (rrgroup_t **) malloc( sizeof( rrgroup_t * ) * nrrgroups )) == NULL ) {
+                       fprintf( stderr, "rmr_add_rte: malloc failed for rrgroup array\n" );
+                       free( rte );
+                       return NULL;
+               }
+               memset( rte->rrgroups, 0, sizeof( rrgroup_t *) * nrrgroups );
+       } else {
+               rte->rrgroups = NULL;
        }
-       memset( rte->rrgroups, 0, sizeof( rrgroup_t *) * nrrgroups );
+
        rte->nrrgroups = nrrgroups;
 
        if( (old_rte = rmr_sym_pull( rt->hash, key )) != NULL ) {
@@ -272,7 +303,7 @@ static rtable_ent_t* uta_add_rte( route_table_t* rt, uint64_t key, int nrrgroups
 }
 
 /*
-       This accepts partially parsed information from a record sent by route manager or read from
+       This accepts partially parsed information from an rte or mse record sent by route manager or read from
        a file such that:
                ts_field is the msg-type,sender field
                subid is the integer subscription id
@@ -304,29 +335,32 @@ static void build_entry( uta_ctx_t* ctx, char* ts_field, uint32_t subid, char* r
                (uta_has_str( ts_field,  ctx->my_name, ',', 127) >= 0) ||               // our name is in the list
                has_myip( ts_field, ctx->ip_list, ',', 127 ) ) {                                // the list has one of our IP addresses
 
-                       key = build_rt_key( subid, atoi( ts_field ) );
-
-                       if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] create rte for mtype=%s subid=%d key=%lx\n", ts_field, subid, key );
+               key = build_rt_key( subid, atoi( ts_field ) );
 
-                       if( (ngtoks = uta_tokenise( rr_field, gtokens, 64, ';' )) > 0 ) {                                       // split round robin groups
-                               rte = uta_add_rte( ctx->new_rtable, key, ngtoks );                                                              // get/create entry for this key
+               if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] create rte for mtype=%s subid=%d key=%lx\n", ts_field, subid, key );
 
-                               for( grp = 0; grp < ngtoks; grp++ ) {
-                                       if( (ntoks = uta_rmip_tokenise( gtokens[grp], ctx->ip_list, tokens, 64, ',' )) > 0 ) {          // remove any referneces to our ip addrs
-                                               for( i = 0; i < ntoks; i++ ) {
-                                                       if( strcmp( tokens[i], ctx->my_name ) != 0 ) {                                  // don't add if it is us -- cannot send to ourself
-                                                               if( DEBUG > 1  || (vlevel > 1)) fprintf( stderr, "[DBUG] add endpoint  ts=%s %s\n", ts_field, tokens[i] );
-                                                               uta_add_ep( ctx->new_rtable, rte, tokens[i], grp );
-                                                       }
+               if( (ngtoks = uta_tokenise( rr_field, gtokens, 64, ';' )) > 0 ) {                                       // split round robin groups
+                       if( strcmp( gtokens[0], "%meid" ) == 0 ) {
+                               ngtoks = 0;                                                                                                                                     // special indicator that uses meid to find endpoint, no rrobin
+                       }
+                       rte = uta_add_rte( ctx->new_rtable, key, ngtoks );                                                              // get/create entry for this key
+
+                       for( grp = 0; grp < ngtoks; grp++ ) {
+                               if( (ntoks = uta_rmip_tokenise( gtokens[grp], ctx->ip_list, tokens, 64, ',' )) > 0 ) {          // remove any referneces to our ip addrs
+                                       for( i = 0; i < ntoks; i++ ) {
+                                               if( strcmp( tokens[i], ctx->my_name ) != 0 ) {                                  // don't add if it is us -- cannot send to ourself
+                                                       if( DEBUG > 1  || (vlevel > 1)) fprintf( stderr, "[DBUG] add endpoint  ts=%s %s\n", ts_field, tokens[i] );
+                                                       uta_add_ep( ctx->new_rtable, rte, tokens[i], grp );
                                                }
                                        }
                                }
                        }
-               } else {
-                       if( DEBUG || (vlevel > 2) ) {
-                               fprintf( stderr, "entry not included, sender not matched: %s\n", tokens[1] );
-                       }
                }
+       } else {
+               if( DEBUG || (vlevel > 2) ) {
+                       fprintf( stderr, "entry not included, sender not matched: %s\n", tokens[1] );
+               }
+       }
 }
 
 /*
@@ -371,6 +405,153 @@ static void trash_entry( uta_ctx_t* ctx, char* ts_field, uint32_t subid, int vle
        }
 }
 
+/*
+       Given the tokens from an mme_ar (meid add/replace) entry, add the entries.
+       the 'owner' which should be the dns name or IP address of an enpoint
+       the meid_list is a space separated list of me IDs
+
+       This function assumes the caller has vetted the pointers as needed.
+
+       For each meid in the list, an entry is pushed into the hash which references the owner
+       endpoint such that when the meid is used to route a message it references the endpoint
+       to send messages to.
+*/
+static void parse_meid_ar( route_table_t* rtab, char* owner, char* meid_list, int vlevel ) {
+       char*   tok;
+       int             ntoks;
+       char*   tokens[128];
+       int             i;
+       int             state;
+       endpoint_t*     ep;                                             // endpoint struct for the owner
+
+       owner = clip( owner );                          // ditch extra whitespace and trailing comments
+       meid_list = clip( meid_list );
+
+       ntoks = uta_tokenise( meid_list, tokens, 128, ' ' );
+       for( i = 0; i < ntoks; i++ ) {
+               if( (ep = rt_ensure_ep( rtab, owner )) != NULL ) {
+                       state = rmr_sym_put( rtab->hash, tokens[i], RT_ME_SPACE, ep );                                          // slam this one in if new; replace if there
+                       if( DEBUG || (vlevel > 1) ) fprintf( stderr, "[DBUG] parse_meid_ar: add/replace meid: %s owned by: %s state=%d\n", tokens[i], owner, state );
+fprintf( stderr, "[DBUG] parse_meid_ar: add/replace meid: %s owned by: %s state=%d\n", tokens[i], owner, state );
+               } else {
+                       fprintf( stderr, "[WRN] rmr parse_meid_ar: unable to create an endpoint for owner: %s", owner );
+               }
+       }
+}
+
+/*
+       Given the tokens from an mme_del, delete the listed meid entries from the new
+       table. The list is a space separated list of meids.
+
+       The meids in the hash reference endpoints which are never deleted and so
+       the only thing that we need to do here is to remove the meid from the hash.
+
+       This function assumes the caller has vetted the pointers as needed.
+*/
+static void parse_meid_del( route_table_t* rtab, char* meid_list, int vlevel ) {
+       char*   tok;
+       int             ntoks;
+       char*   tokens[128];
+       int             i;
+
+       if( rtab->hash == NULL ) {
+               return;
+       }
+
+       meid_list = clip( meid_list );
+
+       ntoks = uta_tokenise( meid_list, tokens, 128, ' ' );
+       for( i = 0; i < ntoks; i++ ) {
+               rmr_sym_del( rtab->hash, tokens[i], RT_ME_SPACE );                                              // and it only took my little finger to blow it away!
+               if( DEBUG || (vlevel > 1) ) fprintf( stderr, "[DBUG] parse_meid_del: meid deleted: %s\n", tokens[i] );
+       }
+}
+
+/*
+       Parse a partially parsed meid record. Tokens[0] should be one of:
+               meid_map, mme_ar, mme_del.
+*/
+static void meid_parser( uta_ctx_t* ctx, char** tokens, int ntoks, int vlevel ) {
+       if( tokens == NULL || ntoks < 1 ) {
+               return;                                                 // silent but should never happen
+       }
+
+       if( ntoks < 2 ) {                                       // must have at least two for any valid request record
+               fprintf( stderr, "[ERR] meid_parse: not enough tokens on %s record\n", tokens[0] );
+               return;
+       }
+
+       if( strcmp( tokens[0], "meid_map" ) == 0 ) {                                    // start or end of the meid map update
+               tokens[1] = clip( tokens[1] );
+               if( *(tokens[1]) == 's' ) {
+                       if( ctx->new_rtable != NULL ) {                                 // one in progress?  this forces it out
+                               if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] meid map start: dropping incomplete table\n" );
+                               uta_rt_drop( ctx->new_rtable );
+                       }
+
+                       ctx->new_rtable = uta_rt_clone_all( ctx->rtable );              // start with a clone of everything (mtype, endpoint refs and meid)
+                       ctx->new_rtable->mupdates = 0;
+                       if( DEBUG || (vlevel > 1)  ) fprintf( stderr, "[DBUG] meid_parse: meid map start found\n" );
+               } else {
+                       if( strcmp( tokens[1], "end" ) == 0 ) {                                                         // wrap up the table we were building
+                               if( ntoks > 2 ) {                                                                                               // meid_map | end | <count> |??? given
+                                       if( ctx->new_rtable->mupdates != atoi( tokens[2] ) ) {          // count they added didn't match what we received
+                                               fprintf( stderr, "[ERR] meid_parse: meid map update had wrong number of records: received %d expected %s\n", ctx->new_rtable->mupdates, tokens[2] );
+                                               uta_rt_drop( ctx->new_rtable );
+                                               ctx->new_rtable = NULL;
+                                               return;
+                                       }
+
+                                       if( DEBUG ) fprintf( stderr, "[DBUG] meid_parse: meid map update ended; found expected number of entries: %s\n", tokens[2] );
+                               }
+
+                               if( ctx->new_rtable ) {
+                                       uta_rt_drop( ctx->old_rtable );                         // time to drop one that was previously replaced
+                                       ctx->old_rtable = ctx->rtable;                          // currently active becomes old and allowed to 'drain'
+                                       ctx->rtable = ctx->new_rtable;                          // one we've been adding to becomes active
+                                       ctx->new_rtable = NULL;
+                                       if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] end of meid map noticed\n" );
+
+                                       if( vlevel > 0 ) {
+                                               fprintf( stderr, "[DBUG] old route table:\n" );
+                                               rt_stats( ctx->old_rtable );
+                                               fprintf( stderr, "[DBUG] new route table:\n" );
+                                               rt_stats( ctx->rtable );
+                                       }
+                               } else {
+                                       if( DEBUG ) fprintf( stderr, "[DBUG] end of meid map noticed, but one was not started!\n" );
+                                       ctx->new_rtable = NULL;
+                               }
+                       }
+               }
+
+               return;
+       }       
+
+       if( ! ctx->new_rtable ) {                       // for any other mmap entries, there must be a table in progress or we punt
+               if( DEBUG ) fprintf( stderr, "[DBUG] meid update/delte (%s) encountered, but table update not started\n", tokens[0] );
+               return;
+       }
+
+       if( strcmp( tokens[0], "mme_ar" ) == 0 ) {
+               if( ntoks < 3  || tokens[1] == NULL || tokens[2] == NULL ) {
+                       fprintf( stderr, "[ERR] meid_parse: mme_ar record didn't have enough tokens found %d\n", ntoks );
+                       return;
+               }
+               parse_meid_ar( ctx->new_rtable,  tokens[1], tokens[2], vlevel );
+               ctx->new_rtable->mupdates++;
+       }
+
+       if( strcmp( tokens[0], "mme_del" ) == 0 ) {
+               if( ntoks < 2 ) {
+                       fprintf( stderr, "[ERR] meid_parse: mme_del record didn't have enough tokens\n" );
+                       return;
+               }
+               parse_meid_del( ctx->new_rtable,  tokens[1], vlevel );
+               ctx->new_rtable->mupdates++;
+       }
+}
+
 /*
        Parse a single record recevied from the route table generator, or read
        from a static route table file.  Start records cause a new table to
@@ -379,10 +560,32 @@ static void trash_entry( uta_ctx_t* ctx, char* ts_field, uint32_t subid, int vle
        end record causes the in progress table to be finalised and the
        currently active table is replaced.
 
-       We expect one of several types:
-               newrt|{start|end}
+       The updated table will be activated when the *|end record is encountered.
+       However, to allow for a "double" update, where both the meid map and the
+       route table must be updated at the same time, the end indication on a
+       route table (new or update) may specifiy "hold" which indicates that meid
+       map entries are to follow and the updated route table should be held as
+       pending until the end of the meid map is received and validated.
+
+       CAUTION:  we are assuming that there is a single route/meid map generator
+               and as such only one type of update is received at a time; in other
+               words, the sender cannot mix update records and if there is more than
+               one sender process they must synchronise to avoid issues.
+
+
+       For a RT update, we expect:
+               newrt|{start|end [hold]}
                rte|<mtype>[,sender]|<endpoint-grp>[;<endpoint-grp>,...]
                mse|<mtype>[,sender]|<sub-id>|<endpoint-grp>[;<endpoint-grp>,...]
+               mse| <mtype>[,sender] | <sub-id> | %meid
+
+
+       For a meid map update we expect:
+               meid_map | start
+               meid_map | end | <count> | <md5-hash>
+               mme_ar | <e2term-id> | <meid0> <meid1>...<meidn>
+               mme_del | <meid0> <meid1>...<meidn>
+
 */
 static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) {
        int i;
@@ -391,7 +594,6 @@ static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) {
        int     grp;                                                    // group number
        rtable_ent_t*   rte;                            // route table entry added
        char*   tokens[128];
-       char*   gtokens[64];                            // groups
        char*   tok;                                            // pointer into a token or string
 
        if( ! buf ) {
@@ -405,6 +607,7 @@ static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) {
        *(tok+1) = 0;
 
        if( (ntoks = uta_tokenise( buf, tokens, 128, '|' )) > 0 ) {
+               tokens[0] = clip( tokens[0] );
                switch( *(tokens[0]) ) {
                        case 0:                                                                                                 // ignore blanks
                                // fallthrough
@@ -451,27 +654,28 @@ static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) {
                                                uta_rt_drop( ctx->new_rtable );
                                        }
 
-                                       if( ctx->rtable )  {
-                                               ctx->new_rtable = uta_rt_clone( ctx->rtable );  // create by cloning endpoint entries from active table
-                                       } else {
-                                               ctx->new_rtable = uta_rt_init(  );                              // don't have one yet, just crate empty
-                                       }
+                                       ctx->new_rtable = NULL;
+                                       ctx->new_rtable = uta_rt_clone( ctx->rtable );  // create by cloning endpoint and meidtentries from active table
                                        if( DEBUG > 1 || (vlevel > 1)  ) fprintf( stderr, "[DBUG] start of route table noticed\n" );
                                }
                                break;
 
-                       case 'm':                                       // assume mse entry
-                               if( ! ctx->new_rtable ) {                       // bad sequence, or malloc issue earlier; ignore siliently
-                                       break;
-                               }
+                       case 'm':                                                               // mse entry or one of the meid_ records
+                               if( strcmp( tokens[0], "mse" ) == 0 ) {
+                                       if( ! ctx->new_rtable ) {                       // bad sequence, or malloc issue earlier; ignore siliently
+                                               break;
+                                       }
 
-                               if( ntoks < 4 ) {
-                                       if( DEBUG ) fprintf( stderr, "[WRN] rmr_rtc: mse record had too few fields: %d instead of 4\n", ntoks );
-                                       break;
-                               }
+                                       if( ntoks < 4 ) {
+                                               if( DEBUG ) fprintf( stderr, "[WRN] rmr_rtc: mse record had too few fields: %d instead of 4\n", ntoks );
+                                               break;
+                                       }
 
-                               build_entry( ctx, tokens[1], atoi( tokens[2] ), tokens[3], vlevel );
-                               ctx->new_rtable->updates++;
+                                       build_entry( ctx, tokens[1], atoi( tokens[2] ), tokens[3], vlevel );
+                                       ctx->new_rtable->updates++;
+                               } else {
+                                       meid_parser( ctx, tokens, ntoks, vlevel );
+                               }
                                break;
 
                        case 'r':                                       // assume rt entry
@@ -527,12 +731,7 @@ static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) {
                                                uta_rt_drop( ctx->new_rtable );
                                        }
 
-                                       if( ctx->rtable )  {
-                                               ctx->new_rtable = uta_rt_clone_all( ctx->rtable );      // start with a clone of everything (endpts and entries)
-                                       } else {
-                                               ctx->new_rtable = uta_rt_init(  );                              // don't have one yet, just crate empty
-                                       }
-
+                                       ctx->new_rtable = uta_rt_clone_all( ctx->rtable );      // start with a clone of everything (endpts and entries)
                                        ctx->new_rtable->updates = 0;                                           // init count of updates received
                                        if( DEBUG > 1 || (vlevel > 1)  ) fprintf( stderr, "[DBUG] start of rt update noticed\n" );
                                }
@@ -592,7 +791,7 @@ static void read_static_rt( uta_ctx_t* ctx, int vlevel ) {
                parse_rt_rec( ctx, rec, vlevel );
        }
 
-       if( DEBUG ) fprintf( stderr, "[DBUG] rmr:  seed route table successfully parsed: %d records\n", rcount );
+       if( DEBUG ) fprintf( stderr, "[DBUG] rmr_read_static:  seed route table successfully parsed: %d records\n", rcount );
        free( fbuf );
 }
 
@@ -611,6 +810,7 @@ static void collect_things( void* st, void* entry, char const* name, void* thing
                return;
        }
 
+       tl->names[tl->nused] = name;                    // the name/key
        tl->things[tl->nused++] = thing;                // save a reference to the thing
 }
 
@@ -728,7 +928,7 @@ static route_table_t* uta_rt_init( ) {
                return NULL;
        }
 
-       if( (rt->hash = rmr_sym_alloc( 509 )) == NULL ) {               // modest size, prime
+       if( (rt->hash = rmr_sym_alloc( RT_SIZE )) == NULL ) {
                free( rt );
                return NULL;
        }
@@ -737,121 +937,105 @@ static route_table_t* uta_rt_init( ) {
 }
 
 /*
-       Clone (sort of) an existing route table.  This is done to preserve the endpoint
-       names referenced in a table (and thus existing sessions) when a new set
-       of message type to endpoint name mappings is received.  A new route table
-       with only endpoint name references is returned based on the active table in
-       the context.
+       Clones one of the spaces in the given table.
+       Srt is the source route table, Nrt is the new route table; if nil, we allocate it.
+       Space is the space in the old table to copy. Space 0 uses an integer key and
+       references rte structs. All other spaces use a string key and reference endpoints.
 */
-static route_table_t* uta_rt_clone( route_table_t* srt ) {
+static route_table_t* rt_clone_space( route_table_t* srt, route_table_t* nrt, int space ) {
        endpoint_t*             ep;             // an endpoint
-       route_table_t*  nrt;    // new route table
+       rtable_ent_t*   rte;    // a route table entry
        void*   sst;                    // source symtab
        void*   nst;                    // new symtab
-       thing_list_t things;
-       int i;
-
-       if( srt == NULL ) {
-               return NULL;
-       }
+       thing_list_t things;    // things from the space to copy
+       int             i;
+       int             free_on_err = 0;
 
-       if( (nrt = (route_table_t *) malloc( sizeof( *nrt ) )) == NULL ) {
-               return NULL;
+       if( nrt == NULL ) {                             // make a new table if needed
+               free_on_err = 1;
+               nrt = uta_rt_init();
        }
 
-       if( (nrt->hash = rmr_sym_alloc( 509 )) == NULL ) {              // modest size, prime
-               free( nrt );
-               return NULL;
+       if( srt == NULL ) {             // source was nil, just give back the new table
+               return nrt;
        }
 
        things.nalloc = 2048;
        things.nused = 0;
        things.things = (void **) malloc( sizeof( void * ) * things.nalloc );
+       things.names = (const char **) malloc( sizeof( char * ) * things.nalloc );
        if( things.things == NULL ) {
-               free( nrt->hash );
-               free( nrt );
-               return NULL;
+               if( free_on_err ) {
+                       free( nrt->hash );
+                       free( nrt );
+                       nrt = NULL;
+               }
+
+               return nrt;
        }
 
        sst = srt->hash;                                                                                        // convenience pointers (src symtab)
        nst = nrt->hash;
 
-       rmr_sym_foreach_class( sst, 1, collect_things, &things );               // collect the named endpoints in the active table
+       rmr_sym_foreach_class( sst, space, collect_things, &things );           // collect things from this space
 
+       if( DEBUG ) fprintf( stderr, "[DBUG] clone space cloned %d things in space %d\n",  things.nused, space );
        for( i = 0; i < things.nused; i++ ) {
-               ep = (endpoint_t *) things.things[i];
-               rmr_sym_put( nst, ep->name, 1, ep );                                            // slam this one into the new table
+               if( space ) {                                                                                           // string key, epoint reference
+                       ep = (endpoint_t *) things.things[i];
+                       rmr_sym_put( nst, things.names[i], space, ep );                                 // slam this one into the new table
+               } else {
+                       rte = (rtable_ent_t *) things.things[i];
+                       rte->refs++;                                                                                    // rtes can be removed, so we track references
+                       rmr_sym_map( nst, rte->key, rte );                                              // add to hash using numeric mtype/sub-id as key (default to space 0)
+               }
        }
 
        free( things.things );
+       free( (void *) things.names );
        return nrt;
 }
 
 /*
-       Clones _all_ of the given route table (references both endpoints AND the route table
-       entries. Needed to support a partial update where some route table entries will not
-       be deleted if not explicitly in the update.
+       Creates a new route table and then clones the parts of the table which we must keep with each newrt|start.
+       The endpoint and meid entries in the hash must be preserved.
 */
-static route_table_t* uta_rt_clone_all( route_table_t* srt ) {
-       endpoint_t*             ep;             // an endpoint
-       rtable_ent_t*   rte;    // a route table entry
-       route_table_t*  nrt;    // new route table
-       void*   sst;                    // source symtab
-       void*   nst;                    // new symtab
-       thing_list_t things0;   // things from space 0 (table entries)
-       thing_list_t things1;   // things from space 1 (end points)
+static route_table_t* uta_rt_clone( route_table_t* srt ) {
+       endpoint_t*             ep;                             // an endpoint
+       rtable_ent_t*   rte;                    // a route table entry
+       route_table_t*  nrt = NULL;             // new route table
        int i;
 
        if( srt == NULL ) {
-               return NULL;
-       }
-
-       if( (nrt = (route_table_t *) malloc( sizeof( *nrt ) )) == NULL ) {
-               return NULL;
-       }
-
-       if( (nrt->hash = rmr_sym_alloc( 509 )) == NULL ) {              // modest size, prime
-               free( nrt );
-               return NULL;
+               return uta_rt_init();           // no source to clone, just return an empty table
        }
 
-       things0.nalloc = 2048;
-       things0.nused = 0;
-       things0.things = (void **) malloc( sizeof( void * ) * things0.nalloc );
-       if( things0.things == NULL ) {
-               free( nrt->hash );
-               free( nrt );
-               return NULL;
-       }
-
-       things1.nalloc = 2048;
-       things1.nused = 0;
-       things1.things = (void **) malloc( sizeof( void * ) * things1.nalloc );
-       if( things1.things == NULL ) {
-               free( nrt->hash );
-               free( nrt );
-               return NULL;
-       }
+       nrt = rt_clone_space( srt, nrt, RT_NAME_SPACE );                // allocate a new one, add endpoint refs
+       rt_clone_space( srt, nrt, RT_ME_SPACE );                                // add meid refs to new
 
-       sst = srt->hash;                                                                                        // convenience pointers (src symtab)
-       nst = nrt->hash;
+       return nrt;
+}
 
-       rmr_sym_foreach_class( sst, 0, collect_things, &things0 );              // collect the rtes
-       rmr_sym_foreach_class( sst, 1, collect_things, &things1 );              // collect the named endpoints in the active table
+/*
+       Creates a new route table and then clones  _all_ of the given route table (references 
+       both endpoints AND the route table entries. Needed to support a partial update where 
+       some route table entries will not be deleted if not explicitly in the update and when 
+       we are adding/replacing meid references.
+*/
+static route_table_t* uta_rt_clone_all( route_table_t* srt ) {
+       endpoint_t*             ep;                             // an endpoint
+       rtable_ent_t*   rte;                    // a route table entry
+       route_table_t*  nrt = NULL;             // new route table
+       int i;
 
-       for( i = 0; i < things0.nused; i++ ) {
-               rte = (rtable_ent_t *) things0.things[i];
-               rte->refs++;                                                                                            // rtes can be removed, so we track references
-               rmr_sym_map( nst, rte->key, rte );                                                      // add to hash using numeric mtype/sub-id as key (default to space 0)
+       if( srt == NULL ) {
+               return uta_rt_init();           // no source to clone, just return an empty table
        }
 
-       for( i = 0; i < things1.nused; i++ ) {
-               ep = (endpoint_t *) things1.things[i];
-               rmr_sym_put( nst, ep->name, 1, ep );                                            // slam this one into the new table
-       }
+       nrt = rt_clone_space( srt, nrt, RT_MT_SPACE );                  // create new, clone all spaces to it
+       rt_clone_space( srt, nrt, RT_NAME_SPACE );
+       rt_clone_space( srt, nrt, RT_ME_SPACE );
 
-       free( things0.things );
-       free( things1.things );
        return nrt;
 }
 
index cce8ddc..58fc940 100644 (file)
 #include <sys/stat.h>
 #include <unistd.h>
 
+static int refresh_vlevel( int vfd ) {
+       int vlevel = 0;
+       char    rbuf[128];
+
+       if( vfd >= 0 ) {                                        // if file is open, read current value
+               rbuf[0] = 0;
+               lseek( vfd, 0, 0 );
+               read( vfd, rbuf, 10 );
+               vlevel = atoi( rbuf );
+       }
+
+       return vlevel;
+}
+
 /*
        Route Table Collector
        A side thread which opens a socket and subscribes to a routing table generator.
@@ -71,7 +85,7 @@
 
        Buffers received from the route table generator can contain multiple newline terminated
        records, but each buffer must be less than 4K in length, and the last record in a
-       buffere may NOT be split across buffers.
+       buffer may NOT be split across buffers.
 
        Other chores:
        In addition to the primary task of getting, vetting, and installing a new route table, or
@@ -119,12 +133,7 @@ static void* rtc( void* vctx ) {
 
        if( (eptr = getenv( ENV_VERBOSE_FILE )) != NULL ) {
                vfd = open( eptr, O_RDONLY );
-               if( vfd >= 0 ) {
-                       wbuf[0] = 0;
-                       lseek( vfd, 0, 0 );
-                       read( vfd, wbuf, 10 );
-                       vlevel = atoi( wbuf );
-               }
+               vlevel = refresh_vlevel( vfd );
        }                
 
        read_static_rt( ctx, vlevel );                                          // seed the route table if one provided
@@ -210,20 +219,18 @@ static void* rtc( void* vctx ) {
                        }
 
                        if( time( NULL ) > blabber  ) {
-                               blabber = time( NULL ) + count_delay;                                   // set next time to blabber, then do so
-                               if( blabber > bump_freq ) {
-                                       count_delay = 300;
+                               vlevel = refresh_vlevel( vfd );
+                               if( vlevel >= 0 ) {                                                                             // allow it to be forced off with -n in verbose file
+                                       blabber = time( NULL ) + count_delay;                           // set next time to blabber, then do so
+                                       if( blabber > bump_freq ) {
+                                               count_delay = 300;
+                                       }
+                                       rt_epcounts( ctx->rtable, ctx->my_name );
                                }
-                               rt_epcounts( ctx->rtable, ctx->my_name );
                        }
                }
 
-               if( vfd >= 0 ) {                                                        // if file open, check for change to vlevel
-                       wbuf[0] = 0;
-                       lseek( vfd, 0, 0 );
-                       read( vfd, wbuf, 10 );
-                       vlevel = atoi( wbuf );
-               }
+               vlevel = refresh_vlevel( vfd );                 // ensure it's fresh when we get a message
 
                if( msg != NULL && msg->len > 0 ) {
                        payload = msg->payload;
index 7cd3e20..81e79ca 100644 (file)
@@ -388,6 +388,20 @@ static inline rtable_ent_t*  uta_get_rte( route_table_t *rt, int sid, int mtype,
        return rte;
 }
 
+/*
+       Given a route table and meid string, find the owner (if known). Returns a pointer to
+       the endpoint struct or nil.
+*/
+static inline endpoint_t*  get_meid_owner( route_table_t *rt, char* meid ) {
+       endpoint_t* ep;         // the ep we found in the hash
+
+       if( rt == NULL || rt->hash == NULL || meid == NULL || *meid == 0 ) {
+               return NULL;
+       }
+
+       return (endpoint_t *) rmr_sym_get( rt->hash, meid, RT_ME_SPACE ); 
+}
+
 /*
        Return a string of count information. E.g.:
                <ep-name>:<port> <good> <hard-fail> <soft-fail>
@@ -419,4 +433,56 @@ static inline char* get_ep_counts( endpoint_t* ep, char* ubuf, int ubuf_len ) {
        return rs;
 }
 
+/*
+       Given a message, use the meid field to find the owner endpoint for the meid.
+       The owner ep is then used to extract the socket through which the message
+       is sent. This returns TRUE if we found a socket and it was written to the
+       nn_sock pointer; false if we didn't.
+
+       We've been told that the meid is a string, thus we count on it being a nil
+       terminated set of bytes.
+*/
+static int epsock_meid( route_table_t *rtable, rmr_mbuf_t* msg, nng_socket* nn_sock, endpoint_t** uepp ) {
+       endpoint_t*     ep;                             // seected end point
+       int     state = FALSE;                  // processing state
+       char*   meid;
+
+
+       errno = 0;
+       if( ! nn_sock || msg == NULL || rtable == NULL ) {                      // missing stuff; bail fast
+               errno = EINVAL;
+               return FALSE;
+       }
+
+       meid = ((uta_mhdr_t *) msg->header)->meid;
+
+       if( (ep = get_meid_owner( rtable, meid )) == NULL ) {
+               if( uepp != NULL ) {                                                            // caller needs refernce to endpoint too
+                       *uepp = NULL;
+               }
+
+               if( DEBUG ) fprintf( stderr, "[DBUG] epsock_meid: no ep in hash for (%s)\n", meid );
+               return FALSE;
+       }
+
+       state = TRUE;
+       if( ! ep->open ) {                                                              // not connected
+               if( ep->addr == NULL ) {                                        // name didn't resolve before, try again
+                       ep->addr = strdup( ep->name );                  // use the name directly; if not IP then transport will do dns lookup
+               }
+
+               if( uta_link2( ep ) ) {                                         // find entry in table and create link
+                       ep->open = TRUE;
+                       *nn_sock = ep->nn_sock;                                 // pass socket back to caller
+               } else {
+                       state = FALSE;
+               }
+               if( DEBUG ) fprintf( stderr, "[DBUG] epsock_meid: connection attempted with %s: %s\n", ep->name, state ? "[OK]" : "[FAIL]" );
+       } else {
+               *nn_sock = ep->nn_sock;
+       }
+
+       return state;
+}
+
 #endif
index a4e2410..d24a900 100644 (file)
@@ -794,7 +794,12 @@ static  rmr_mbuf_t* mtosend_msg( void* vctx, rmr_mbuf_t* msg, int max_to ) {
        send_again = 1;                                                                                 // force loop entry
        group = 0;                                                                                              // always start with group 0
        while( send_again ) {
-               sock_ok = uta_epsock_rr( rte, group, &send_again, &nn_sock, &ep );              // select endpt from rr group and set again if more groups
+               if( rte->nrrgroups > 0 ) {                                                      // this is a round robin entry
+                       sock_ok = uta_epsock_rr( rte, group, &send_again, &nn_sock, &ep );              // select endpt from rr group and set again if more groups
+               } else {
+                       sock_ok = epsock_meid( ctx->rtable, msg, &nn_sock, &ep );
+                       send_again = 0;
+               }
 
                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 );
index 656287d..d4eca07 100644 (file)
@@ -110,11 +110,19 @@ int mbuf_api_test( ) {
        errors += fail_if( i > 0, "(rv) attempt to copy bytes to meid with nil message" );
 
        errno = 0;
+       strncpy( src_buf, "a very long string that should trigger an error when stuffing it into meid", sizeof( src_buf ) );            // ensure no zero byte at field length
        i = rmr_bytes2meid( mbuf, src_buf, RMR_MAX_MEID + 1 );
        errors += fail_if( errno == 0, "(errno) attempt to copy bytes to meid with large source buffer" );
        errors += fail_if( i != RMR_MAX_MEID, "(rv) attempt to copy bytes to meid with large source buffer" );
+       
+       errno = 0;
+       i = rmr_bytes2meid( mbuf, src_buf, RMR_MAX_MEID );                      // it's not 0 terminated at length expect failure
+       errors += fail_if( errno == 0, "(errno) attempt to copy non-zero termianted bytes to meid with large source buffer" );
+       errors += fail_if( i != RMR_MAX_MEID, "(rv) attempt to copy bytes to meid with large source buffer" );
 
        errno = 0;
+       memset( src_buf, 0, RMR_MAX_MEID+2 );
+       strcpy( src_buf, "smaller bytes" );
        i = rmr_bytes2meid( mbuf, src_buf, RMR_MAX_MEID  );
        errors += fail_if( errno != 0, "copy bytes to meid; expected errno to be ok" );
        errors += fail_if( i != RMR_MAX_MEID, "copy bytes to meid; expected return value to be max meid len" );
index fb12ec2..4fd6f02 100644 (file)
@@ -79,6 +79,7 @@ static void gen_rt( uta_ctx_t* ctx );         // defined in sr_nng_static_test, but use
 
                                                                                        // specific test tools in this directory
 #include "test_support.c"                                      // things like fail_if()
+#include "test_gen_rt.c"
                                                                                        // and finally....
 #include "tools_static_test.c"                         // local test functions pulled directly because of static nature of things
 #include "symtab_static_test.c"
index 87f9931..ae9709b 100644 (file)
@@ -123,6 +123,7 @@ static int rt_test( ) {
        char    *buf;
        char*   seed_fname;             // seed file
        nng_socket nn_sock;             // this is a struct in nng, so difficult to validate
+       rmr_mbuf_t*     mbuf;           // message for meid route testing
 
        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 );
@@ -316,6 +317,7 @@ static int rt_test( ) {
        state = uta_epsock_rr( rte, 22, NULL, NULL, &ep );
        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, 0,  &more, &nn_sock, &ep );                        // drive null case for coverage
@@ -367,6 +369,24 @@ static int rt_test( ) {
        state = uta_link2( ep );
        errors += fail_if_true( state, "link2 did not return false when given nil pointers" );
 
+       // ----------------- test the meid support for looking up an endpoint based on the meid in the message -----
+
+       ctx->my_name = strdup( "my_host_name" );                // set up to load a rtable
+       ctx->my_ip = strdup( "192.168.1.30" );
+       gen_rt( ctx );                                                                  // generate a route table with meid entries and hang off ctx
+
+       mbuf = rmr_alloc_msg( ctx, 2048 );               //  buffer to play with
+       mbuf->len = 100;
+       rmr_str2meid( mbuf, "meid1" );                                  // id that we know is in the map
+
+       state = epsock_meid( ctx->rtable, mbuf, &nn_sock, &ep );
+       errors += fail_if_nil( ep, "ep was nil when looking up ep with known meid in message" );
+       errors += fail_not_equal( state, 1, "state was not true when looking up ep with known meid in message" );
+
+       rmr_str2meid( mbuf, "XXXmeid1" );                               // id that we know is NOT in the map
+       state = epsock_meid( ctx->rtable, mbuf, &nn_sock, &ep );
+       errors += fail_not_nil( ep, "ep was NOT nil when looking ep up with unknown meid in message" );
+       errors += fail_not_equal( state, 0, "state was not false when looking up ep with unknown meid in message" );
 
        return !!errors;                        // 1 or 0 regardless of count
 }
index 168289d..b11934d 100644 (file)
 #define COPY 1
 #define NO_COPY 0
 
-/*
-       Generate a simple route table (for all but direct route table testing).
-       This gets tricky inasmuch as we generate two in one; first a whole table 
-       and then two update tables. The first is a table with a bad counter in the
-       last record to test that we don't load that table and error. The second
-       is a good update.
-*/
-static void gen_rt( uta_ctx_t* ctx ) {
-       int             fd;
-       char*   rt_stuff;               // strings for the route table
-
-       rt_stuff =
-               "newrt|end\n"                                                           // end of table check before start of table found
-               "# comment to drive full comment test\n"
-               "\n"                                                                            // handle blank lines
-               "   \n"                                                                         // handle blank lines
-           "mse|4|10|localhost:4561\n"                                 // entry before start message
-           "rte|4|localhost:4561\n"                                    // entry before start message
-               "newrt|start\n"                                                         // false start to drive detection
-               "xxx|badentry to drive default case"
-               "newrt|start\n"
-           "rte|0|localhost:4560,localhost:4562\n"                                     // these are legitimate entries for our testing
-           "rte|1|localhost:4562;localhost:4561,localhost:4569\n"
-           "rte|2|localhost:4562| 10\n"                                                                // new subid at end
-           "mse|4|10|localhost:4561\n"                                                                 // new msg/subid specifier rec
-           "mse|4|localhost:4561\n"                                                                    // new mse entry with less than needed fields
-               "   rte|   5   |localhost:4563    #garbage comment\n"           // tests white space cleanup
-           "rte|6|localhost:4562\n"
-               "newrt|end\n";
-
-       fd = open( "utesting.rt", O_WRONLY | O_CREAT, 0600 );
-       if( fd < 0 ) {
-               fprintf( stderr, "<BUGGERED> unable to open file for testing route table gen\n" );
-               return;
-       }
-
-       setenv( "RMR_SEED_RT", "utesting.rt", 1 );
-       write( fd, rt_stuff, strlen( rt_stuff ) );              // write in the whole table
-
-       rt_stuff =
-               "updatert|start\n"                                                                      // this is an update to the table
-           "mse|4|99|fooapp:9999,barapp:9999;logger:9999\n"    // update just one entry
-               "updatert|end | 3\n";                                                           // bad count; this update should be rejected
-       write( fd, rt_stuff, strlen( rt_stuff ) );
-
-       rt_stuff =
-               "updatert|start\n"                                                                      // this is an update to the table
-           "mse|4|10|fooapp:4561,barapp:4561;logger:9999\n"    // update just one entry
-               "del|2|-1\n"                                                                            // delete an entry; not there so no action
-               "del|2|10\n"                                                                            // delete an entry
-               "updatert|end | 3\n";                                                           // end table; updates have a count as last field
-       write( fd, rt_stuff, strlen( rt_stuff ) );
-       
-       close( fd );
-       read_static_rt( ctx, 1 );                                                               // force in verbose mode to see stats on tty if failure
-       unlink( "utesting.rt" );
-}
-
-
 /*
        Drive the send and receive functions.  We also drive as much of the route
        table collector as is possible without a real rtg process running somewhere.
@@ -113,6 +54,9 @@ static void gen_rt( uta_ctx_t* ctx ) {
        module as it tests the user facing send/receive/call/rts functions. These tests
        should exercise specific cases for the internal functions as they will not
        specifically be driven elsewhere.
+
+       This requires the gen_rt funcition that is in the test_gen_rt module and should
+       have been included by the test module(s) which include this.
 */
 static int sr_nng_test() {
        uta_ctx_t* ctx;                         // context needed to test load static rt
diff --git a/test/test_gen_rt.c b/test/test_gen_rt.c
new file mode 100644 (file)
index 0000000..08c1276
--- /dev/null
@@ -0,0 +1,141 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+           Copyright (c) 2019 Nokia
+           Copyright (c) 2018-2019 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+       Mnemonic:       test_gen_rt.c
+       Abstract:       This provides the means to generate a route table to disk.
+       Author:         E. Scott Daniels
+       Date:           6 January 2019
+*/
+
+#ifndef _test_gen_rt_c
+#define _test_gen_rt_c
+
+
+/*
+       Generate a simple route table (for all but direct route table testing).
+       This gets tricky inasmuch as we generate two in one; first a whole table 
+       and then two update tables. The first is a table with a bad counter in the
+       last record to test that we don't load that table and error. The second
+       is a good update. The same applies to the meid map; first has a bad counter
+       and some bad records to drive coverage testing. The end should leave a good
+       meid map in the table.
+*/
+static void gen_rt( uta_ctx_t* ctx ) {
+       int             fd;
+       char*   rt_stuff;               // strings for the route table
+
+       fd = open( "utesting.rt", O_WRONLY | O_CREAT, 0600 );
+       if( fd < 0 ) {
+               fprintf( stderr, "<BUGGERED> unable to open file for testing route table gen\n" );
+               return;
+       }
+
+       rt_stuff =
+               "newrt|end\n"                                                           // end of table check before start of table found
+               "# comment to drive full comment test\n"
+               "\n"                                                                            // handle blank lines
+               "   \n"                                                                         // handle blank lines
+           "mse|4|10|localhost:4561\n"                                 // entry before start message
+           "rte|4|localhost:4561\n"                                    // entry before start message
+               "newrt|start\n"                                                         // false start to drive detection
+               "xxx|badentry to drive default case"
+               "newrt|start\n"
+           "rte|0|localhost:4560,localhost:4562\n"                                     // these are legitimate entries for our testing
+           "rte|1|localhost:4562;localhost:4561,localhost:4569\n"
+           "rte|2|localhost:4562| 10\n"                                                                // new subid at end
+           "mse|4|10|localhost:4561\n"                                                                 // new msg/subid specifier rec
+           "mse|4|localhost:4561\n"                                                                    // new mse entry with less than needed fields
+               "   rte|   5   |localhost:4563    #garbage comment\n"           // tests white space cleanup
+           "rte|6|localhost:4562\n"
+               "newrt|end\n";
+
+       setenv( "RMR_SEED_RT", "utesting.rt", 1 );
+       write( fd, rt_stuff, strlen( rt_stuff ) );                              // write in the whole table
+
+       rt_stuff =                                                                                              // add an meid map which will fail
+               "meid_map | start\n"
+               "mme_ar | e2t-1 | one two three four\n"
+               "mme_del | one two\n"
+               "mme_del \n"                                                                            // short entries drive various checks for coverage
+               "mme_ar \n"                                                                                     
+               "mme_ar | e2t-0 \n"                                                                     
+               "meid_map | end | 5\n";                                                         // this will fail as the short recs don't "count"
+       write( fd, rt_stuff, strlen( rt_stuff ) );
+
+       rt_stuff =
+               "updatert|start\n"                                                                      // this is an update to the table
+           "mse|4|99|fooapp:9999,barapp:9999;logger:9999\n"    // update just one entry
+               "updatert|end | 3\n";                                                           // bad count; this update should be rejected
+       write( fd, rt_stuff, strlen( rt_stuff ) );
+
+
+       rt_stuff =
+               "updatert|start\n"                                                                      // this is an update to the table
+           "mse|4|10|fooapp:4561,barapp:4561;logger:9999\n"    // update just one entry
+               "mse | 99 | -1 | %meid\n"                                                       // type 99 will route based on meid and not mtype
+               "del|2|-1\n"                                                                            // delete an entry; not there so no action
+               "del|2|10\n"                                                                            // delete an entry
+               "updatert|end | 4\n";                                                           // end table; updates have a count as last field
+       write( fd, rt_stuff, strlen( rt_stuff ) );
+
+       rt_stuff =                                                                                              // this leaves an meid map in place too
+               "meid_map | start\n"
+               "mme_ar | localhost:4567 | meid1 meid2 meid3 meid4\n"
+               "mme_ar | localhost:4067 | meid11 meid12\n"
+               "meid_map | end | 2\n";
+       write( fd, rt_stuff, strlen( rt_stuff ) );
+
+       rt_stuff =                                                                                                      // verify that we can del entries in the current table
+               "meid_map | start\n"
+               "mme_del | meid11 meid12 meid13\n"              // includes a non-existant meid
+               "meid_map | end | 1\n";
+       write( fd, rt_stuff, strlen( rt_stuff ) );
+       
+       close( fd );
+       read_static_rt( ctx, 1 );                                                               // force in verbose mode to see stats on tty if failure
+       unlink( "utesting.rt" );
+}
+
+
+
+/*
+       Generate a custom route table file using the buffer passed in.
+*/
+static void gen_custom_rt( uta_ctx_t* ctx, char* buf ) {
+       int             fd;
+       char*   rt_stuff;               // strings for the route table
+
+       fd = open( "utesting.rt", O_WRONLY | O_CREAT, 0600 );
+       if( fd < 0 ) {
+               fprintf( stderr, "<BUGGERED> unable to open file for testing route table gen\n" );
+               return;
+       }
+       setenv( "RMR_SEED_RT", "utesting.rt", 1 );
+
+       write( fd, rt_stuff, strlen( buf ) );
+
+       close( fd );
+       read_static_rt( ctx, 1 );                                                               // force in verbose mode to see stats on tty if failure
+       unlink( "utesting.rt" );
+}
+
+
+#endif
index 9ab1827..8b21fee 100755 (executable)
@@ -450,7 +450,7 @@ do
        if ! ./${tfile%.c} >/tmp/PID$$.log 2>&1
        then
                echo "[FAIL] unit test failed for: $tfile"
-               if [[ -n capture_file ]] 
+               if [[ -n $capture_file ]] 
                then
                        echo "all errors captured in $capture_file, listing only fail message on tty"
                        echo "$tfile --------------------------------------" >>$capture_file