0f796e31373a3b5abfdbba3757fe9662af87af9e
[ric-plt/lib/rmr.git] / src / rmr / si / src / rtable_si_static.c
1 // vim: ts=4 sw=4 noet :
2 /*
3 ==================================================================================
4         Copyright (c) 2019-2020 Nokia
5         Copyright (c) 2018-2020 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:       rtable_si_static.c
23         Abstract:       Route table management functions which depend on the underlying
24                                 transport mechanism and thus cannot be included with the generic
25                                 route table functions.
26
27                                 This module is designed to be included by any module (main) needing
28                                 the static/private stuff.
29
30         Author:         E. Scott Daniels
31         Date:           29 November 2018
32 */
33
34 #ifndef rtable_static_c
35 #define rtable_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
51 /*
52         Mark an endpoint closed because it's in a failing state.
53 */
54 static void uta_ep_failed( endpoint_t* ep ) {
55         if( ep != NULL ) {
56                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "connection to %s was closed\n", ep->name );
57                 ep->open = FALSE;
58         }
59 }
60
61 /*
62         Establish a TCP connection to the indicated target (IP address).
63         Target assumed to be address:port.  The new socket is returned via the
64         user supplied pointer so that a success/fail code is returned directly.
65         Return value is 0 (false) on failure, 1 (true)  on success.
66
67         In order to support multi-threaded user applications we must hold a lock before
68         we attempt to create a dialer and connect. NNG is thread safe, but we can
69         get things into a bad state if we allow a collision here.  The lock grab
70         only happens on the intial session setup.
71 */
72 //static int uta_link2( si_ctx_t* si_ctx, endpoint_t* ep ) {
73 static int uta_link2( uta_ctx_t *ctx, endpoint_t* ep ) {
74         static int      flags = 0;
75         char*           target;
76         char            conn_info[SI_MAX_ADDR_LEN];     // string to give to nano to make the connection
77         char*           addr;
78         int                     state = FALSE;
79         char*           tok;
80
81         if( ep == NULL ) {
82                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "link2 ep was nil!\n" );
83                 return FALSE;
84         }
85
86         target = ep->name;                              // always give name to transport so changing dest IP does not break reconnect
87         if( target == NULL  ||  (addr = strchr( target, ':' )) == NULL ) {              // bad address:port
88                 if( ep->notify ) {
89                         rmr_vlog( RMR_VL_WARN, "rmr: link2: unable to create link: bad target: %s\n", target == NULL ? "<nil>" : target );
90                         ep->notify = 0;
91                 }
92                 return FALSE;
93         }
94
95         pthread_mutex_lock( &ep->gate );                        // grab the lock
96         if( ep->open ) {
97                 pthread_mutex_unlock( &ep->gate );
98                 return TRUE;
99         }
100
101         snprintf( conn_info, sizeof( conn_info ), "%s", target );
102         errno = 0;
103         if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "link2 attempting connection with: %s\n", conn_info );
104         if( (ep->nn_sock = SIconnect( ctx->si_ctx, conn_info )) < 0 ) {
105                 pthread_mutex_unlock( &ep->gate );
106
107                 if( ep->notify ) {                                                      // need to notify if set
108                         rmr_vlog( RMR_VL_WARN, "rmr: link2: unable to connect  to target: %s: %d %s\n", target, errno, strerror( errno ) );
109                         ep->notify = 0;
110                 }
111                 return FALSE;
112         }
113
114         if( DEBUG ) rmr_vlog( RMR_VL_INFO, "rmr_si_link2: connection was successful to: %s\n", target );
115
116         ep->open = TRUE;                                                // set open/notify before giving up lock
117         fd2ep_add( ctx, ep->nn_sock, ep );              // map fd to ep for disc cleanup (while we have the lock)
118
119         if( ! ep->notify ) {                                            // if we yammered about a failure, indicate finally good
120                 rmr_vlog( RMR_VL_INFO, "rmr: link2: connection finally establisehd with target: %s\n", target );
121                 ep->notify = 1;
122         }
123
124         pthread_mutex_unlock( &ep->gate );
125         return TRUE;
126 }
127
128 /*
129         This provides a protocol independent mechanism for establishing the connection to an endpoint.
130         Return is true (1) if the link was opened; false on error.
131 */
132 static int rt_link2_ep( void* vctx, endpoint_t* ep ) {
133         uta_ctx_t* ctx;
134
135         if( ep == NULL ) {
136                 return FALSE;
137         }
138
139         if( ep->open )  {                       // already open, do nothing
140                 return TRUE;
141         }
142
143         if( (ctx = (uta_ctx_t *) vctx) == NULL ) {
144                 return FALSE;
145         }
146
147         uta_link2( ctx, ep );
148         return ep->open;
149 }
150
151
152 /*
153         Add an endpoint to a route table entry for the group given. If the endpoint isn't in the
154         hash we add it and create the endpoint struct.
155
156         The caller must supply the specific route table (we assume it will be pending, but they
157         could live on the edge and update the active one, though that's not at all a good idea).
158 */
159 extern endpoint_t*  uta_add_ep( route_table_t* rt, rtable_ent_t* rte, char* ep_name, int group  ) {
160         endpoint_t*     ep;
161         rrgroup_t* rrg;                         // pointer at group to update
162
163         if( ! rte || ! rt ) {
164                 rmr_vlog( RMR_VL_WARN, "uda_add_ep didn't get a valid rt and/or rte pointer\n" );
165                 return NULL;
166         }
167
168         if( rte->nrrgroups <= group || group < 0 ) {
169                 rmr_vlog( RMR_VL_WARN, "uda_add_ep group out of range: %d (max == %d)\n", group, rte->nrrgroups );
170                 return NULL;
171         }
172
173         //fprintf( stderr, ">>>> add ep grp=%d to rte @ 0x%p  rrg=%p\n", group, rte, rte->rrgroups[group] );
174         if( (rrg = rte->rrgroups[group]) == NULL ) {
175                 if( (rrg = (rrgroup_t *) malloc( sizeof( *rrg ) )) == NULL ) {
176                         rmr_vlog( RMR_VL_WARN, "rmr_add_ep: malloc failed for round robin group: group=%d\n", group );
177                         return NULL;
178                 }
179                 memset( rrg, 0, sizeof( *rrg ) );
180
181                 if( (rrg->epts = (endpoint_t **) malloc( sizeof( endpoint_t ) * MAX_EP_GROUP )) == NULL ) {
182                         rmr_vlog( RMR_VL_WARN, "rmr_add_ep: malloc failed for group endpoint array: group=%d\n", group );
183                         return NULL;
184                 }
185                 memset( rrg->epts, 0, sizeof( endpoint_t ) * MAX_EP_GROUP );
186
187                 rte->rrgroups[group] = rrg;
188                 //fprintf( stderr, ">>>> added new rrg grp=%d to rte @ 0x%p  rrg=%p\n", group, rte, rte->rrgroups[group] );
189
190                 rrg->ep_idx = 0;                                                // next endpoint to send to
191                 rrg->nused = 0;                                                 // number populated
192                 rrg->nendpts = MAX_EP_GROUP;                    // number allocated
193
194                 if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "rrg added to rte: mtype=%d group=%d\n", rte->mtype, group );
195         }
196
197         ep = rt_ensure_ep( rt, ep_name );                       // get the ep and create one if not known
198
199         if( rrg != NULL ) {
200                 if( rrg->nused >= rrg->nendpts ) {
201                         // future: reallocate
202                         rmr_vlog( RMR_VL_WARN, "endpoint array for mtype/group %d/%d is full!\n", rte->mtype, group );
203                         return NULL;
204                 }
205
206                 rrg->epts[rrg->nused] = ep;
207                 rrg->nused++;
208         }
209
210         if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "endpoint added to mtype/group: %d/%d %s nused=%d\n", rte->mtype, group, ep_name, rrg->nused );
211         return ep;
212 }
213
214
215 /*
216         Given a name, find the nano socket needed to send to it. Returns the socket via
217         the user pointer passed in and sets the return value to true (1). If the
218         endpoint cannot be found false (0) is returned.
219 */
220 static int uta_epsock_byname( uta_ctx_t* ctx, char* ep_name, int* nn_sock, endpoint_t** uepp ) {
221         route_table_t*  rt;
222         si_ctx_t*               si_ctx;
223         endpoint_t*             ep;
224         int                             state = FALSE;
225
226         if( PARINOID_CHECKS ) {
227                 if( ctx == NULL || (rt = ctx->rtable) == NULL || (si_ctx = ctx->si_ctx) == NULL  ) {
228                         return FALSE;
229                 }
230         } else {
231                 rt = ctx->rtable;                               // faster but more risky
232                 si_ctx = ctx->si_ctx;
233         }
234
235         ep =  rmr_sym_get( rt->hash, ep_name, 1 );
236         if( uepp != NULL ) {                                                    // caller needs endpoint too, give it back
237                 *uepp = ep;
238         }
239         if( ep == NULL ) {
240                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "get ep by name for %s not in hash!\n", ep_name );
241                 if( ! ep_name || (ep = rt_ensure_ep( rt, ep_name)) == NULL ) {                          // create one if not in rt (support rts without entry in our table)
242                         return FALSE;
243                 }
244         }
245
246         if( ! ep->open )  {                                                                             // not open -- connect now
247                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "get ep by name for %s session not started... starting\n", ep_name );
248                 if( ep->addr == NULL ) {                                        // name didn't resolve before, try again
249                         ep->addr = strdup( ep->name );                  // use the name directly; if not IP then transport will do dns lookup
250                 }
251                 if( uta_link2( ctx, ep ) ) {                                                                                    // find entry in table and create link
252                         state = TRUE;
253                         ep->open = TRUE;
254                         *nn_sock = ep->nn_sock;                                                 // pass socket back to caller
255                         fd2ep_add( ctx, ep->nn_sock, ep );                              // map fd to this ep for disc cleanup
256                 }
257                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "epsock_bn: connection state: %s %s\n", state ? "[OK]" : "[FAIL]", ep->name );
258         } else {
259                 *nn_sock = ep->nn_sock;
260                 state = TRUE;
261         }
262
263         return state;
264 }
265
266 /*
267         Make a round robin selection within a round robin group for a route table
268         entry. Returns the nanomsg socket if there is a rte for the message
269         key, and group is defined. Socket is returned via pointer in the parm
270         list (nn_sock).
271
272         The group is the group number to select from.
273
274         The user supplied (via pointer to) integer 'more' will be set if there are
275         additional groups beyond the one selected. This allows the caller to
276         to easily iterate over the group list -- more is set when the group should
277         be incremented and the function invoked again. Groups start at 0.
278
279         The return value is true (>0) if the socket was found and *nn_sock was updated
280         and false (0) if there is no associated socket for the msg type, group combination.
281         We return the index+1 from the round robin table on success so that we can verify
282         during test that different entries are being seleted; we cannot depend on the nng
283         socket being different as we could with nano.
284
285         NOTE:   The round robin selection index increment might collide with other
286                 threads if multiple threads are attempting to send to the same round
287                 robin group; the consequences are small and avoid locking. The only side
288                 effect is either sending two messages in a row to, or skipping, an endpoint.
289                 Both of these, in the grand scheme of things, is minor compared to the
290                 overhead of grabbing a lock on each call.
291 */
292 static int uta_epsock_rr( uta_ctx_t* ctx, rtable_ent_t* rte, int group, int* more, int* nn_sock, endpoint_t** uepp ) {
293         si_ctx_t*               si_ctx;
294         endpoint_t*     ep;                             // selected end point
295         int  state = FALSE;                     // processing state
296         int dummy;
297         rrgroup_t* rrg;
298         int     idx;
299
300         if( PARINOID_CHECKS ) {
301                 if( ctx == NULL || (si_ctx = ctx->si_ctx) == NULL  ) {
302                         return FALSE;
303                 }
304         } else {
305                 si_ctx = ctx->si_ctx;
306         }
307
308         //fprintf( stderr, ">>>> epsock_rr selecting: grp=%d mtype=%d ngrps=%d\n", group, rte->mtype, rte->nrrgroups );
309
310         if( ! more ) {                          // eliminate cheks each time we need to use
311                 more = &dummy;
312         }
313
314         if( ! nn_sock ) {                       // user didn't supply a pointer
315                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "epsock_rr invalid nnsock pointer\n" );
316                 errno = EINVAL;
317                 *more = 0;
318                 return FALSE;
319         }
320
321         if( rte == NULL ) {
322                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "epsock_rr rte was nil; nothing selected\n" );
323                 *more = 0;
324                 return FALSE;
325         }
326
327         if( group < 0 || group >= rte->nrrgroups ) {
328                 if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "group out of range: group=%d max=%d\n", group, rte->nrrgroups );
329                 *more = 0;
330                 return FALSE;
331         }
332
333         if( (rrg = rte->rrgroups[group]) == NULL ) {
334                 if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "rrg not found for group %d (ptr rrgroups[g] == nil)\n", group );
335                 *more = 0;                                      // groups are inserted contig, so nothing should be after a nil pointer
336                 return FALSE;
337         }
338
339         *more = group < rte->nrrgroups-1 ? (rte->rrgroups[group+1] != NULL): 0; // more if something in next group slot
340
341         switch( rrg->nused ) {
342                 case 0:                         // nothing allocated, just punt
343                         if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "nothing allocated for the rrg\n" );
344                         return FALSE;
345
346                 case 1:                         // exactly one, no rr to deal with
347                         ep = rrg->epts[0];
348                         if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "_rr returning socket with one choice in group \n" );
349                         state = TRUE;
350                         break;
351
352                 default:                                                                                // need to pick one and adjust rr counts
353                         idx = rrg->ep_idx++ % rrg->nused;                       // see note above
354                         ep = rrg->epts[idx];                                            // select next endpoint
355                         if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "_rr returning socket with multiple choices in group idx=%d \n", rrg->ep_idx );
356                         state = idx + 1;                                                        // unit test checks to see that we're cycling through, so must not just be TRUE
357                         break;
358         }
359
360         if( uepp != NULL ) {                                                            // caller may need refernce to endpoint too; give it if pointer supplied
361                 *uepp = ep;
362         }
363         if( state ) {                                                                           // end point selected, open if not, get socket either way
364                 if( ! ep->open ) {                                                              // not connected
365                         if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "epsock_rr selected endpoint not yet open; opening %s\n", ep->name );
366                         if( ep->addr == NULL ) {                                        // name didn't resolve before, try again
367                                 ep->addr = strdup( ep->name );                  // use the name directly; if not IP then transport will do dns lookup
368                         }
369
370                         if( uta_link2( ctx, ep ) ) {                                                                                    // find entry in table and create link
371                                 ep->open = TRUE;
372                                 *nn_sock = ep->nn_sock;                                                 // pass socket back to caller
373                                 fd2ep_add( ctx, ep->nn_sock, ep );                              // map fd to ep for disc cleanup
374                         } else {
375                                 state = FALSE;
376                         }
377                         if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "epsock_rr: connection attempted with %s: %s\n", ep->name, state ? "[OK]" : "[FAIL]" );
378                 } else {
379                         *nn_sock = ep->nn_sock;
380                 }
381         }
382
383         if( DEBUG > 1 ) rmr_vlog( RMR_VL_DEBUG, "epsock_rr returns state=%d\n", state );
384         return state;
385 }
386
387 /*
388         Given a message, use the meid field to find the owner endpoint for the meid.
389         The owner ep is then used to extract the socket through which the message
390         is sent. This returns TRUE if we found a socket and it was written to the
391         nn_sock pointer; false if we didn't.
392
393         We've been told that the meid is a string, thus we count on it being a nil
394         terminated set of bytes.
395
396         If we return false the caller's ep reference may NOT be valid or even nil.
397 */
398 static int epsock_meid( uta_ctx_t* ctx, route_table_t *rtable, rmr_mbuf_t* msg, int* nn_sock, endpoint_t** uepp ) {
399         endpoint_t*     ep;                             // seected end point
400         int     state = FALSE;                  // processing state
401         char*   meid;
402         si_ctx_t*       si_ctx;
403
404         if( PARINOID_CHECKS ) {
405                 if( ctx == NULL || (si_ctx = ctx->si_ctx) == NULL  ) {
406                         return FALSE;
407                 }
408         } else {
409                 si_ctx = ctx->si_ctx;
410         }
411
412         errno = 0;
413         if( ! nn_sock || msg == NULL || rtable == NULL ) {                      // missing stuff; bail fast
414                 errno = EINVAL;
415                 return FALSE;
416         }
417
418         meid = ((uta_mhdr_t *) msg->header)->meid;
419
420         ep = get_meid_owner( rtable, meid );
421         if( uepp != NULL ) {                                                            // caller needs refernce to endpoint too
422                 *uepp = ep;
423         }
424
425         if( ep == NULL ) {
426                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "epsock_meid: no ep in hash for (%s)\n", meid );
427                 return FALSE;
428         }
429
430         state = TRUE;
431         if( ! ep->open ) {                                                              // not connected
432                 if( ep->addr == NULL ) {                                        // name didn't resolve before, try again
433                         ep->addr = strdup( ep->name );                  // use the name directly; if not IP then transport will do dns lookup
434                 }
435
436                 if( uta_link2( ctx, ep ) ) {                            // find entry in table and create link
437                         ep->open = TRUE;
438                         *nn_sock = ep->nn_sock;                                 // pass socket back to caller
439                 } else {
440                         state = FALSE;
441                 }
442                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "epsock_meid: connection attempted with %s: %s\n", ep->name, state ? "[OK]" : "[FAIL]" );
443         } else {
444                 *nn_sock = ep->nn_sock;
445         }
446
447         return state;
448 }
449
450 /*
451         Finds the rtable entry which matches the key. Returns a nil pointer if
452         no entry is found. If try_alternate is set, then we will attempt
453         to find the entry with a key based only on the message type.
454 */
455 static inline rtable_ent_t*  uta_get_rte( route_table_t *rt, int sid, int mtype, int try_alt ) {
456         uint64_t key;                   // key is sub id and mtype banged together
457         rtable_ent_t* rte;              // the entry we found
458
459         if( rt == NULL || rt->hash == NULL ) {
460                 return NULL;
461         }
462
463         key = build_rt_key( sid, mtype );                                                                                       // first try with a 'full' key
464         if( ((rte = rmr_sym_pull( rt->hash, key )) != NULL)  ||  ! try_alt ) {          // found or not allowed to try the alternate, return what we have
465                 return rte;
466         }
467
468         if( sid != UNSET_SUBID ) {                                                              // not found, and allowed to try alternate; and the sub_id was set
469                 key = build_rt_key( UNSET_SUBID, mtype );                       // rebuild key
470                 rte = rmr_sym_pull( rt->hash, key );                            // see what we get with this
471         }
472
473         return rte;
474 }
475
476 /*
477         Return a string of count information. E.g.:
478                 <ep-name>:<port> <good> <hard-fail> <soft-fail>
479
480         Caller must free the string allocated if a buffer was not provided.
481
482         Pointer returned is to a freshly allocated string, or the user buffer
483         for convenience.
484
485         If the endpoint passed is a nil pointer, then we return a nil -- caller
486         must check!
487 */
488 static inline char* get_ep_counts( endpoint_t* ep, char* ubuf, int ubuf_len ) {
489         char*   rs;                     // result string
490
491         if( ep == NULL ) {
492                 return NULL;
493         }
494
495         if( ubuf != NULL ) {
496                 rs = ubuf;
497         } else {
498                 ubuf_len = 256;
499                 rs = malloc( sizeof( char ) * ubuf_len );
500         }
501
502         snprintf( rs, ubuf_len, "%s %lld %lld %lld", ep->name, ep->scounts[EPSC_GOOD], ep->scounts[EPSC_FAIL], ep->scounts[EPSC_TRANS] );
503
504         return rs;
505 }
506
507
508 // ---- fd to ep functions --------------------------------------------------------------------------
509
510 /*
511         Create the hash which maps file descriptors to endpoints. We need this
512         to easily mark an endpoint as disconnected when we are notified. Thus we
513         expect these to be driven very seldomly; locking should not be an issue.
514         Locking is needed to prevent problems when the user application is multi-
515         threaded and attempting to (re)connect from concurrent threads.
516 */
517 static void fd2ep_init( uta_ctx_t* ctx ) {
518
519         if( ctx  && ! ctx->fd2ep ) {
520                 ctx->fd2ep = rmr_sym_alloc( 129 );
521
522                 if( ctx->fd2ep_gate == NULL ) {
523                         ctx->fd2ep_gate = (pthread_mutex_t *) malloc( sizeof( *ctx->fd2ep_gate ) );
524                         if( ctx->fd2ep_gate != NULL ) {
525                                 pthread_mutex_init( ctx->fd2ep_gate, NULL );
526                         }
527                 }
528         }
529 }
530
531 /*
532         Add an entry into the fd2ep hash to map the FD to the endpoint.
533 */
534 static void fd2ep_add( uta_ctx_t* ctx, int fd, endpoint_t* ep ) {
535         if( ctx && ctx->fd2ep ) {
536                 pthread_mutex_lock( ctx->fd2ep_gate );
537
538                 rmr_sym_map( ctx->fd2ep, (uint64_t) fd, (void *) ep );
539
540                 pthread_mutex_unlock( ctx->fd2ep_gate );
541         }
542 }
543
544 /*
545         Given a file descriptor this fetches the related endpoint from the hash and
546         deletes the entry from the hash (when we detect a disconnect).
547
548         This will also set the state on the ep open to false, and revoke the
549         FD (nn_socket).
550 */
551 static endpoint_t*  fd2ep_del( uta_ctx_t* ctx, int fd ) {
552         endpoint_t* ep = NULL;
553
554         if( ctx && ctx->fd2ep ) {
555                 ep = rmr_sym_pull(  ctx->fd2ep, (uint64_t) fd );
556                 if( ep ) {
557                         pthread_mutex_lock( ctx->fd2ep_gate );
558
559                         rmr_sym_ndel(  ctx->fd2ep, (uint64_t) fd );
560
561                         pthread_mutex_unlock( ctx->fd2ep_gate );
562                 }
563         }
564
565         return ep;
566 }
567
568 /*
569         Given a file descriptor fetches the related endpoint from the hash.
570         Returns nil if there is no reference in the hash.
571 */
572 static endpoint_t*  fd2ep_get( uta_ctx_t* ctx, int fd ) {
573         endpoint_t* ep = NULL;
574
575         if( ctx && ctx->fd2ep ) {
576                 pthread_mutex_lock( ctx->fd2ep_gate );
577
578                 ep = rmr_sym_pull(  ctx->fd2ep, (uint64_t) fd );
579
580                 pthread_mutex_unlock( ctx->fd2ep_gate );
581         }
582
583         return ep;
584 }
585
586
587 #endif