8d2cdc84e11a1499010c4d82633d87835d89c4c5
[oweals/gnunet.git] / src / ats / ats_api_scheduling.c
1 /*
2      This file is part of GNUnet.
3      (C) 2010-2015 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file ats/ats_api_scheduling.c
22  * @brief automatic transport selection and outbound bandwidth determination
23  * @author Christian Grothoff
24  * @author Matthias Wachs
25  *
26  * TODO:
27  * - we could avoid a linear scan over the
28  *   active addresses in some cases, so if
29  *   there is need, we can still optimize here
30  * - we might want to split off the logic to
31  *   determine LAN vs. WAN, as it has nothing
32  *   to do with accessing the ATS service.
33  */
34 #include "platform.h"
35 #include "gnunet_ats_service.h"
36 #include "ats.h"
37
38 /**
39  * How frequently do we scan the interfaces for changes to the addresses?
40  */
41 #define INTERFACE_PROCESSING_INTERVAL GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)
42
43
44 /**
45  * Session ID we use if there is no session / slot.
46  */
47 #define NOT_FOUND 0
48
49
50 /**
51  * Information we track per address, incoming or outgoing.  It also
52  * doesn't matter if we have a session, any address that ATS is
53  * allowed to suggest right now should be tracked.
54  */
55 struct GNUNET_ATS_AddressRecord
56 {
57
58   /**
59    * Scheduling handle this address record belongs to.
60    */
61   struct GNUNET_ATS_SchedulingHandle *sh;
62
63   /**
64    * Address data.
65    */
66   struct GNUNET_HELLO_Address *address;
67
68   /**
69    * Session handle.  NULL if we have an address but no
70    * active session for this address.
71    */
72   struct Session *session;
73
74   /**
75    * Array with performance data about the address.
76    */
77   struct GNUNET_ATS_Information *ats;
78
79   /**
80    * Number of entries in @e ats.
81    */
82   uint32_t ats_count;
83
84   /**
85    * Which slot (index) in the session array does
86    * this record correspond to?  FIXME:
87    * FIXME: a linear search on this is really crappy!
88    * Maybe switch to a 64-bit global counter and be
89    * done with it?  Or does that then cause too much
90    * trouble on the ATS-service side?
91    */
92   uint32_t slot;
93
94   /**
95    * Is this address currently in use?  In use means
96    * that the transport service will use this address
97    * for sending.
98    */
99   int in_use;
100
101   /**
102    * We're about to destroy this address record, just ATS does
103    * not know this yet.  Once ATS confirms its destruction,
104    * we can clean up.
105    */
106   int in_destroy;
107 };
108
109
110 /**
111  * We keep a list of our local networks so we can answer
112  * LAN vs. WAN questions.  Note: WLAN is not detected yet.
113  * (maybe we can do that heuristically based on interface
114  * name in the future?).
115  *
116  * FIXME: should this be part of the ATS scheduling API?
117  * Seems to be more generic and independent of ATS.
118  */
119 struct ATS_Network
120 {
121   /**
122    * Kept in a DLL.
123    */
124   struct ATS_Network *next;
125
126   /**
127    * Kept in a DLL.
128    */
129   struct ATS_Network *prev;
130
131   /**
132    * Network address.
133    */
134   struct sockaddr *network;
135
136   /**
137    * Netmask to determine what is in the LAN.
138    */
139   struct sockaddr *netmask;
140
141   /**
142    * How long are @e network and @e netmask?
143    */
144   socklen_t length;
145 };
146
147
148 /**
149  * Handle for ATS address suggestion requests.
150  */
151 struct GNUNET_ATS_SuggestHandle
152 {
153   /**
154    * ID of the peer for which address suggestion was requested.
155    */
156   struct GNUNET_PeerIdentity id;
157 };
158
159
160 /**
161  * Handle to the ATS subsystem for bandwidth/transport scheduling information.
162  */
163 struct GNUNET_ATS_SchedulingHandle
164 {
165
166   /**
167    * Our configuration.
168    */
169   const struct GNUNET_CONFIGURATION_Handle *cfg;
170
171   /**
172    * Callback to invoke on suggestions.
173    */
174   GNUNET_ATS_AddressSuggestionCallback suggest_cb;
175
176   /**
177    * Closure for @e suggest_cb.
178    */
179   void *suggest_cb_cls;
180
181   /**
182    * Map with the identities of all the peers for which we would
183    * like to have address suggestions.  The key is the PID, the
184    * value is currently the `struct GNUNET_ATS_SuggestHandle`
185    */
186   struct GNUNET_CONTAINER_MultiPeerMap *sug_requests;
187
188   /**
189    * Connection to ATS service.
190    */
191   struct GNUNET_CLIENT_Connection *client;
192
193   /**
194    * Message queue for sending requests to the ATS service.
195    */
196   struct GNUNET_MQ_Handle *mq;
197
198   /**
199    * Head of LAN networks list.
200    */
201   struct ATS_Network *net_head;
202
203   /**
204    * Tail of LAN networks list.
205    */
206   struct ATS_Network *net_tail;
207
208   /**
209    * Array of session objects (we need to translate them to numbers and back
210    * for the protocol; the offset in the array is the session number on the
211    * network).  Index 0 is always NULL and reserved to represent the NULL pointer.
212    * Unused entries are also NULL.
213    */
214   struct GNUNET_ATS_AddressRecord **session_array;
215
216   /**
217    * Task to trigger reconnect.
218    */
219   struct GNUNET_SCHEDULER_Task *task;
220
221   /**
222    * Task for periodically refreshing our LAN network list.
223    */
224   struct GNUNET_SCHEDULER_Task *interface_task;
225
226   /**
227    * Size of the @e session_array.
228    */
229   unsigned int session_array_size;
230
231 };
232
233
234 /**
235  * Re-establish the connection to the ATS service.
236  *
237  * @param sh handle to use to re-connect.
238  */
239 static void
240 reconnect (struct GNUNET_ATS_SchedulingHandle *sh);
241
242
243 /**
244  * Re-establish the connection to the ATS service.
245  *
246  * @param cls handle to use to re-connect.
247  * @param tc scheduler context
248  */
249 static void
250 reconnect_task (void *cls,
251                 const struct GNUNET_SCHEDULER_TaskContext *tc)
252 {
253   struct GNUNET_ATS_SchedulingHandle *sh = cls;
254
255   sh->task = NULL;
256   reconnect (sh);
257 }
258
259
260 /**
261  * Disconnect from ATS and then reconnect.
262  *
263  * @param sh our handle
264  */
265 static void
266 force_reconnect (struct GNUNET_ATS_SchedulingHandle *sh)
267 {
268   if (NULL != sh->mq)
269   {
270     GNUNET_MQ_destroy (sh->mq);
271     sh->mq = NULL;
272   }
273   if (NULL != sh->client)
274   {
275     GNUNET_CLIENT_disconnect (sh->client);
276     sh->client = NULL;
277   }
278   sh->task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
279                                            &reconnect_task,
280                                            sh);
281 }
282
283
284 /**
285  * Find the session object corresponding to the given session ID.
286  *
287  * @param sh our handle
288  * @param session_id current session ID
289  * @param peer peer the session belongs to
290  * @return the session object (or NULL)
291  */
292 static struct GNUNET_ATS_AddressRecord *
293 find_session (struct GNUNET_ATS_SchedulingHandle *sh,
294               uint32_t session_id,
295               const struct GNUNET_PeerIdentity *peer)
296 {
297   struct GNUNET_ATS_AddressRecord *ar;
298
299   if (session_id >= sh->session_array_size)
300   {
301     GNUNET_break (0);
302     return NULL;
303   }
304   if (0 == session_id)
305     return NULL;
306   ar = sh->session_array[session_id];
307   if (NULL == ar)
308   {
309     GNUNET_break (0);
310     return NULL;
311   }
312   if (NULL == ar->address)
313   {
314     /* address was destroyed in the meantime, this can happen
315        as we communicate asynchronously with the ATS service. */
316     return NULL;
317   }
318   if (0 != memcmp (peer,
319                    &ar->address->peer,
320                    sizeof (struct GNUNET_PeerIdentity)))
321   {
322     GNUNET_break (0);
323     force_reconnect (sh);
324     return NULL;
325   }
326   return ar;
327 }
328
329
330 /**
331  * Get an available session ID.
332  *
333  * @param sh our handle
334  * @return an unused slot, but never NOT_FOUND (0)
335  */
336 static uint32_t
337 find_empty_session_slot (struct GNUNET_ATS_SchedulingHandle *sh)
338 {
339   static uint32_t off;
340   uint32_t i;
341
342   i = 0;
343   while ( ( (NOT_FOUND == off) ||
344             (NULL != sh->session_array[off % sh->session_array_size]) ) &&
345           (i < sh->session_array_size) )
346   {
347     off++;
348     i++;
349   }
350   if ( (NOT_FOUND != off % sh->session_array_size) &&
351        (NULL == sh->session_array[off % sh->session_array_size]) )
352     return off;
353   i = sh->session_array_size;
354   GNUNET_array_grow (sh->session_array,
355                      sh->session_array_size,
356                      sh->session_array_size * 2);
357   return i;
358 }
359
360
361 /**
362  * Get the ID for the given session object.
363  *
364  * @param sh our handle
365  * @param session session object
366  * @param address the address we are looking for
367  * @return the session id or NOT_FOUND for error
368  */
369 static uint32_t
370 find_session_id (struct GNUNET_ATS_SchedulingHandle *sh,
371                  struct Session *session,
372                  const struct GNUNET_HELLO_Address *address)
373 {
374   uint32_t i;
375
376   if (NULL == address)
377   {
378     GNUNET_break (0);
379     return NOT_FOUND;
380   }
381   for (i = 1; i < sh->session_array_size; i++)
382     if ( (NULL != sh->session_array[i]) &&
383          ( (session == sh->session_array[i]->session) ||
384            (NULL == sh->session_array[i]->session) ) &&
385          (0 == GNUNET_HELLO_address_cmp (address,
386                                          sh->session_array[i]->address)) )
387       return i;
388   return NOT_FOUND;
389 }
390
391
392 /**
393  * Release the session slot from the session table (ATS service is
394  * also done using it).
395  *
396  * @param sh our handle
397  * @param session_id identifies session that is no longer valid
398  */
399 static void
400 release_session (struct GNUNET_ATS_SchedulingHandle *sh,
401                  uint32_t session_id)
402 {
403   struct GNUNET_ATS_AddressRecord *ar;
404
405   if (NOT_FOUND == session_id)
406     return;
407   if (session_id >= sh->session_array_size)
408   {
409     GNUNET_break (0);
410     force_reconnect (sh);
411     return;
412   }
413   /* this slot should have been removed from remove_session before */
414   ar = sh->session_array[session_id];
415   if (NULL != ar->session)
416   {
417     GNUNET_break (0);
418     force_reconnect (sh);
419     return;
420   }
421   GNUNET_HELLO_address_free (ar->address);
422   GNUNET_free (ar);
423   sh->session_array[session_id] = NULL;
424 }
425
426
427 /**
428  * Type of a function to call when we receive a session release
429  * message from the service.
430  *
431  * @param cls the `struct GNUNET_ATS_SchedulingHandle`
432  * @param msg message received, NULL on timeout or fatal error
433  */
434 static void
435 process_ats_session_release_message (void *cls,
436                                      const struct GNUNET_MessageHeader *msg)
437 {
438   struct GNUNET_ATS_SchedulingHandle *sh = cls;
439   const struct SessionReleaseMessage *srm;
440
441   srm = (const struct SessionReleaseMessage *) msg;
442   /* Note: peer field in srm not necessary right now,
443      but might be good to have in the future */
444   release_session (sh,
445                    ntohl (srm->session_id));
446 }
447
448
449 /**
450  * Type of a function to call when we receive a address suggestion
451  * message from the service.
452  *
453  * @param cls the `struct GNUNET_ATS_SchedulingHandle`
454  * @param msg message received, NULL on timeout or fatal error
455  */
456 static void
457 process_ats_address_suggestion_message (void *cls,
458                                         const struct GNUNET_MessageHeader *msg)
459 {
460   struct GNUNET_ATS_SchedulingHandle *sh = cls;
461   const struct AddressSuggestionMessage *m;
462   struct GNUNET_ATS_AddressRecord *ar;
463   uint32_t session_id;
464
465   m = (const struct AddressSuggestionMessage *) msg;
466   session_id = ntohl (m->session_id);
467   if (0 == session_id)
468   {
469     GNUNET_break (0);
470     force_reconnect (sh);
471     return;
472   }
473   ar = find_session (sh, session_id, &m->peer);
474   if (NULL == ar)
475   {
476     GNUNET_break (0);
477     force_reconnect (sh);
478     return;
479   }
480   if (NULL == sh->suggest_cb)
481     return;
482   if (GNUNET_YES == ar->in_destroy)
483   {
484     /* ignore suggestion, as this address is dying */
485     return;
486   }
487   if ( (NULL == ar->session) &&
488        (GNUNET_HELLO_address_check_option (ar->address,
489                                            GNUNET_HELLO_ADDRESS_INFO_INBOUND)) )
490   {
491     GNUNET_assert (0);
492     return;
493   }
494   sh->suggest_cb (sh->suggest_cb_cls,
495                   &m->peer,
496                   ar->address,
497                   ar->session,
498                   m->bandwidth_out,
499                   m->bandwidth_in);
500 }
501
502
503 /**
504  * We encountered an error handling the MQ to the
505  * ATS service.  Reconnect.
506  *
507  * @param cls the `struct GNUNET_ATS_SchedulingHandle`
508  * @param error details about the error
509  */
510 static void
511 error_handler (void *cls,
512                enum GNUNET_MQ_Error error)
513 {
514   struct GNUNET_ATS_SchedulingHandle *sh = cls;
515
516   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
517               "ATS connection died (code %d), reconnecting\n",
518               (int) error);
519   force_reconnect (sh);
520 }
521
522
523 /**
524  * Generate and transmit the `struct AddressAddMessage` for the given
525  * address record.
526  *
527  * @param sh the scheduling handle to use for transmission
528  * @param ar the address to inform the ATS service about
529  */
530 static void
531 send_add_address_message (struct GNUNET_ATS_SchedulingHandle *sh,
532                           const struct GNUNET_ATS_AddressRecord *ar)
533 {
534   struct GNUNET_MQ_Envelope *ev;
535   struct AddressAddMessage *m;
536   struct GNUNET_ATS_Information *am;
537   char *pm;
538   size_t namelen;
539   size_t msize;
540
541   if (NULL == sh->mq)
542     return; /* disconnected, skip for now */
543   namelen = (NULL == ar->address->transport_name)
544     ? 0
545     : strlen (ar->address->transport_name) + 1;
546   msize = ar->address->address_length +
547     ar->ats_count * sizeof (struct GNUNET_ATS_Information) + namelen;
548
549   ev = GNUNET_MQ_msg_extra (m, msize, GNUNET_MESSAGE_TYPE_ATS_ADDRESS_ADD);
550   m->ats_count = htonl (ar->ats_count);
551   m->peer = ar->address->peer;
552   m->address_length = htons (ar->address->address_length);
553   m->address_local_info = htonl ((uint32_t) ar->address->local_info);
554   m->plugin_name_length = htons (namelen);
555   m->session_id = htonl (ar->slot);
556
557   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
558               "Adding address for peer `%s', plugin `%s', session %p id %u\n",
559               GNUNET_i2s (&ar->address->peer),
560               ar->address->transport_name,
561               ar->session,
562               ar->slot);
563   am = (struct GNUNET_ATS_Information *) &m[1];
564   memcpy (am,
565           ar->ats,
566           ar->ats_count * sizeof (struct GNUNET_ATS_Information));
567   pm = (char *) &am[ar->ats_count];
568   memcpy (pm,
569           ar->address->address,
570           ar->address->address_length);
571   if (NULL != ar->address->transport_name)
572     memcpy (&pm[ar->address->address_length],
573             ar->address->transport_name,
574             namelen);
575   GNUNET_MQ_send (sh->mq, ev);
576 }
577
578
579 /**
580  * Transmit request for an address suggestion.
581  *
582  * @param cls the `struct GNUNET_ATS_SchedulingHandle`
583  * @param peer peer to ask for an address suggestion for
584  * @param value the `struct GNUNET_ATS_SuggestHandle`
585  * @return #GNUNET_OK (continue to iterate), #GNUNET_SYSERR on
586  *         failure (message queue no longer exists)
587  */
588 static int
589 transmit_suggestion (void *cls,
590                      const struct GNUNET_PeerIdentity *peer,
591                      void *value)
592 {
593   struct GNUNET_ATS_SchedulingHandle *sh = cls;
594   struct GNUNET_MQ_Envelope *ev;
595   struct RequestAddressMessage *m;
596
597   if (NULL == sh->mq)
598     return GNUNET_SYSERR;
599   ev = GNUNET_MQ_msg (m, GNUNET_MESSAGE_TYPE_ATS_REQUEST_ADDRESS);
600   m->reserved = htonl (0);
601   m->peer = *peer;
602   GNUNET_MQ_send (sh->mq, ev);
603   return GNUNET_OK;
604 }
605
606
607 /**
608  * Generate and transmit the `struct AddressUseMessage` for the given
609  * address record.
610  *
611  * @param ar the address to inform the ATS service about
612  * @param in_use say if it is in use or not
613  */
614 static void
615 send_in_use_message (struct GNUNET_ATS_AddressRecord *ar,
616                      int in_use)
617 {
618   struct GNUNET_ATS_SchedulingHandle *sh = ar->sh;
619   struct GNUNET_MQ_Envelope *ev;
620   struct AddressUseMessage *m;
621
622   ev = GNUNET_MQ_msg (m, GNUNET_MESSAGE_TYPE_ATS_ADDRESS_IN_USE);
623   m->peer = ar->address->peer;
624   m->in_use = htonl ((uint32_t) in_use);
625   m->session_id = htonl (ar->slot);
626   GNUNET_MQ_send (sh->mq, ev);
627 }
628
629
630 /**
631  * Re-establish the connection to the ATS service.
632  *
633  * @param sh handle to use to re-connect.
634  */
635 static void
636 reconnect (struct GNUNET_ATS_SchedulingHandle *sh)
637 {
638   static const struct GNUNET_MQ_MessageHandler handlers[] =
639     { { &process_ats_session_release_message,
640         GNUNET_MESSAGE_TYPE_ATS_SESSION_RELEASE,
641         sizeof (struct SessionReleaseMessage) },
642       { &process_ats_address_suggestion_message,
643         GNUNET_MESSAGE_TYPE_ATS_ADDRESS_SUGGESTION,
644         sizeof (struct AddressSuggestionMessage) },
645       { NULL, 0, 0 } };
646   struct GNUNET_MQ_Envelope *ev;
647   struct ClientStartMessage *init;
648   unsigned int i;
649   struct GNUNET_ATS_AddressRecord *ar;
650
651   GNUNET_assert (NULL == sh->client);
652   sh->client = GNUNET_CLIENT_connect ("ats", sh->cfg);
653   if (NULL == sh->client)
654   {
655     force_reconnect (sh);
656     return;
657   }
658   sh->mq = GNUNET_MQ_queue_for_connection_client (sh->client,
659                                                   handlers,
660                                                   &error_handler,
661                                                   sh);
662   ev = GNUNET_MQ_msg (init,
663                       GNUNET_MESSAGE_TYPE_ATS_START);
664   init->start_flag = htonl (START_FLAG_SCHEDULING);
665   GNUNET_MQ_send (sh->mq, ev);
666   if (NULL == sh->mq)
667     return;
668   for (i=0;i<sh->session_array_size;i++)
669   {
670     ar = sh->session_array[i];
671     if (NULL == ar)
672       continue;
673     send_add_address_message (sh, ar);
674     if (ar->in_use)
675       send_in_use_message (ar, GNUNET_YES);
676     if (NULL == sh->mq)
677       return;
678   }
679   GNUNET_CONTAINER_multipeermap_iterate (sh->sug_requests,
680                                          &transmit_suggestion,
681                                          sh);
682 }
683
684
685 /**
686  * Delete all entries from the current network list.
687  *
688  * @param sh scheduling handle to clean up
689  */
690 static void
691 delete_networks (struct GNUNET_ATS_SchedulingHandle *sh)
692 {
693   struct ATS_Network *cur;
694
695   while (NULL != (cur = sh->net_head))
696   {
697     GNUNET_CONTAINER_DLL_remove (sh->net_head,
698                                  sh->net_tail,
699                                  cur);
700     GNUNET_free (cur);
701   }
702 }
703
704
705 /**
706  * Function invoked for each interface found.  Adds the interface's
707  * network addresses to the respective DLL, so we can distinguish
708  * between LAN and WAN.
709  *
710  * @param cls closure
711  * @param name name of the interface (can be NULL for unknown)
712  * @param isDefault is this presumably the default interface
713  * @param addr address of this interface (can be NULL for unknown or unassigned)
714  * @param broadcast_addr the broadcast address (can be NULL for unknown or unassigned)
715  * @param netmask the network mask (can be NULL for unknown or unassigned)
716  * @param addrlen length of the address
717  * @return #GNUNET_OK to continue iteration
718  */
719 static int
720 interface_proc (void *cls,
721                 const char *name,
722                 int isDefault,
723                 const struct sockaddr *addr,
724                 const struct sockaddr *broadcast_addr,
725                 const struct sockaddr *netmask,
726                 socklen_t addrlen)
727 {
728   struct GNUNET_ATS_SchedulingHandle *sh = cls;
729   /* Calculate network */
730   struct ATS_Network *net = NULL;
731
732   /* Skipping IPv4 loopback addresses since we have special check  */
733   if  (addr->sa_family == AF_INET)
734   {
735     const struct sockaddr_in *a4 = (const struct sockaddr_in *) addr;
736
737     if ((a4->sin_addr.s_addr & htonl(0xff000000)) == htonl (0x7f000000))
738        return GNUNET_OK;
739   }
740   /* Skipping IPv6 loopback addresses since we have special check  */
741   if  (addr->sa_family == AF_INET6)
742   {
743     const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *) addr;
744     if (IN6_IS_ADDR_LOOPBACK (&a6->sin6_addr))
745       return GNUNET_OK;
746   }
747
748   if (addr->sa_family == AF_INET)
749   {
750     const struct sockaddr_in *addr4 = (const struct sockaddr_in *) addr;
751     const struct sockaddr_in *netmask4 = (const struct sockaddr_in *) netmask;
752     struct sockaddr_in *tmp;
753     struct sockaddr_in network4;
754
755     net = GNUNET_malloc (sizeof (struct ATS_Network) + 2 * sizeof (struct sockaddr_in));
756     tmp = (struct sockaddr_in *) &net[1];
757     net->network = (struct sockaddr *) &tmp[0];
758     net->netmask = (struct sockaddr *) &tmp[1];
759     net->length = addrlen;
760
761     memset (&network4, 0, sizeof (network4));
762     network4.sin_family = AF_INET;
763 #if HAVE_SOCKADDR_IN_SIN_LEN
764     network4.sin_len = sizeof (network4);
765 #endif
766     network4.sin_addr.s_addr = (addr4->sin_addr.s_addr & netmask4->sin_addr.s_addr);
767
768     memcpy (net->netmask, netmask4, sizeof (struct sockaddr_in));
769     memcpy (net->network, &network4, sizeof (struct sockaddr_in));
770   }
771
772   if (addr->sa_family == AF_INET6)
773   {
774     const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) addr;
775     const struct sockaddr_in6 *netmask6 = (const struct sockaddr_in6 *) netmask;
776     struct sockaddr_in6 * tmp;
777     struct sockaddr_in6 network6;
778
779     net = GNUNET_malloc (sizeof (struct ATS_Network) + 2 * sizeof (struct sockaddr_in6));
780     tmp = (struct sockaddr_in6 *) &net[1];
781     net->network = (struct sockaddr *) &tmp[0];
782     net->netmask = (struct sockaddr *) &tmp[1];
783     net->length = addrlen;
784
785     memset (&network6, 0, sizeof (network6));
786     network6.sin6_family = AF_INET6;
787 #if HAVE_SOCKADDR_IN_SIN_LEN
788     network6.sin6_len = sizeof (network6);
789 #endif
790     unsigned int c = 0;
791     uint32_t *addr_elem = (uint32_t *) &addr6->sin6_addr;
792     uint32_t *mask_elem = (uint32_t *) &netmask6->sin6_addr;
793     uint32_t *net_elem = (uint32_t *) &network6.sin6_addr;
794     for (c = 0; c < 4; c++)
795       net_elem[c] = addr_elem[c] & mask_elem[c];
796
797     memcpy (net->netmask, netmask6, sizeof (struct sockaddr_in6));
798     memcpy (net->network, &network6, sizeof (struct sockaddr_in6));
799   }
800   if (NULL == net)
801     return GNUNET_OK; /* odd / unsupported address family */
802
803   /* Store in list */
804 #if VERBOSE_ATS
805   char * netmask = GNUNET_strdup (GNUNET_a2s((struct sockaddr *) net->netmask, addrlen));
806   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
807               "Adding network `%s', netmask `%s'\n",
808               GNUNET_a2s ((struct sockaddr *) net->network,
809                           addrlen),
810               netmask);
811   GNUNET_free (netmask);
812 #endif
813   GNUNET_CONTAINER_DLL_insert (sh->net_head,
814                                sh->net_tail,
815                                net);
816
817   return GNUNET_OK;
818 }
819
820
821 /**
822  * Periodically get list of network addresses from our interfaces.
823  *
824  * @param cls closure
825  * @param tc Task context
826  */
827 static void
828 get_addresses (void *cls,
829                const struct GNUNET_SCHEDULER_TaskContext *tc)
830 {
831   struct GNUNET_ATS_SchedulingHandle *sh = cls;
832
833   sh->interface_task = NULL;
834   delete_networks (sh);
835   GNUNET_OS_network_interfaces_list (&interface_proc,
836                                      sh);
837   sh->interface_task = GNUNET_SCHEDULER_add_delayed (INTERFACE_PROCESSING_INTERVAL,
838                                                      &get_addresses,
839                                                      sh);
840 }
841
842
843 /**
844  * Convert a `enum GNUNET_ATS_Network_Type` to a string
845  *
846  * @param net the network type
847  * @return a string or NULL if invalid
848  */
849 const char *
850 GNUNET_ATS_print_network_type (enum GNUNET_ATS_Network_Type net)
851 {
852   switch (net)
853     {
854     case GNUNET_ATS_NET_UNSPECIFIED:
855       return "UNSPECIFIED";
856     case GNUNET_ATS_NET_LOOPBACK:
857       return "LOOPBACK";
858     case GNUNET_ATS_NET_LAN:
859       return "LAN";
860     case GNUNET_ATS_NET_WAN:
861       return "WAN";
862     case GNUNET_ATS_NET_WLAN:
863       return "WLAN";
864     case GNUNET_ATS_NET_BT:
865       return "BLUETOOTH";
866     default:
867       return NULL;
868     }
869 }
870
871
872 /**
873  * Convert a ATS property to a string
874  *
875  * @param type the property type
876  * @return a string or NULL if invalid
877  */
878 const char *
879 GNUNET_ATS_print_property_type (enum GNUNET_ATS_Property type)
880 {
881   switch (type)
882   {
883   case GNUNET_ATS_ARRAY_TERMINATOR:
884     return "TERMINATOR";
885   case GNUNET_ATS_UTILIZATION_OUT:
886     return "UTILIZATION_UP";
887   case GNUNET_ATS_UTILIZATION_IN:
888     return "UTILIZATION_DOWN";
889   case GNUNET_ATS_UTILIZATION_PAYLOAD_OUT:
890     return "UTILIZATION_PAYLOAD_UP";
891   case GNUNET_ATS_UTILIZATION_PAYLOAD_IN:
892     return "UTILIZATION_PAYLOAD_DOWN";
893   case GNUNET_ATS_NETWORK_TYPE:
894     return "NETWORK_TYPE";
895   case GNUNET_ATS_QUALITY_NET_DELAY:
896     return "DELAY";
897   case GNUNET_ATS_QUALITY_NET_DISTANCE:
898     return "DISTANCE";
899   case GNUNET_ATS_COST_WAN:
900     return "COST_WAN";
901   case GNUNET_ATS_COST_LAN:
902     return "COST_LAN";
903   case GNUNET_ATS_COST_WLAN:
904     return "COST_WLAN";
905   default:
906     return NULL;
907   }
908 }
909
910
911 /**
912  * Returns where the address is located: LAN or WAN or ...
913  *
914  * @param sh the scheduling handle
915  * @param addr address
916  * @param addrlen address length
917  * @return type of the network the address belongs to
918  */
919 enum GNUNET_ATS_Network_Type
920 GNUNET_ATS_address_get_type (struct GNUNET_ATS_SchedulingHandle *sh,
921                              const struct sockaddr *addr,
922                              socklen_t addrlen)
923 {
924   struct ATS_Network *cur = sh->net_head;
925   enum GNUNET_ATS_NetworkType type = GNUNET_ATS_NET_UNSPECIFIED;
926
927   switch (addr->sa_family)
928     {
929     case AF_UNIX:
930       type = GNUNET_ATS_NET_LOOPBACK;
931       break;
932     case AF_INET:
933       {
934         const struct sockaddr_in *a4 = (const struct sockaddr_in *) addr;
935
936         if ((a4->sin_addr.s_addr & htonl(0xff000000)) == htonl (0x7f000000))
937           type = GNUNET_ATS_NET_LOOPBACK;
938         break;
939       }
940     case AF_INET6:
941       {
942         const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *) addr;
943
944         if (IN6_IS_ADDR_LOOPBACK (&a6->sin6_addr))
945           type = GNUNET_ATS_NET_LOOPBACK;
946         break;
947       }
948     default:
949       GNUNET_break (0);
950       break;
951    }
952
953   /* Check local networks */
954   while ((NULL != cur) && (GNUNET_ATS_NET_UNSPECIFIED == type))
955   {
956     if (addrlen != cur->length)
957     {
958       cur = cur->next;
959       continue;
960     }
961     if (addr->sa_family == AF_INET)
962     {
963       const struct sockaddr_in *a4 = (const struct sockaddr_in *) addr;
964       const struct sockaddr_in *net4 = (const struct sockaddr_in *) cur->network;
965       const struct sockaddr_in *mask4 = (const struct sockaddr_in *) cur->netmask;
966
967       if (((a4->sin_addr.s_addr & mask4->sin_addr.s_addr)) == net4->sin_addr.s_addr)
968         type = GNUNET_ATS_NET_LAN;
969     }
970     if (addr->sa_family == AF_INET6)
971     {
972       const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *) addr;
973       const struct sockaddr_in6 *net6 = (const struct sockaddr_in6 *) cur->network;
974       const struct sockaddr_in6 *mask6 = (const struct sockaddr_in6 *) cur->netmask;
975
976       int res = GNUNET_YES;
977       int c = 0;
978       uint32_t *addr_elem = (uint32_t *) &a6->sin6_addr;
979       uint32_t *mask_elem = (uint32_t *) &mask6->sin6_addr;
980       uint32_t *net_elem = (uint32_t *) &net6->sin6_addr;
981       for (c = 0; c < 4; c++)
982         if ((addr_elem[c] & mask_elem[c]) != net_elem[c])
983           res = GNUNET_NO;
984
985       if (res == GNUNET_YES)
986         type = GNUNET_ATS_NET_LAN;
987     }
988     cur = cur->next;
989   }
990
991   /* no local network found for this address, default: WAN */
992   if (type == GNUNET_ATS_NET_UNSPECIFIED)
993     type = GNUNET_ATS_NET_WAN;
994   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
995                    "ats-scheduling-api",
996                    "`%s' is in network `%s'\n",
997                    GNUNET_a2s (addr,
998                                addrlen),
999                    GNUNET_ATS_print_network_type (type));
1000   return type;
1001 }
1002
1003
1004 /**
1005  * Initialize the ATS subsystem.
1006  *
1007  * @param cfg configuration to use
1008  * @param suggest_cb notification to call whenever the suggestation changed
1009  * @param suggest_cb_cls closure for @a suggest_cb
1010  * @return ats context
1011  */
1012 struct GNUNET_ATS_SchedulingHandle *
1013 GNUNET_ATS_scheduling_init (const struct GNUNET_CONFIGURATION_Handle *cfg,
1014                             GNUNET_ATS_AddressSuggestionCallback suggest_cb,
1015                             void *suggest_cb_cls)
1016 {
1017   struct GNUNET_ATS_SchedulingHandle *sh;
1018
1019   sh = GNUNET_new (struct GNUNET_ATS_SchedulingHandle);
1020   sh->cfg = cfg;
1021   sh->suggest_cb = suggest_cb;
1022   sh->suggest_cb_cls = suggest_cb_cls;
1023   GNUNET_array_grow (sh->session_array,
1024                      sh->session_array_size,
1025                      4);
1026   sh->sug_requests = GNUNET_CONTAINER_multipeermap_create (32,
1027                                                            GNUNET_YES);
1028   GNUNET_OS_network_interfaces_list (&interface_proc,
1029                                      sh);
1030   sh->interface_task = GNUNET_SCHEDULER_add_delayed (INTERFACE_PROCESSING_INTERVAL,
1031                                                      &get_addresses,
1032                                                      sh);
1033   reconnect (sh);
1034   return sh;
1035 }
1036
1037
1038 /**
1039  * Function called to free all `struct GNUNET_ATS_SuggestHandles`
1040  * in the map.
1041  *
1042  * @param cls NULL
1043  * @param key the key
1044  * @param value the value to free
1045  * @return #GNUNET_OK (continue to iterate)
1046  */
1047 static int
1048 free_sug_handle (void *cls,
1049                  const struct GNUNET_PeerIdentity *key,
1050                  void *value)
1051 {
1052   struct GNUNET_ATS_SuggestHandle *cur = value;
1053
1054   GNUNET_free (cur);
1055   return GNUNET_OK;
1056 }
1057
1058
1059 /**
1060  * Client is done with ATS scheduling, release resources.
1061  *
1062  * @param sh handle to release
1063  */
1064 void
1065 GNUNET_ATS_scheduling_done (struct GNUNET_ATS_SchedulingHandle *sh)
1066 {
1067   if (NULL != sh->mq)
1068   {
1069     GNUNET_MQ_destroy (sh->mq);
1070     sh->mq = NULL;
1071   }
1072   if (NULL != sh->client)
1073   {
1074     GNUNET_CLIENT_disconnect (sh->client);
1075     sh->client = NULL;
1076   }
1077   if (NULL != sh->task)
1078   {
1079     GNUNET_SCHEDULER_cancel (sh->task);
1080     sh->task = NULL;
1081   }
1082   GNUNET_CONTAINER_multipeermap_iterate (sh->sug_requests,
1083                                          &free_sug_handle,
1084                                          NULL);
1085   GNUNET_CONTAINER_multipeermap_destroy (sh->sug_requests);
1086   if (NULL != sh->interface_task)
1087   {
1088     GNUNET_SCHEDULER_cancel (sh->interface_task);
1089     sh->interface_task = NULL;
1090   }
1091   delete_networks (sh);
1092   GNUNET_array_grow (sh->session_array,
1093                      sh->session_array_size,
1094                      0);
1095   GNUNET_free (sh);
1096 }
1097
1098
1099 /**
1100  * We would like to reset the address suggestion block time for this
1101  * peer.
1102  *
1103  * @param sh handle
1104  * @param peer identity of the peer we want to reset
1105  */
1106 void
1107 GNUNET_ATS_reset_backoff (struct GNUNET_ATS_SchedulingHandle *sh,
1108                           const struct GNUNET_PeerIdentity *peer)
1109 {
1110   struct GNUNET_MQ_Envelope *ev;
1111   struct ResetBackoffMessage *m;
1112
1113   ev = GNUNET_MQ_msg (m, GNUNET_MESSAGE_TYPE_ATS_RESET_BACKOFF);
1114   m->reserved = htonl (0);
1115   m->peer = *peer;
1116   GNUNET_MQ_send (sh->mq, ev);
1117 }
1118
1119
1120 /**
1121  * We would like to receive address suggestions for a peer. ATS will
1122  * respond with a call to the continuation immediately containing an address or
1123  * no address if none is available. ATS can suggest more addresses until we call
1124  * #GNUNET_ATS_suggest_address_cancel().
1125  *
1126  * @param sh handle
1127  * @param peer identity of the peer we need an address for
1128  * @return suggest handle, NULL if a request is already pending
1129  */
1130 struct GNUNET_ATS_SuggestHandle *
1131 GNUNET_ATS_suggest_address (struct GNUNET_ATS_SchedulingHandle *sh,
1132                             const struct GNUNET_PeerIdentity *peer)
1133 {
1134   struct GNUNET_ATS_SuggestHandle *s;
1135
1136   s = GNUNET_new (struct GNUNET_ATS_SuggestHandle);
1137   s->id = *peer;
1138   if (GNUNET_OK !=
1139       GNUNET_CONTAINER_multipeermap_put (sh->sug_requests,
1140                                          &s->id,
1141                                          s,
1142                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
1143   {
1144     GNUNET_break (0);
1145     return NULL;
1146   }
1147   if (NULL == sh->mq)
1148     return s;
1149   (void) transmit_suggestion (sh,
1150                               &s->id,
1151                               s);
1152   return s;
1153 }
1154
1155
1156 /**
1157  * We would like to stop receiving address updates for this peer
1158  *
1159  * @param sh handle
1160  * @param peer identity of the peer
1161  */
1162 void
1163 GNUNET_ATS_suggest_address_cancel (struct GNUNET_ATS_SchedulingHandle *sh,
1164                                    const struct GNUNET_PeerIdentity *peer)
1165 {
1166   struct GNUNET_MQ_Envelope *ev;
1167   struct RequestAddressMessage *m;
1168   struct GNUNET_ATS_SuggestHandle *s;
1169
1170   s = GNUNET_CONTAINER_multipeermap_get (sh->sug_requests,
1171                                          peer);
1172   if (NULL == s)
1173   {
1174     GNUNET_break (0);
1175     return;
1176   }
1177   GNUNET_assert (GNUNET_OK ==
1178                  GNUNET_CONTAINER_multipeermap_remove (sh->sug_requests,
1179                                                        &s->id,
1180                                                        s));
1181   GNUNET_free (s);
1182   if (NULL == sh->mq)
1183     return;
1184   ev = GNUNET_MQ_msg (m, GNUNET_MESSAGE_TYPE_ATS_REQUEST_ADDRESS_CANCEL);
1185   m->reserved = htonl (0);
1186   m->peer = *peer;
1187   GNUNET_MQ_send (sh->mq, ev);
1188 }
1189
1190
1191 /**
1192  * Test if a address and a session is known to ATS
1193  *
1194  * @param sh the scheduling handle
1195  * @param address the address
1196  * @param session the session
1197  * @return #GNUNET_YES or #GNUNET_NO
1198  */
1199 int
1200 GNUNET_ATS_session_known (struct GNUNET_ATS_SchedulingHandle *sh,
1201                           const struct GNUNET_HELLO_Address *address,
1202                           struct Session *session)
1203 {
1204   if (NULL == session)
1205     return GNUNET_NO;
1206   if (NOT_FOUND != find_session_id (sh,
1207                                     session,
1208                                     address))
1209     return GNUNET_YES;  /* Exists */
1210   return GNUNET_NO;
1211 }
1212
1213
1214 /**
1215  * We have a new address ATS should know. Addresses have to be added
1216  * with this function before they can be: updated, set in use and
1217  * destroyed.
1218  *
1219  * @param sh handle
1220  * @param address the address
1221  * @param session session handle, can be NULL
1222  * @param ats performance data for the address
1223  * @param ats_count number of performance records in @a ats
1224  * @return handle to the address representation inside ATS, NULL
1225  *         on error (i.e. ATS knows this exact address already)
1226  */
1227 struct GNUNET_ATS_AddressRecord *
1228 GNUNET_ATS_address_add (struct GNUNET_ATS_SchedulingHandle *sh,
1229                         const struct GNUNET_HELLO_Address *address,
1230                         struct Session *session,
1231                         const struct GNUNET_ATS_Information *ats,
1232                         uint32_t ats_count)
1233 {
1234   struct GNUNET_ATS_AddressRecord *ar;
1235   size_t namelen;
1236   size_t msize;
1237   uint32_t s;
1238
1239   if (NULL == address)
1240   {
1241     /* we need a valid address */
1242     GNUNET_break (0);
1243     return NULL;
1244   }
1245   namelen = (NULL == address->transport_name)
1246     ? 0
1247     : strlen (address->transport_name) + 1;
1248   msize = address->address_length +
1249     ats_count * sizeof (struct GNUNET_ATS_Information) + namelen;
1250   if ((msize + sizeof (struct AddressUpdateMessage) >= GNUNET_SERVER_MAX_MESSAGE_SIZE) ||
1251       (address->address_length >= GNUNET_SERVER_MAX_MESSAGE_SIZE) ||
1252       (namelen >= GNUNET_SERVER_MAX_MESSAGE_SIZE) ||
1253       (ats_count >=
1254        GNUNET_SERVER_MAX_MESSAGE_SIZE / sizeof (struct GNUNET_ATS_Information)))
1255   {
1256     /* address too large for us, this should not happen */
1257     GNUNET_break (0);
1258     return NULL;
1259   }
1260
1261   if (NOT_FOUND != find_session_id (sh, session, address))
1262   {
1263     /* Already existing, nothing todo, but this should not happen */
1264     GNUNET_break (0);
1265     return NULL;
1266   }
1267   s = find_empty_session_slot (sh);
1268   ar = GNUNET_new (struct GNUNET_ATS_AddressRecord);
1269   ar->sh = sh;
1270   ar->slot = s;
1271   ar->session = session;
1272   ar->address = GNUNET_HELLO_address_copy (address);
1273   GNUNET_array_grow (ar->ats,
1274                      ar->ats_count,
1275                      ats_count);
1276   memcpy (ar->ats,
1277           ats,
1278           ats_count * sizeof (struct GNUNET_ATS_Information));
1279   sh->session_array[s] = ar;
1280   send_add_address_message (sh, ar);
1281   return ar;
1282 }
1283
1284
1285 /**
1286  * An address was used to initiate a session.
1287  *
1288  * @param ar address record to update information for
1289  * @param session session handle
1290  */
1291 void
1292 GNUNET_ATS_address_add_session (struct GNUNET_ATS_AddressRecord *ar,
1293                                 struct Session *session)
1294 {
1295   GNUNET_break (NULL == ar->session);
1296   ar->session = session;
1297 }
1298
1299
1300 /**
1301  * A session was destroyed, disassociate it from the
1302  * given address record.  If this was an incoming
1303  * addess, destroy the address as well.
1304  *
1305  * @param ar address record to update information for
1306  * @param session session handle
1307  * @return #GNUNET_YES if the @a ar was destroyed because
1308  *                     it was an incoming address,
1309  *         #GNUNET_NO if the @ar was kept because we can
1310  *                    use it still to establish a new session
1311  */
1312 int
1313 GNUNET_ATS_address_del_session (struct GNUNET_ATS_AddressRecord *ar,
1314                                 struct Session *session)
1315 {
1316   GNUNET_break (session == ar->session);
1317   ar->session = NULL;
1318   GNUNET_break (GNUNET_NO == ar->in_use);
1319   if (GNUNET_HELLO_address_check_option (ar->address,
1320                                          GNUNET_HELLO_ADDRESS_INFO_INBOUND))
1321   {
1322     GNUNET_ATS_address_destroy (ar);
1323     return GNUNET_YES;
1324   }
1325   return GNUNET_NO;
1326 }
1327
1328
1329 /**
1330  * We have updated performance statistics for a given address.  Note
1331  * that this function can be called for addresses that are currently
1332  * in use as well as addresses that are valid but not actively in use.
1333  * Furthermore, the peer may not even be connected to us right now (in
1334  * which case the call may be ignored or the information may be stored
1335  * for later use).  Update bandwidth assignments.
1336  *
1337  * @param ar address record to update information for
1338  * @param ats performance data for the address
1339  * @param ats_count number of performance records in @a ats
1340  */
1341 void
1342 GNUNET_ATS_address_update (struct GNUNET_ATS_AddressRecord *ar,
1343                            const struct GNUNET_ATS_Information *ats,
1344                            uint32_t ats_count)
1345 {
1346   struct GNUNET_ATS_SchedulingHandle *sh = ar->sh;
1347   struct GNUNET_MQ_Envelope *ev;
1348   struct AddressUpdateMessage *m;
1349   struct GNUNET_ATS_Information *am;
1350   size_t msize;
1351
1352   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1353               "Adding address for peer `%s', plugin `%s', session %p id %u\n",
1354               GNUNET_i2s (&ar->address->peer),
1355               ar->address->transport_name,
1356               ar->session,
1357               ar->slot);
1358   GNUNET_array_grow (ar->ats,
1359                      ar->ats_count,
1360                      ats_count);
1361   memcpy (ar->ats,
1362           ats,
1363           ats_count * sizeof (struct GNUNET_ATS_Information));
1364
1365   if (NULL == sh->mq)
1366     return; /* disconnected, skip for now */
1367   msize = ar->ats_count * sizeof (struct GNUNET_ATS_Information);
1368   ev = GNUNET_MQ_msg_extra (m, msize, GNUNET_MESSAGE_TYPE_ATS_ADDRESS_UPDATE);
1369   m->ats_count = htonl (ar->ats_count);
1370   m->peer = ar->address->peer;
1371   m->session_id = htonl (ar->slot);
1372   am = (struct GNUNET_ATS_Information *) &m[1];
1373   memcpy (am,
1374           ar->ats,
1375           ar->ats_count * sizeof (struct GNUNET_ATS_Information));
1376   GNUNET_MQ_send (sh->mq, ev);
1377 }
1378
1379
1380 /**
1381  * An address is now in use or not used any more.
1382  *
1383  * @param ar the address
1384  * @param in_use #GNUNET_YES if this address is now used, #GNUNET_NO
1385  * if address is not used any more
1386  */
1387 void
1388 GNUNET_ATS_address_set_in_use (struct GNUNET_ATS_AddressRecord *ar,
1389                                int in_use)
1390 {
1391   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1392               "Setting address used to %s for peer `%s', plugin `%s', session %p\n",
1393               (GNUNET_YES == in_use) ? "YES" : "NO",
1394               GNUNET_i2s (&ar->address->peer),
1395               ar->address->transport_name,
1396               ar->session);
1397   ar->in_use = in_use;
1398   if (NULL == ar->sh->mq)
1399     return;
1400   send_in_use_message (ar, in_use);
1401 }
1402
1403
1404 /**
1405  * An address got destroyed, stop using it as a valid address.
1406  *
1407  * @param ar address to destroy
1408  */
1409 void
1410 GNUNET_ATS_address_destroy (struct GNUNET_ATS_AddressRecord *ar)
1411 {
1412   struct GNUNET_ATS_SchedulingHandle *sh = ar->sh;
1413   struct GNUNET_MQ_Envelope *ev;
1414   struct AddressDestroyedMessage *m;
1415
1416   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1417               "Deleting address for peer `%s', plugin `%s', session %p\n",
1418               GNUNET_i2s (&ar->address->peer),
1419               ar->address->transport_name,
1420               ar->session);
1421   GNUNET_break (NULL == ar->session);
1422   ar->session = NULL;
1423   ar->in_destroy = GNUNET_YES;
1424   GNUNET_array_grow (ar->ats,
1425                      ar->ats_count,
1426                      0);
1427   if (NULL == sh->mq)
1428     return;
1429   ev = GNUNET_MQ_msg (m, GNUNET_MESSAGE_TYPE_ATS_ADDRESS_DESTROYED);
1430   m->session_id = htonl (ar->slot);
1431   m->peer = ar->address->peer;
1432   GNUNET_MQ_send (sh->mq, ev);
1433 }
1434
1435
1436 /* end of ats_api_scheduling.c */