Initial commit of RMR Library
[ric-plt/lib/rmr.git] / src / common / src / rt_generic_static.c
diff --git a/src/common/src/rt_generic_static.c b/src/common/src/rt_generic_static.c
new file mode 100644 (file)
index 0000000..26e907e
--- /dev/null
@@ -0,0 +1,466 @@
+// :vi sw=4 ts=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:       rt_generic_static.c
+       Abstract:       These are route table functions which are not specific to the 
+                               underlying protocol.  rtable_static, and rtable_nng_static
+                               have transport provider specific code.
+
+                               This file must be included before the nng/nano specific file as
+                               it defines types. 
+
+       Author:         E. Scott Daniels
+       Date:           5  February 2019
+*/
+
+#ifndef rt_generic_static_c
+#define rt_generic_static_c
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/*
+       Passed to a symtab foreach callback to construct a list of pointers from 
+       a current symtab.
+*/
+typedef struct thing_list {
+       int nalloc;
+       int nused;
+       void** things;
+} thing_list_t;
+
+
+/*
+       Given a message type create a route table entry and add to the hash keyed on the
+       message type.  Once in the hash, endpoints can be added with uta_add_ep. Size
+       is the number of group slots to allocate in the entry.
+*/
+static rtable_ent_t* uta_add_rte( route_table_t* rt, int mtype, int nrrgroups ) {
+       rtable_ent_t* rte;
+
+       if( rt == NULL ) {
+               return NULL;
+       }
+
+       if( (rte = (rtable_ent_t *) malloc( sizeof( *rte ) )) == NULL ) {
+               fprintf( stderr, "rmr_add_rte: malloc failed for entry\n" );
+               return NULL;
+       }
+       memset( rte, 0, sizeof( *rte ) );
+
+       if( nrrgroups <= 0 ) {
+               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;
+       }
+       memset( rte->rrgroups, 0, sizeof( rrgroup_t *) * nrrgroups );
+       rte->nrrgroups = nrrgroups;
+
+       rmr_sym_map( rt->hash, mtype, rte );                                                    // add to hash using numeric mtype as key
+
+       if( DEBUG ) fprintf( stderr, "[DBUG] route table entry created: mt=%d groups=%d\n", mtype, nrrgroups );
+       return rte;
+}
+
+/*
+       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 
+       be started (if a partial table was received it is discarded. Table
+       entry records are added to the currenly 'in progress' table, and an 
+       end record causes the in progress table to be finalised and the
+       currently active table is replaced.
+*/
+static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) {
+       int i;
+       int ntoks;                                                      // number of tokens found in something
+       int ngtoks;
+       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 ) {
+               return;
+       }
+
+       while( *buf && isspace( *buf ) ) {                                                      // skip leading whitespace
+               buf++;
+       }
+       for( tok = buf + (strlen( buf ) - 1); tok > buf && isspace( *tok ); tok-- );    // trim trailing spaces too
+       *(tok+1) = 0;
+
+       if( (ntoks = uta_tokenise( buf, tokens, 128, '|' )) > 0 ) {
+               switch( *(tokens[0]) ) {
+                       case 0:                                                                                                 // ignore blanks
+                               // fallthrough
+                       case '#':                                                                                               // and comment lines
+                               break;
+
+                       case 'n':                                                                                               // newrt|{start|end}
+                               if( strcmp( tokens[1], "end" ) == 0 ) {                         // wrap up the table we were building
+                                       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 route table noticed\n" );
+                                       } else {
+                                               if( DEBUG > 1 ) fprintf( stderr, "[DBUG] end of route table noticed, but one was not started!\n" );
+                                               ctx->new_rtable = NULL;
+                                       }
+                               } else {                                                                                        // start a new table.
+                                       if( ctx->new_rtable != NULL ) {                                 // one in progress?  this forces it out
+                                               if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] new table; dropping incomplete table\n" );
+                                               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
+                                       }
+                                       if( DEBUG > 1 || (vlevel > 1)  ) fprintf( stderr, "[DBUG] start of route table noticed\n" );
+                               }
+                               break;
+
+                       case 'r':                                       // assume rt entry
+                               if( ! ctx->new_rtable ) {                       // bad sequence, or malloc issue earlier; ignore siliently
+                                       break;
+                               }
+
+                               if( ((tok = strchr( tokens[1], ',' )) == NULL ) ||                                      // no sender names
+                                       (uta_has_str( tokens[1],  ctx->my_name, ',', 127) >= 0) ||              // our name isn't in the list
+                                       has_myip( tokens[1], ctx->ip_list, ',', 127 ) ) {                               // the list has one of our IP addresses 
+
+                                       if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] create rte for mtype=%s\n", tokens[1] );
+                                       
+                                       if( (ngtoks = uta_tokenise( tokens[2], gtokens, 64, ';' )) > 0 ) {                                      // split last field by groups first
+                                               rte = uta_add_rte( ctx->new_rtable, atoi( tokens[1] ), ngtoks );                        // get/create entry for message type
+                                               for( grp = 0; grp < ngtoks; grp++ ) {
+                                                       if( (ntoks = uta_tokenise( gtokens[grp], tokens, 64, ',' )) > 0 ) {
+                                                               for( i = 0; i < ntoks; i++ ) {
+                                                                       if( DEBUG > 1  || (vlevel > 1)) fprintf( stderr, "[DBUG]    add endpoint  %s\n", 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] );
+                               }
+
+                               break;
+
+                       default:
+                               if( DEBUG ) fprintf( stderr, "[WRN] rmr_rtc: unrecognised request: %s\n", tokens[0] );
+                               break;
+               }
+       }
+}
+
+/*
+       This function attempts to open a static route table in order to create a 'seed'
+       table during initialisation.  The environment variable RMR_SEED_RT is expected
+       to contain the necessary path to the file. If missing, or if the file is empty,
+       no route table will be available until one is received from the generator. 
+
+       This function is probably most useful for testing situations, or extreme 
+       cases where the routes are static.
+*/
+static void read_static_rt( uta_ctx_t* ctx, int vlevel ) {
+       int             i;
+       char*   fname;
+       char*   fbuf;                           // buffer with file contents
+       char*   rec;                            // start of the record
+       char*   eor;                            // end of the record
+       int             rcount = 0;                     // record count for debug
+
+       if( (fname = getenv( ENV_SEED_RT )) == NULL ) {
+               return;
+       }
+
+       if( (fbuf = uta_fib( fname ) ) == NULL ) {                      // read file into a single buffer
+               fprintf( stderr, "[WRN] seed route table could not be opened: %s: %s\n", fname, strerror( errno ) );
+               return;
+       }
+
+       if( DEBUG ) fprintf( stderr, "[INFO] seed route table successfully opened: %s\n", fname );
+       for( rec = fbuf; rec && *rec; rec = eor+1 ) {
+               rcount++;
+               if( (eor = strchr( rec, '\n' )) != NULL ) {
+                       *eor = 0;
+               }
+
+               parse_rt_rec( ctx, rec, vlevel );
+       }
+
+       if( DEBUG ) fprintf( stderr, "[INFO] seed route table successfully parsed: %d records\n", rcount );
+       free( fbuf );
+}
+
+/*
+       Callback driven for each named thing in a symtab. We collect the pointers to those
+       things for later use (cloning).
+*/
+static void collect_things( void* st, void* entry, char const* name, void* thing, void* vthing_list ) {
+       thing_list_t*   tl;
+
+       if( (tl = (thing_list_t *) vthing_list) == NULL ) {
+               return;
+       }
+
+       if( thing == NULL ) {
+               return;
+       }
+
+       tl->things[tl->nused++] = thing;                // save a reference to the thing
+}
+
+/*
+       Called to delete a route table entry struct. We delete the array of endpoint 
+       pointers, but NOT the endpoints referenced as those are referenced from 
+       multiple entries.
+*/
+static void del_rte( void* st, void* entry, char const* name, void* thing, void* data ) {
+       rtable_ent_t*   rte;
+       int i;
+
+       if( (rte = (rtable_ent_t *) thing) == NULL ) {
+               return;
+       }
+
+       if( rte->rrgroups ) {                                                                   // clean up the round robin groups
+               for( i = 0; i < rte->nrrgroups; i++ ) {
+                       if( rte->rrgroups[i] ) {
+                               free( rte->rrgroups[i]->epts );                 // ditch list of endpoint pointers (end points are reused; don't trash them)
+                       }
+               }
+
+               free( rte->rrgroups );
+       }
+
+       free( rte );                                                                                    // finally, drop the potato
+}
+
+/*
+       Read an entire file into a buffer. We assume for route table files
+       they will be smallish and so this won't be a problem.
+       Returns a pointer to the buffer, or nil. Caller must free.
+       Terminates the buffer with a nil character for string processing.
+
+       If we cannot stat the file, we assume it's empty or missing and return
+       an empty buffer, as opposed to a nil, so the caller can generate defaults
+       or error if an empty/missing file isn't tolerated.
+*/
+static char* uta_fib( char* fname ) {
+       struct stat     stats;
+       off_t           fsize = 8192;   // size of the file
+       off_t           nread;                  // number of bytes read
+       int                     fd;
+       char*           buf;                    // input buffer
+       
+       if( (fd = open( fname, O_RDONLY )) >= 0 ) {
+               if( fstat( fd, &stats ) >= 0 ) {
+                       if( stats.st_size <= 0 ) {                                      // empty file
+                               close( fd );
+                               fd = -1;
+                       } else {
+                               fsize = stats.st_size;                                          // stat ok, save the file size
+                       }
+               } else {
+                       fsize = 8192;                                                           // stat failed, we'll leave the file open and try to read a default max of 8k
+               }
+       }
+
+       if( fd < 0 ) {                                                                                  // didn't open or empty
+               if( (buf = (char *) malloc( sizeof( char ) * 1 )) == NULL ) {
+                       return NULL;
+               }
+
+               *buf = 0;
+               return buf;
+       }
+
+       // add a size limit check here
+
+       if( (buf = (char *) malloc( sizeof( char ) * fsize + 2 )) == NULL ) {           // enough to add nil char to make string
+               close( fd );
+               errno = ENOMEM;
+               return NULL;
+       }
+
+       nread = read( fd, buf, fsize );
+       if( nread < 0 || nread > fsize ) {                                                      // failure of some kind
+               free( buf );
+               errno = EFBIG;                                                                                  // likely too much to handle
+               close( fd );
+               return NULL;
+       }
+
+       buf[nread] = 0;
+
+       close( fd );
+       return buf;
+}
+
+/*
+       Create and initialise a route table; Returns a pointer to the table struct.
+*/
+static route_table_t* uta_rt_init( ) {
+       route_table_t*  rt;
+
+       if( (rt = (route_table_t *) malloc( sizeof( route_table_t ) )) == NULL ) {
+               return NULL;
+       }
+
+       if( (rt->hash = rmr_sym_alloc( 509 )) == NULL ) {               // modest size, prime
+               free( rt );
+               return NULL;
+       }
+
+       return rt;
+}
+
+/*
+       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.
+*/
+static route_table_t* uta_rt_clone( route_table_t* srt ) {
+       endpoint_t*             ep;             // an endpoint 
+       route_table_t*  nrt;    // new route table
+       route_table_t*  art;    // active route table
+       void*   sst;                    // source symtab
+       void*   nst;                    // new symtab
+       thing_list_t things;
+       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;
+       }
+
+       things.nalloc = 2048;
+       things.nused = 0;
+       things.things = (void **) malloc( sizeof( void * ) * things.nalloc );
+       if( things.things == NULL ) {
+               free( nrt->hash );
+               free( nrt );
+               return NULL;
+       }
+
+       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
+       
+       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
+       }
+
+       free( things.things );
+       return nrt;
+}
+
+/*
+       Given a name, find the endpoint struct in the provided route table.
+*/
+static endpoint_t* uta_get_ep( route_table_t* rt, char const* ep_name ) {
+
+       if( rt == NULL || rt->hash == NULL || ep_name == NULL || *ep_name == 0 ) {
+               return NULL;
+       }
+
+       return rmr_sym_get( rt->hash, ep_name, 1 );
+}
+
+/*
+       Drop the given route table. Purge all type 0 entries, then drop the symtab itself.
+*/
+static void uta_rt_drop( route_table_t* rt ) {
+       if( rt == NULL ) {
+               return;
+       }
+
+       rmr_sym_foreach_class( rt->hash, 0, del_rte, NULL );            // free each rte referenced by the hash, but NOT the endpoints
+       rmr_sym_free( rt->hash );                                                                       // free all of the hash related data
+       free( rt );
+}
+
+/*
+       Look up and return the pointer to the endpoint stuct matching the given name.
+       If not in the hash, a new endpoint is created, added to the hash. Should always
+       return a pointer.
+*/
+static endpoint_t* rt_ensure_ep( route_table_t* rt, char const* ep_name ) {
+       endpoint_t*     ep;
+
+       if( !rt || !ep_name || ! *ep_name ) {
+               fprintf( stderr, "[WARN] rmr: rt_ensure:  internal mishap, something undefined rt=%p ep_name=%p\n", rt, ep_name );
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if( (ep = uta_get_ep( rt, ep_name )) == NULL ) {                                        // not there yet, make
+               if( (ep = (endpoint_t *) malloc( sizeof( *ep ) )) == NULL ) {
+                       fprintf( stderr, "[WARN] rmr: rt_ensure:  malloc failed for endpoint creation: %s\n", ep_name );
+                       errno = ENOMEM;
+                       return NULL;
+               }
+
+               ep->open = 0;                                   // not connected
+               ep->addr = uta_h2ip( ep_name );
+               ep->name = strdup( ep_name );
+
+               rmr_sym_put( rt->hash, ep_name, 1, ep );
+       }
+
+       return ep;
+}
+
+
+#endif