Initial commit of RMR Library
[ric-plt/lib/rmr.git] / src / common / src / rt_generic_static.c
1 // :vi sw=4 ts=4 noet:
2 /*
3 ==================================================================================
4         Copyright (c) 2019 Nokia 
5         Copyright (c) 2018-2019 AT&T Intellectual Property.
6
7    Licensed under the Apache License, Version 2.0 (the "License");
8    you may not use this file except in compliance with the License.
9    You may obtain a copy of the License at
10
11        http://www.apache.org/licenses/LICENSE-2.0
12
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18 ==================================================================================
19 */
20
21 /*
22         Mnemonic:       rt_generic_static.c
23         Abstract:       These are route table functions which are not specific to the 
24                                 underlying protocol.  rtable_static, and rtable_nng_static
25                                 have transport provider specific code.
26
27                                 This file must be included before the nng/nano specific file as
28                                 it defines types. 
29
30         Author:         E. Scott Daniels
31         Date:           5  February 2019
32 */
33
34 #ifndef rt_generic_static_c
35 #define rt_generic_static_c
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <netdb.h>
40 #include <errno.h>
41 #include <string.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <unistd.h>
47
48
49 /*
50         Passed to a symtab foreach callback to construct a list of pointers from 
51         a current symtab.
52 */
53 typedef struct thing_list {
54         int nalloc;
55         int nused;
56         void** things;
57 } thing_list_t;
58
59
60 /*
61         Given a message type create a route table entry and add to the hash keyed on the
62         message type.  Once in the hash, endpoints can be added with uta_add_ep. Size
63         is the number of group slots to allocate in the entry.
64 */
65 static rtable_ent_t* uta_add_rte( route_table_t* rt, int mtype, int nrrgroups ) {
66         rtable_ent_t* rte;
67
68         if( rt == NULL ) {
69                 return NULL;
70         }
71
72         if( (rte = (rtable_ent_t *) malloc( sizeof( *rte ) )) == NULL ) {
73                 fprintf( stderr, "rmr_add_rte: malloc failed for entry\n" );
74                 return NULL;
75         }
76         memset( rte, 0, sizeof( *rte ) );
77
78         if( nrrgroups <= 0 ) {
79                 nrrgroups = 10;
80         }
81
82         if( (rte->rrgroups = (rrgroup_t **) malloc( sizeof( rrgroup_t * ) * nrrgroups )) == NULL ) {
83                 fprintf( stderr, "rmr_add_rte: malloc failed for rrgroup array\n" );
84                 free( rte );
85                 return NULL;
86         }
87         memset( rte->rrgroups, 0, sizeof( rrgroup_t *) * nrrgroups );
88         rte->nrrgroups = nrrgroups;
89
90         rmr_sym_map( rt->hash, mtype, rte );                                                    // add to hash using numeric mtype as key
91
92         if( DEBUG ) fprintf( stderr, "[DBUG] route table entry created: mt=%d groups=%d\n", mtype, nrrgroups );
93         return rte;
94 }
95
96 /*
97         Parse a single record recevied from the route table generator, or read
98         from a static route table file.  Start records cause a new table to 
99         be started (if a partial table was received it is discarded. Table
100         entry records are added to the currenly 'in progress' table, and an 
101         end record causes the in progress table to be finalised and the
102         currently active table is replaced.
103 */
104 static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) {
105         int i;
106         int ntoks;                                                      // number of tokens found in something
107         int ngtoks;
108         int     grp;                                                    // group number
109         rtable_ent_t*   rte;                            // route table entry added
110         char*   tokens[128];
111         char*   gtokens[64];                            // groups
112         char*   tok;                                            // pointer into a token or string
113
114         if( ! buf ) {
115                 return;
116         }
117
118         while( *buf && isspace( *buf ) ) {                                                      // skip leading whitespace
119                 buf++;
120         }
121         for( tok = buf + (strlen( buf ) - 1); tok > buf && isspace( *tok ); tok-- );    // trim trailing spaces too
122         *(tok+1) = 0;
123
124         if( (ntoks = uta_tokenise( buf, tokens, 128, '|' )) > 0 ) {
125                 switch( *(tokens[0]) ) {
126                         case 0:                                                                                                 // ignore blanks
127                                 // fallthrough
128                         case '#':                                                                                               // and comment lines
129                                 break;
130
131                         case 'n':                                                                                               // newrt|{start|end}
132                                 if( strcmp( tokens[1], "end" ) == 0 ) {                         // wrap up the table we were building
133                                         if( ctx->new_rtable ) {
134                                                 uta_rt_drop( ctx->old_rtable );                         // time to drop one that was previously replaced
135                                                 ctx->old_rtable = ctx->rtable;                          // currently active becomes old and allowed to 'drain'
136                                                 ctx->rtable = ctx->new_rtable;                          // one we've been adding to becomes active
137                                                 ctx->new_rtable = NULL;
138                                                 if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] end of route table noticed\n" );
139                                         } else {
140                                                 if( DEBUG > 1 ) fprintf( stderr, "[DBUG] end of route table noticed, but one was not started!\n" );
141                                                 ctx->new_rtable = NULL;
142                                         }
143                                 } else {                                                                                        // start a new table.
144                                         if( ctx->new_rtable != NULL ) {                                 // one in progress?  this forces it out
145                                                 if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] new table; dropping incomplete table\n" );
146                                                 uta_rt_drop( ctx->new_rtable );
147                                         }       
148
149                                         if( ctx->rtable )  {
150                                                 ctx->new_rtable = uta_rt_clone( ctx->rtable );  // create by cloning endpoint entries from active table
151                                         } else {
152                                                 ctx->new_rtable = uta_rt_init(  );                              // don't have one yet, just crate empty
153                                         }
154                                         if( DEBUG > 1 || (vlevel > 1)  ) fprintf( stderr, "[DBUG] start of route table noticed\n" );
155                                 }
156                                 break;
157
158                         case 'r':                                       // assume rt entry
159                                 if( ! ctx->new_rtable ) {                       // bad sequence, or malloc issue earlier; ignore siliently
160                                         break;
161                                 }
162
163                                 if( ((tok = strchr( tokens[1], ',' )) == NULL ) ||                                      // no sender names
164                                         (uta_has_str( tokens[1],  ctx->my_name, ',', 127) >= 0) ||              // our name isn't in the list
165                                         has_myip( tokens[1], ctx->ip_list, ',', 127 ) ) {                               // the list has one of our IP addresses 
166
167                                         if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] create rte for mtype=%s\n", tokens[1] );
168                                         
169                                         if( (ngtoks = uta_tokenise( tokens[2], gtokens, 64, ';' )) > 0 ) {                                      // split last field by groups first
170                                                 rte = uta_add_rte( ctx->new_rtable, atoi( tokens[1] ), ngtoks );                        // get/create entry for message type
171                                                 for( grp = 0; grp < ngtoks; grp++ ) {
172                                                         if( (ntoks = uta_tokenise( gtokens[grp], tokens, 64, ',' )) > 0 ) {
173                                                                 for( i = 0; i < ntoks; i++ ) {
174                                                                         if( DEBUG > 1  || (vlevel > 1)) fprintf( stderr, "[DBUG]    add endpoint  %s\n", tokens[i] );
175                                                                         uta_add_ep( ctx->new_rtable, rte, tokens[i], grp );
176                                                                 }
177                                                         }
178                                                 }
179                                         }
180                                 } else {
181                                         if( DEBUG || (vlevel > 2) ) 
182                                                 fprintf( stderr, "entry not included, sender not matched: %s\n", tokens[1] );
183                                 }
184
185                                 break;
186
187                         default:
188                                 if( DEBUG ) fprintf( stderr, "[WRN] rmr_rtc: unrecognised request: %s\n", tokens[0] );
189                                 break;
190                 }
191         }
192 }
193
194 /*
195         This function attempts to open a static route table in order to create a 'seed'
196         table during initialisation.  The environment variable RMR_SEED_RT is expected
197         to contain the necessary path to the file. If missing, or if the file is empty,
198         no route table will be available until one is received from the generator. 
199
200         This function is probably most useful for testing situations, or extreme 
201         cases where the routes are static.
202 */
203 static void read_static_rt( uta_ctx_t* ctx, int vlevel ) {
204         int             i;
205         char*   fname;
206         char*   fbuf;                           // buffer with file contents
207         char*   rec;                            // start of the record
208         char*   eor;                            // end of the record
209         int             rcount = 0;                     // record count for debug
210
211         if( (fname = getenv( ENV_SEED_RT )) == NULL ) {
212                 return;
213         }
214
215         if( (fbuf = uta_fib( fname ) ) == NULL ) {                      // read file into a single buffer
216                 fprintf( stderr, "[WRN] seed route table could not be opened: %s: %s\n", fname, strerror( errno ) );
217                 return;
218         }
219
220         if( DEBUG ) fprintf( stderr, "[INFO] seed route table successfully opened: %s\n", fname );
221         for( rec = fbuf; rec && *rec; rec = eor+1 ) {
222                 rcount++;
223                 if( (eor = strchr( rec, '\n' )) != NULL ) {
224                         *eor = 0;
225                 }
226
227                 parse_rt_rec( ctx, rec, vlevel );
228         }
229
230         if( DEBUG ) fprintf( stderr, "[INFO] seed route table successfully parsed: %d records\n", rcount );
231         free( fbuf );
232 }
233
234 /*
235         Callback driven for each named thing in a symtab. We collect the pointers to those
236         things for later use (cloning).
237 */
238 static void collect_things( void* st, void* entry, char const* name, void* thing, void* vthing_list ) {
239         thing_list_t*   tl;
240
241         if( (tl = (thing_list_t *) vthing_list) == NULL ) {
242                 return;
243         }
244
245         if( thing == NULL ) {
246                 return;
247         }
248
249         tl->things[tl->nused++] = thing;                // save a reference to the thing
250 }
251
252 /*
253         Called to delete a route table entry struct. We delete the array of endpoint 
254         pointers, but NOT the endpoints referenced as those are referenced from 
255         multiple entries.
256 */
257 static void del_rte( void* st, void* entry, char const* name, void* thing, void* data ) {
258         rtable_ent_t*   rte;
259         int i;
260
261         if( (rte = (rtable_ent_t *) thing) == NULL ) {
262                 return;
263         }
264
265         if( rte->rrgroups ) {                                                                   // clean up the round robin groups
266                 for( i = 0; i < rte->nrrgroups; i++ ) {
267                         if( rte->rrgroups[i] ) {
268                                 free( rte->rrgroups[i]->epts );                 // ditch list of endpoint pointers (end points are reused; don't trash them)
269                         }
270                 }
271
272                 free( rte->rrgroups );
273         }
274
275         free( rte );                                                                                    // finally, drop the potato
276 }
277
278 /*
279         Read an entire file into a buffer. We assume for route table files
280         they will be smallish and so this won't be a problem.
281         Returns a pointer to the buffer, or nil. Caller must free.
282         Terminates the buffer with a nil character for string processing.
283
284         If we cannot stat the file, we assume it's empty or missing and return
285         an empty buffer, as opposed to a nil, so the caller can generate defaults
286         or error if an empty/missing file isn't tolerated.
287 */
288 static char* uta_fib( char* fname ) {
289         struct stat     stats;
290         off_t           fsize = 8192;   // size of the file
291         off_t           nread;                  // number of bytes read
292         int                     fd;
293         char*           buf;                    // input buffer
294         
295         if( (fd = open( fname, O_RDONLY )) >= 0 ) {
296                 if( fstat( fd, &stats ) >= 0 ) {
297                         if( stats.st_size <= 0 ) {                                      // empty file
298                                 close( fd );
299                                 fd = -1;
300                         } else {
301                                 fsize = stats.st_size;                                          // stat ok, save the file size
302                         }
303                 } else {
304                         fsize = 8192;                                                           // stat failed, we'll leave the file open and try to read a default max of 8k
305                 }
306         }
307
308         if( fd < 0 ) {                                                                                  // didn't open or empty
309                 if( (buf = (char *) malloc( sizeof( char ) * 1 )) == NULL ) {
310                         return NULL;
311                 }
312
313                 *buf = 0;
314                 return buf;
315         }
316
317         // add a size limit check here
318
319         if( (buf = (char *) malloc( sizeof( char ) * fsize + 2 )) == NULL ) {           // enough to add nil char to make string
320                 close( fd );
321                 errno = ENOMEM;
322                 return NULL;
323         }
324
325         nread = read( fd, buf, fsize );
326         if( nread < 0 || nread > fsize ) {                                                      // failure of some kind
327                 free( buf );
328                 errno = EFBIG;                                                                                  // likely too much to handle
329                 close( fd );
330                 return NULL;
331         }
332
333         buf[nread] = 0;
334
335         close( fd );
336         return buf;
337 }
338
339 /*
340         Create and initialise a route table; Returns a pointer to the table struct.
341 */
342 static route_table_t* uta_rt_init( ) {
343         route_table_t*  rt;
344
345         if( (rt = (route_table_t *) malloc( sizeof( route_table_t ) )) == NULL ) {
346                 return NULL;
347         }
348
349         if( (rt->hash = rmr_sym_alloc( 509 )) == NULL ) {               // modest size, prime
350                 free( rt );
351                 return NULL;
352         }
353
354         return rt;
355 }
356
357 /*
358         Clone (sort of) an existing route table.  This is done to preserve the endpoint
359         names referenced in a table (and thus existing sessions) when a new set
360         of message type to endpoint name mappings is received.  A new route table
361         with only endpoint name references is returned based on the active table in
362         the context.
363 */
364 static route_table_t* uta_rt_clone( route_table_t* srt ) {
365         endpoint_t*             ep;             // an endpoint 
366         route_table_t*  nrt;    // new route table
367         route_table_t*  art;    // active route table
368         void*   sst;                    // source symtab
369         void*   nst;                    // new symtab
370         thing_list_t things;
371         int i;
372
373         if( srt == NULL ) {
374                 return NULL;
375         }
376
377         if( (nrt = (route_table_t *) malloc( sizeof( *nrt ) )) == NULL ) {
378                 return NULL;
379         }
380
381         if( (nrt->hash = rmr_sym_alloc( 509 )) == NULL ) {              // modest size, prime
382                 free( nrt );
383                 return NULL;
384         }
385
386         things.nalloc = 2048;
387         things.nused = 0;
388         things.things = (void **) malloc( sizeof( void * ) * things.nalloc );
389         if( things.things == NULL ) {
390                 free( nrt->hash );
391                 free( nrt );
392                 return NULL;
393         }
394
395         sst = srt->hash;                                                                                        // convenience pointers (src symtab)
396         nst = nrt->hash;
397
398         rmr_sym_foreach_class( sst, 1, collect_things, &things );               // collect the named endpoints in the active table
399         
400         for( i = 0; i < things.nused; i++ ) {
401                 ep = (endpoint_t *) things.things[i];
402                 rmr_sym_put( nst, ep->name, 1, ep );                                            // slam this one into the new table
403         }
404
405         free( things.things );
406         return nrt;
407 }
408
409 /*
410         Given a name, find the endpoint struct in the provided route table.
411 */
412 static endpoint_t* uta_get_ep( route_table_t* rt, char const* ep_name ) {
413
414         if( rt == NULL || rt->hash == NULL || ep_name == NULL || *ep_name == 0 ) {
415                 return NULL;
416         }
417
418         return rmr_sym_get( rt->hash, ep_name, 1 );
419 }
420
421 /*
422         Drop the given route table. Purge all type 0 entries, then drop the symtab itself.
423 */
424 static void uta_rt_drop( route_table_t* rt ) {
425         if( rt == NULL ) {
426                 return;
427         }
428
429         rmr_sym_foreach_class( rt->hash, 0, del_rte, NULL );            // free each rte referenced by the hash, but NOT the endpoints
430         rmr_sym_free( rt->hash );                                                                       // free all of the hash related data
431         free( rt );
432 }
433
434 /*
435         Look up and return the pointer to the endpoint stuct matching the given name.
436         If not in the hash, a new endpoint is created, added to the hash. Should always
437         return a pointer.
438 */
439 static endpoint_t* rt_ensure_ep( route_table_t* rt, char const* ep_name ) {
440         endpoint_t*     ep;
441
442         if( !rt || !ep_name || ! *ep_name ) {
443                 fprintf( stderr, "[WARN] rmr: rt_ensure:  internal mishap, something undefined rt=%p ep_name=%p\n", rt, ep_name );
444                 errno = EINVAL;
445                 return NULL;
446         }
447
448         if( (ep = uta_get_ep( rt, ep_name )) == NULL ) {                                        // not there yet, make
449                 if( (ep = (endpoint_t *) malloc( sizeof( *ep ) )) == NULL ) {
450                         fprintf( stderr, "[WARN] rmr: rt_ensure:  malloc failed for endpoint creation: %s\n", ep_name );
451                         errno = ENOMEM;
452                         return NULL;
453                 }
454
455                 ep->open = 0;                                   // not connected
456                 ep->addr = uta_h2ip( ep_name );
457                 ep->name = strdup( ep_name );
458
459                 rmr_sym_put( rt->hash, ep_name, 1, ep );
460         }
461
462         return ep;
463 }
464
465
466 #endif