cleanup
[oweals/gnunet.git] / src / ats / ats_api_performance.c
1 /*
2      This file is part of GNUnet.
3      (C) 2010,2011 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_performance.c
22  * @brief automatic transport selection and outbound bandwidth determination
23  * @author Christian Grothoff
24  * @author Matthias Wachs
25   */
26 #include "platform.h"
27 #include "gnunet_ats_service.h"
28 #include "ats.h"
29
30
31 /**
32  * Message in linked list we should send to the ATS service.  The
33  * actual binary message follows this struct.
34  */
35 struct PendingMessage
36 {
37
38   /**
39    * Kept in a DLL.
40    */ 
41   struct PendingMessage *next;
42
43   /**
44    * Kept in a DLL.
45    */ 
46   struct PendingMessage *prev;
47
48   /**
49    * Size of the message.
50    */
51   size_t size;
52
53   /**
54    * Is this the 'ATS_START' message?
55    */ 
56   int is_init;
57 };
58
59
60 /**
61  * Linked list of pending reservations.
62  */
63 struct GNUNET_ATS_ReservationContext
64 {
65
66   /**
67    * Kept in a DLL.
68    */ 
69   struct GNUNET_ATS_ReservationContext *next;
70
71   /**
72    * Kept in a DLL.
73    */ 
74   struct GNUNET_ATS_ReservationContext *prev;
75
76   /**
77    * Target peer.
78    */
79   struct GNUNET_PeerIdentity peer;
80                             
81   /**
82    * Desired reservation
83    */
84   int32_t size;
85
86   /**
87    * Function to call on result.
88    */
89   GNUNET_ATS_ReservationCallback rcb;
90
91   /**
92    * Closure for 'rcb'
93    */
94   void *rcb_cls;
95
96   /**
97    * Do we need to undo this reservation if it succeeded?  Set to
98    * GNUNET_YES if a reservation is cancelled.  (at that point, 'info'
99    * is also set to NULL; however, info will ALSO be NULL for the
100    * reservation context that is created to undo the original request,
101    * so 'info' being NULL cannot be used to check if undo is
102    * required).
103    */
104   int undo;
105 };
106
107
108 /**
109  * ATS Handle to obtain and/or modify performance information.
110  */
111 struct GNUNET_ATS_PerformanceHandle
112 {
113  
114   /**
115    * Our configuration.
116    */
117   const struct GNUNET_CONFIGURATION_Handle *cfg;
118
119   /**
120    * Callback to invoke on performance changes.
121    */
122   GNUNET_ATS_PeerInformationCallback infocb;
123   
124   /**
125    * Closure for 'infocb'.
126    */
127   void *infocb_cls;
128
129   /**
130    * Connection to ATS service.
131    */
132   struct GNUNET_CLIENT_Connection *client;
133
134   /**
135    * Head of list of messages for the ATS service.
136    */
137   struct PendingMessage *pending_head;
138
139   /**
140    * Tail of list of messages for the ATS service
141    */
142   struct PendingMessage *pending_tail;
143
144   /**
145    * Head of linked list of pending reservation requests.
146    */
147   struct GNUNET_ATS_ReservationContext *reservation_head;
148
149   /**
150    * Tail of linked list of pending reservation requests.
151    */
152   struct GNUNET_ATS_ReservationContext *reservation_tail;
153
154   /**
155    * Current request for transmission to ATS.
156    */
157   struct GNUNET_CLIENT_TransmitHandle *th;
158
159 };
160
161
162 /**
163  * Re-establish the connection to the ATS service.
164  *
165  * @param sh handle to use to re-connect.
166  */
167 static void
168 reconnect (struct GNUNET_ATS_PerformanceHandle *ph);
169
170
171 /**
172  * Transmit messages from the message queue to the service
173  * (if there are any, and if we are not already trying).
174  *
175  * @param sh handle to use
176  */
177 static void
178 do_transmit (struct GNUNET_ATS_PerformanceHandle *ph);
179
180
181 /**
182  * We can now transmit a message to ATS. Do it.
183  *
184  * @param cls the 'struct GNUNET_ATS_SchedulingHandle'
185  * @param size number of bytes we can transmit to ATS
186  * @param buf where to copy the messages
187  * @return number of bytes copied into buf
188  */
189 static size_t
190 transmit_message_to_ats (void *cls,
191                          size_t size,
192                          void *buf)
193 {
194   struct GNUNET_ATS_PerformanceHandle *ph = cls;
195   struct PendingMessage *p;
196   size_t ret;
197   char *cbuf;
198
199   ph->th = NULL;
200   ret = 0;
201   cbuf = buf;
202   while ( (NULL != (p = ph->pending_head)) &&
203           (p->size <= size) )
204   {
205     memcpy (&cbuf[ret], &p[1], p->size);    
206     ret += p->size;
207     size -= p->size;
208     GNUNET_CONTAINER_DLL_remove (ph->pending_head,
209                                  ph->pending_tail,
210                                  p);
211     GNUNET_free (p);
212   }
213   do_transmit (ph);
214   return ret;
215 }
216
217
218 /**
219  * Transmit messages from the message queue to the service
220  * (if there are any, and if we are not already trying).
221  *
222  * @param ph handle to use
223  */
224 static void
225 do_transmit (struct GNUNET_ATS_PerformanceHandle *ph)
226 {
227   struct PendingMessage *p;
228
229   if (NULL != ph->th)
230     return;
231   if (NULL == (p = ph->pending_head))
232     return;
233   ph->th = GNUNET_CLIENT_notify_transmit_ready (ph->client,
234                                                 p->size,
235                                                 GNUNET_TIME_UNIT_FOREVER_REL,
236                                                 GNUNET_YES,
237                                                 &transmit_message_to_ats, ph);
238 }
239
240
241 /**
242  * We received a peer information message.  Validate and process it.
243  *
244  * @param ph our context with the callback
245  * @param msg the message
246  * @return GNUNET_OK if the message was well-formed
247  */
248 static int
249 process_pi_message (struct GNUNET_ATS_PerformanceHandle *ph,
250                     const struct GNUNET_MessageHeader *msg)
251 {
252   const struct PeerInformationMessage *pi;
253   const struct GNUNET_TRANSPORT_ATS_Information *atsi;
254   const char *address;
255   const char *plugin_name;
256   uint16_t address_length;
257   uint16_t plugin_name_length;
258   uint32_t ats_count;
259
260   if (ph->infocb == NULL)
261   {
262     GNUNET_break (0);
263     return GNUNET_SYSERR;
264   }    
265   if (ntohs (msg->size) < sizeof (struct PeerInformationMessage))
266   {
267     GNUNET_break (0);
268     return GNUNET_SYSERR;
269   }
270   pi = (const struct PeerInformationMessage*) msg;
271   ats_count = ntohl (pi->ats_count);
272   address_length = ntohs (pi->address_length);
273   plugin_name_length = ntohs (pi->plugin_name_length);
274   atsi = (const struct GNUNET_TRANSPORT_ATS_Information*) &pi[1];
275   address = (const char*) &atsi[ats_count];
276   plugin_name = &address[address_length];
277   if ( (address_length +
278         plugin_name_length +
279         ats_count * sizeof (struct GNUNET_TRANSPORT_ATS_Information) +
280         sizeof (struct PeerInformationMessage) != ntohs (msg->size))  ||
281        (ats_count > GNUNET_SERVER_MAX_MESSAGE_SIZE / sizeof (struct GNUNET_TRANSPORT_ATS_Information)) ||
282        (plugin_name[plugin_name_length - 1] != '\0') )
283   {
284     GNUNET_break (0);
285     return GNUNET_SYSERR;
286   }
287   ph->infocb (ph->infocb_cls,
288               &pi->peer,
289               plugin_name,
290               address, address_length,
291               pi->bandwidth_out,
292               pi->bandwidth_in,
293               atsi,
294               ats_count);
295   return GNUNET_OK;
296 }
297
298
299 /**
300  * We received a reservation result message.  Validate and process it.
301  *
302  * @param ph our context with the callback
303  * @param msg the message
304  * @return GNUNET_OK if the message was well-formed
305  */
306 static int
307 process_rr_message (struct GNUNET_ATS_PerformanceHandle *ph,
308                     const struct GNUNET_MessageHeader *msg)
309 {
310   const struct ReservationResultMessage *rr;
311   struct GNUNET_ATS_ReservationContext *rc;
312   int32_t amount;
313
314   if (ph->infocb == NULL)
315   {
316     GNUNET_break (0);
317     return GNUNET_SYSERR;
318   }    
319   if (ntohs (msg->size) < sizeof (struct ReservationResultMessage))
320   {
321     GNUNET_break (0);
322     return GNUNET_SYSERR;
323   }
324   rr = (const struct ReservationResultMessage*) msg;
325   amount = ntohl (rr->amount);
326   rc = ph->reservation_head;
327   if (0 != memcmp (&rr->peer,
328                    &rc->peer,
329                    sizeof (struct GNUNET_PeerIdentity)))
330   {
331     GNUNET_break (0);
332     return GNUNET_SYSERR;
333   }
334   GNUNET_CONTAINER_DLL_remove (ph->reservation_head,
335                                ph->reservation_tail,
336                                rc);
337   if ( (amount == 0) ||
338        (rc->rcb != NULL) )
339   {
340     /* tell client if not cancelled */
341     if (rc->rcb != NULL)
342       rc->rcb (rc->rcb_cls,
343                &rr->peer,
344                amount,
345               GNUNET_TIME_relative_ntoh (rr->res_delay));       
346     GNUNET_free (rc);
347     return GNUNET_OK;
348   }
349   GNUNET_free (rc);
350   /* amount non-zero, but client cancelled, consider undo! */
351   if (GNUNET_YES != rc->undo)
352     return GNUNET_OK; /* do not try to undo failed undos or negative amounts */
353   (void) GNUNET_ATS_reserve_bandwidth (ph, &rr->peer, -amount, NULL, NULL);
354   return GNUNET_OK;
355 }
356
357
358 /**
359  * Type of a function to call when we receive a message
360  * from the service.
361  *
362  * @param cls the 'struct GNUNET_ATS_SchedulingHandle'
363  * @param msg message received, NULL on timeout or fatal error
364  */
365 static void
366 process_ats_message (void *cls,
367                      const struct GNUNET_MessageHeader *msg)
368 {
369   struct GNUNET_ATS_PerformanceHandle *ph = cls;
370
371   if (NULL == msg) 
372   {
373     GNUNET_CLIENT_disconnect (ph->client, GNUNET_NO);
374     ph->client = NULL;
375     reconnect (ph);
376     return;
377   }
378   switch (ntohs (msg->type))
379   {
380   case GNUNET_MESSAGE_TYPE_ATS_PEER_INFORMATION:
381     if (GNUNET_OK != process_pi_message (ph, msg))
382     {
383       GNUNET_CLIENT_disconnect (ph->client, GNUNET_NO);
384       ph->client = NULL;
385       reconnect (ph);
386       return;
387     }
388     break;
389   case GNUNET_MESSAGE_TYPE_ATS_RESERVATION_RESULT:
390     if (GNUNET_OK != process_rr_message (ph, msg))
391     {
392       GNUNET_CLIENT_disconnect (ph->client, GNUNET_NO);
393       ph->client = NULL;
394       reconnect (ph);
395       return;
396     }
397     break;
398   default:
399     GNUNET_break (0);
400     GNUNET_CLIENT_disconnect (ph->client, GNUNET_NO);
401     ph->client = NULL;
402     reconnect (ph);
403     return;
404   }
405   GNUNET_CLIENT_receive (ph->client,
406                          &process_ats_message, ph,
407                          GNUNET_TIME_UNIT_FOREVER_REL);
408 }
409
410
411 /**
412  * Re-establish the connection to the ATS service.
413  *
414  * @param ph handle to use to re-connect.
415  */
416 static void
417 reconnect (struct GNUNET_ATS_PerformanceHandle *ph)
418 {
419   struct PendingMessage *p;
420   struct ClientStartMessage *init;
421
422   GNUNET_assert (NULL == ph->client);
423   ph->client = GNUNET_CLIENT_connect ("ats", ph->cfg);
424   GNUNET_assert (NULL != ph->client);
425   GNUNET_CLIENT_receive (ph->client,
426                          &process_ats_message, ph,
427                          GNUNET_TIME_UNIT_FOREVER_REL);
428   if ( (NULL == (p = ph->pending_head)) ||
429        (GNUNET_YES != p->is_init) )
430   {
431     p = GNUNET_malloc (sizeof (struct PendingMessage) +
432                        sizeof (struct ClientStartMessage));
433     p->size = sizeof (struct ClientStartMessage);
434     p->is_init = GNUNET_YES;
435     init = (struct ClientStartMessage *) &p[1];
436     init->header.type = htons (GNUNET_MESSAGE_TYPE_ATS_START);
437     init->header.size = htons (sizeof (struct ClientStartMessage));
438     init->start_flag = htonl ((ph->infocb == NULL) 
439                               ? START_FLAG_PERFORMANCE_NO_PIC 
440                               : START_FLAG_PERFORMANCE_WITH_PIC);
441     GNUNET_CONTAINER_DLL_insert (ph->pending_head,
442                                  ph->pending_tail,
443                                  p);
444   }
445   do_transmit (ph);
446 }
447
448
449
450 /**
451  * Get handle to access performance API of the ATS subsystem.
452  *
453  * @param cfg configuration to use
454  * @param infocb function to call on allocation changes, can be NULL
455  * @param infocb_cls closure for infocb
456  * @return ats performance context
457  */
458 struct GNUNET_ATS_PerformanceHandle *
459 GNUNET_ATS_performance_init (const struct GNUNET_CONFIGURATION_Handle *cfg,
460                              GNUNET_ATS_PeerInformationCallback infocb,
461                              void *infocb_cls)
462 {
463   struct GNUNET_ATS_PerformanceHandle *ph;
464
465   ph = GNUNET_malloc (sizeof (struct GNUNET_ATS_PerformanceHandle));
466   ph->cfg = cfg;
467   ph->infocb = infocb;
468   ph->infocb_cls = infocb_cls;
469   reconnect (ph);
470   return ph;
471 }
472
473
474 /**
475  * Client is done using the ATS performance subsystem, release resources.
476  *
477  * @param ph handle
478  */
479 void
480 GNUNET_ATS_performance_done (struct GNUNET_ATS_PerformanceHandle *ph)
481 {
482   struct PendingMessage *p;
483   struct GNUNET_ATS_ReservationContext *rc;
484   
485   while (NULL != (p = ph->pending_head))
486   {
487     GNUNET_CONTAINER_DLL_remove (ph->pending_head,
488                                  ph->pending_tail,
489                                  p);
490     GNUNET_free (p);
491   }
492   while (NULL != (rc = ph->reservation_head))
493   {
494     GNUNET_CONTAINER_DLL_remove (ph->reservation_head,
495                                  ph->reservation_tail,
496                                  rc);
497     GNUNET_break (NULL == rc->rcb);
498     GNUNET_free (p);
499   }  
500   GNUNET_CLIENT_disconnect (ph->client, GNUNET_NO);
501   GNUNET_free (ph);
502 }
503
504
505 /**
506  * Reserve inbound bandwidth from the given peer.  ATS will look at
507  * the current amount of traffic we receive from the peer and ensure
508  * that the peer could add 'amount' of data to its stream.
509  *
510  * @param ph performance handle
511  * @param peer identifies the peer
512  * @param amount reserve N bytes for receiving, negative
513  *                amounts can be used to undo a (recent) reservation;
514  * @param rcb function to call with the resulting reservation information
515  * @param rcb_cls closure for info
516  * @return NULL on error
517  * @deprecated will be replaced soon
518  */
519 struct GNUNET_ATS_ReservationContext *
520 GNUNET_ATS_reserve_bandwidth (struct GNUNET_ATS_PerformanceHandle *ph,
521                               const struct GNUNET_PeerIdentity *peer,
522                               int32_t amount, 
523                               GNUNET_ATS_ReservationCallback rcb, 
524                               void *rcb_cls)
525 {
526   struct GNUNET_ATS_ReservationContext *rc;
527   struct PendingMessage *p;
528   struct ReservationRequestMessage *m;
529
530   rc = GNUNET_malloc (sizeof (struct GNUNET_ATS_ReservationContext));
531   rc->size = amount;
532   rc->peer = *peer;
533   rc->rcb = rcb;
534   rc->rcb_cls = rcb_cls;
535   if ( (rc != NULL) && (amount > 0) )
536     rc->undo = GNUNET_YES;
537   GNUNET_CONTAINER_DLL_insert_tail (ph->reservation_head,
538                                     ph->reservation_tail,
539                                     rc);
540   
541   p = GNUNET_malloc (sizeof (struct PendingMessage) + 
542                      sizeof (struct ReservationRequestMessage));
543   p->size = sizeof (struct ReservationRequestMessage);
544   p->is_init = GNUNET_NO;
545   m = (struct ReservationRequestMessage*) &p[1];
546   m->header.type = htons (GNUNET_MESSAGE_TYPE_ATS_ADDRESS_UPDATE);
547   m->header.size = htons (sizeof (struct ReservationRequestMessage));
548   m->amount = htonl (amount);
549   m->peer = *peer;
550   GNUNET_CONTAINER_DLL_insert_tail (ph->pending_head,
551                                     ph->pending_tail,
552                                     p);
553   return rc;
554 }
555
556
557 /**
558  * Cancel request for reserving bandwidth.
559  *
560  * @param rc context returned by the original GNUNET_ATS_reserve_bandwidth call
561  */
562 void
563 GNUNET_ATS_reserve_bandwidth_cancel (struct
564                                      GNUNET_ATS_ReservationContext *rc)
565 {
566   rc->rcb = NULL;
567 }
568
569
570 /**
571  * Change preferences for the given peer. Preference changes are forgotten if peers
572  * disconnect.
573  * 
574  * @param ph performance handle
575  * @param peer identifies the peer
576  * @param ... 0-terminated specification of the desired changes
577  */
578 void
579 GNUNET_ATS_change_preference (struct GNUNET_ATS_PerformanceHandle *ph,
580                               const struct GNUNET_PeerIdentity *peer,
581                               ...)
582 {
583   struct PendingMessage *p;
584   struct ChangePreferenceMessage *m;
585   size_t msize;
586   uint32_t count;
587   struct PreferenceInformation *pi;
588   va_list ap;
589   enum GNUNET_ATS_PreferenceKind kind;
590
591   count = 0;
592   va_start (ap, peer);
593   while (GNUNET_ATS_PREFERENCE_END != (kind = va_arg (ap, enum GNUNET_ATS_PreferenceKind)))
594   {
595     switch (kind)
596     {
597     case GNUNET_ATS_PREFERENCE_BANDWIDTH:
598       count++;
599       (void) va_arg (ap, double);
600       break;
601     case GNUNET_ATS_PREFERENCE_LATENCY:
602       count++;
603       (void) va_arg (ap, double);
604       break;
605     default:
606       GNUNET_assert (0);      
607     }
608   }
609   va_end (ap);
610   msize = count * sizeof (struct PreferenceInformation) +
611     sizeof (struct ChangePreferenceMessage);
612   p = GNUNET_malloc (sizeof (struct PendingMessage) + 
613                      msize);
614   p->size = msize;
615   p->is_init = GNUNET_NO;
616   m = (struct ChangePreferenceMessage*) &p[1];
617   m->header.type = htons (GNUNET_MESSAGE_TYPE_ATS_ADDRESS_UPDATE);
618   m->header.size = htons (msize);
619   m->num_preferences = htonl (count);
620   m->peer = *peer;
621   pi = (struct PreferenceInformation*) &m[1];
622   count = 0;
623   va_start (ap, peer);
624   while (GNUNET_ATS_PREFERENCE_END != (kind = va_arg (ap, enum GNUNET_ATS_PreferenceKind)))
625   {
626     pi[count].preference_kind = htonl (kind);
627     switch (kind)
628     {
629     case GNUNET_ATS_PREFERENCE_BANDWIDTH:
630       pi[count].preference_value = (float) va_arg (ap, double);
631       count++;
632       break;
633     case GNUNET_ATS_PREFERENCE_LATENCY:
634       pi[count].preference_value = (float) va_arg (ap, double);
635       count++;
636       break;
637     default:
638       GNUNET_assert (0);      
639     }
640   }
641   va_end (ap);
642   GNUNET_CONTAINER_DLL_insert_tail (ph->pending_head,
643                                     ph->pending_tail,
644                                     p);
645 }
646
647 /* end of ats_api_performance.c */
648