no need for public key
[oweals/gnunet.git] / src / ats / ats_api.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.c
22  * @brief automatic transport selection API
23  * @author Christian Grothoff
24  * @author Matthias Wachs
25  */
26 #include "platform.h"
27 #include "gnunet_ats_service.h"
28
29 // NOTE: this implementation is simply supposed
30 // to implement a simplistic strategy in-process;
31 // in the future, we plan to replace it with a real
32 // service implementation
33
34 /**
35  * Allocation record for a peer's address.
36  */
37 struct AllocationRecord
38 {
39
40   /**
41    * Performance information associated with this address (array).
42    */
43   struct GNUNET_TRANSPORT_ATS_Information *ats;
44
45   /**
46    * Name of the plugin
47    */
48   char *plugin_name;
49
50   /**
51    * Address this record represents, allocated at the end of this struct.
52    */
53   const void *plugin_addr;
54
55   /**
56    * Session associated with this record.
57    */
58   struct Session *session;
59
60   /**
61    * Number of bytes in plugin_addr.
62    */
63   size_t plugin_addr_len;            
64
65   /**
66    * Number of entries in 'ats'.
67    */
68   uint32_t ats_count;
69
70   /**
71    * Bandwidth assigned to this address right now, 0 for none.
72    */
73   struct GNUNET_BANDWIDTH_Value32NBO bandwidth;
74
75   /**
76    * Set to GNUNET_YES if this is the connected address of a connected peer.
77    */
78   int connected;
79
80 };
81
82
83 /**
84  * Opaque handle to stop incremental validation address callbacks.
85  */
86 struct GNUNET_ATS_SuggestionContext
87 {
88
89   /**
90    * Function to call with our final suggestion.
91    */
92   GNUNET_ATS_AddressSuggestionCallback cb;
93
94   /**
95    * Closure for 'cb'.
96    */
97   void *cb_cls;
98
99   /**
100    * Global ATS handle.
101    */ 
102   struct GNUNET_ATS_Handle *atc;
103
104   /**
105    * Which peer are we monitoring?
106    */   
107   struct GNUNET_PeerIdentity target;
108
109 };
110
111
112 /**
113  * Handle to the ATS subsystem.
114  */
115 struct GNUNET_ATS_Handle
116 {
117   /**
118    * Configuration.
119    */
120   const struct GNUNET_CONFIGURATION_Handle *cfg;
121
122   /**
123    * Function to call when the allocation changes.
124    */
125   GNUNET_TRANSPORT_ATS_AllocationNotification alloc_cb;
126
127   /**
128    * Closure for 'alloc_cb'.
129    */
130   void *alloc_cb_cls;
131
132   /**
133    * Information about all connected peers.  Maps peer identities
134    * to one or more 'struct AllocationRecord' values.
135    */
136   struct GNUNET_CONTAINER_MultiHashMap *peers;
137
138   /**
139    * Map of PeerIdentities to 'struct GNUNET_ATS_SuggestionContext's.
140    */
141   struct GNUNET_CONTAINER_MultiHashMap *notify_map;
142
143
144   /**
145    * Task scheduled to update our bandwidth assignment.
146    */
147   GNUNET_SCHEDULER_TaskIdentifier ba_task;
148
149   /**
150    * Total bandwidth per configuration.
151    */
152   unsigned long long total_bps;
153 };
154
155
156 /**
157  * Count number of connected records.
158  *
159  * @param cls pointer to counter
160  * @param key identity of the peer associated with the records
161  * @param value a 'struct AllocationRecord' 
162  * @return GNUNET_YES (continue iteration)
163  */
164 static int
165 count_connections (void *cls,
166                    const GNUNET_HashCode *key,
167                    void *value)
168 {
169   unsigned int *ac = cls;
170   struct AllocationRecord *ar = value;
171   
172   if (GNUNET_YES == ar->connected)
173     (*ac)++;
174   return GNUNET_YES;
175 }
176
177
178 /**
179  * Closure for 'set_bw_connections'.
180  */
181 struct SetBandwidthContext
182 {
183   /**
184    * ATS handle.
185    */
186   struct GNUNET_ATS_Handle *atc;
187
188   /**
189    * Bandwidth to assign.
190    */
191   struct GNUNET_BANDWIDTH_Value32NBO bw;
192 };
193
194
195 /**
196  * Set bandwidth based on record.
197  *
198  * @param cls 'struct SetBandwidthContext'
199  * @param key identity of the peer associated with the records
200  * @param value a 'struct AllocationRecord' 
201  * @return GNUNET_YES (continue iteration)
202  */
203 static int
204 set_bw_connections (void *cls,
205                     const GNUNET_HashCode *key,
206                     void *value)
207 {
208   struct SetBandwidthContext *sbc = cls;
209   struct AllocationRecord *ar = value;
210   
211   if (GNUNET_YES == ar->connected)
212     {
213       ar->bandwidth = sbc->bw;
214       sbc->atc->alloc_cb (sbc->atc->alloc_cb_cls,
215                           (const struct GNUNET_PeerIdentity*) key,
216                           ar->plugin_name,
217                           ar->session,
218                           ar->plugin_addr,
219                           ar->plugin_addr_len,
220                           ar->bandwidth);
221     }
222   else if (ntohl(ar->bandwidth.value__) > 0)
223     {
224       ar->bandwidth = GNUNET_BANDWIDTH_value_init (0);
225       sbc->atc->alloc_cb (sbc->atc->alloc_cb_cls,
226                           (const struct GNUNET_PeerIdentity*) key,
227                           ar->plugin_name,
228                           ar->session,
229                           ar->plugin_addr,
230                           ar->plugin_addr_len,
231                           ar->bandwidth);
232     }
233   return GNUNET_YES;
234 }
235
236
237 /**
238  * Task run to update bandwidth assignments.
239  *
240  * @param cls the 'struct GNUNET_ATS_Handle'
241  * @param tc scheduler context
242  */ 
243 static void
244 update_bandwidth_task (void *cls,
245                        const struct GNUNET_SCHEDULER_TaskContext *tc)
246 {
247   struct GNUNET_ATS_Handle *atc = cls;
248   unsigned int ac;
249   struct SetBandwidthContext bwc;
250
251   atc->ba_task = GNUNET_SCHEDULER_NO_TASK;
252   /* FIXME: update calculations NICELY; what follows is a naive version */
253   GNUNET_CONTAINER_multihashmap_iterate (atc->peers,
254                                          &count_connections,
255                                          &ac);
256   bwc.atc = atc;
257   bwc.bw = GNUNET_BANDWIDTH_value_init (atc->total_bps / ac);
258   GNUNET_CONTAINER_multihashmap_iterate (atc->peers,
259                                          &set_bw_connections,
260                                          &bwc);
261 }
262
263
264 /**
265  * Calculate an updated bandwidth assignment and notify.
266  *
267  * @param ats handle
268  * @param change which allocation record changed?
269  */
270 static void
271 update_bandwidth_assignment (struct GNUNET_ATS_Handle *atc,
272                              struct AllocationRecord *change)
273 {
274   /* FIXME: based on the 'change', update the LP-problem... */
275   if (atc->ba_task == GNUNET_SCHEDULER_NO_TASK)
276     atc->ba_task = GNUNET_SCHEDULER_add_now (&update_bandwidth_task,
277                                              atc);
278 }
279
280
281 /**
282  * Function called with feasbile addresses we might want to suggest.
283  *
284  * @param cls the 'struct GNUNET_ATS_SuggestionContext'
285  * @param key identity of the peer
286  * @param value a 'struct AllocationRecord' for the peer
287  * @return GNUNET_NO if we're done, GNUNET_YES if we did not suggest an address yet
288  */
289 static int
290 suggest_address (void *cls,
291                  const GNUNET_HashCode *key,
292                  void *value)
293 {
294   struct GNUNET_ATS_SuggestionContest *asc = cls;
295   struct AllocationRecord *ar = value;
296
297   // FIXME...
298   return GNUNET_YES;
299 }
300
301
302 /**
303  * We would like to establish a new connection with a peer.
304  * ATS should suggest a good address to begin with.
305  *
306  * @param atc handle
307  * @param peer identity of the new peer
308  * @param cb function to call with the address
309  * @param cb_cls closure for cb
310  */
311 struct GNUNET_ATS_SuggestionContext *
312 GNUNET_ATS_suggest_address (struct GNUNET_ATS_Handle *atc,
313                             const struct GNUNET_PeerIdentity *peer,
314                             GNUNET_ATS_AddressSuggestionCallback cb,
315                             void *cb_cls)
316 {
317   struct GNUNET_ATS_SuggestionContext *asc;
318
319   asc = GNUNET_malloc (sizeof (struct GNUNET_ATS_SuggestionContext));
320   asc->cb = cb;
321   asc->cb_cls = cb_cls;
322   asc->atc = atc;
323   asc->target = *peer;
324   GNUNET_CONTAINER_multihashmap_get_multiple (atc->peers,
325                                               &peer->hashPubKey,
326                                               &suggest_address,
327                                               asc);
328   if (NULL == asc->cb)
329     {
330       GNUNET_free (asc);
331       return NULL;
332     }
333   GNUNET_CONTAINER_multihashmap_put (atc->notify_map,
334                                      &peer->hashPubKey,
335                                      asc,
336                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
337   return asc;
338 }
339
340
341 /**
342  * Cancel suggestion request.
343  *
344  * @param asc handle of the request to cancel
345  */
346 void
347 GNUNET_ATS_suggest_address_cancel (struct GNUNET_ATS_SuggestionContext *asc)
348 {
349   GNUNET_assert (GNUNET_OK ==
350                  GNUNET_CONTAINER_multihashmap_remove (asc->atc->notify_map,
351                                                        &asc->target.hashPubKey,
352                                                        asc));
353   GNUNET_free (asc);
354 }
355
356
357 /**
358  * Initialize the ATS subsystem.
359  *
360  * @param cfg configuration to use
361  * @param alloc_cb notification to call whenever the allocation changed
362  * @param alloc_cb_cls closure for 'alloc_cb'
363  * @return ats context
364  */
365 struct GNUNET_ATS_Handle *
366 GNUNET_ATS_init (const struct GNUNET_CONFIGURATION_Handle *cfg,
367                  GNUNET_TRANSPORT_ATS_AllocationNotification alloc_cb,
368                  void *alloc_cb_cls)
369 {
370   struct GNUNET_ATS_Handle *atc;
371
372   atc = GNUNET_malloc (sizeof (struct GNUNET_ATS_Handle));
373   atc->cfg = cfg;
374   atc->alloc_cb = alloc_cb;
375   atc->alloc_cb_cls = alloc_cb_cls;
376   atc->peers = GNUNET_CONTAINER_multihashmap_create (256);
377   GNUNET_CONFIGURATION_get_value_number (cfg,
378                                          "core",
379                                          "TOTAL_QUOTA_OUT",
380                                          &atc->total_bps);
381   return atc;
382 }
383
384
385 /**
386  * Free an allocation record.
387  *
388  * @param cls unused
389  * @param key identity of the peer associated with the record
390  * @param value the 'struct AllocationRecord' to free
391  * @return GNUNET_OK (continue to iterate)
392  */
393 static int
394 destroy_allocation_record (void *cls,
395                            const GNUNET_HashCode *key,
396                            void *value)
397 {
398   struct AllocationRecord *ar = value;
399
400   GNUNET_array_grow (ar->ats, ar->ats_count, 0);
401   GNUNET_free (ar->plugin_name);
402   GNUNET_free (ar);
403   return GNUNET_OK;
404 }
405
406
407 /**
408  * Shutdown the ATS subsystem.
409  *
410  * @param atc handle
411  */
412 void
413 GNUNET_ATS_shutdown (struct GNUNET_ATS_Handle *atc)
414 {
415   if (GNUNET_SCHEDULER_NO_TASK != atc->ba_task)
416     {
417       GNUNET_SCHEDULER_cancel (atc->ba_task);
418       atc->ba_task = GNUNET_SCHEDULER_NO_TASK;
419     }
420   GNUNET_CONTAINER_multihashmap_iterate (atc->peers,
421                                          &destroy_allocation_record,
422                                          NULL);
423   GNUNET_CONTAINER_multihashmap_destroy (atc->peers);
424   GNUNET_assert (GNUNET_CONTAINER_multihashmap_size (atc->notify_map) == 0);
425   GNUNET_CONTAINER_multihashmap_destroy (atc->notify_map);
426   atc->notify_map = NULL;
427   GNUNET_free (atc);
428 }
429
430
431 /**
432  * Closure for 'update_session'
433  */
434 struct UpdateSessionContext
435 {
436   /**
437    * Ats handle.
438    */
439   struct GNUNET_ATS_Handle *atc;
440
441   /**
442    * Allocation record with new information.
443    */
444   struct AllocationRecord *arnew;
445 };
446
447
448 /**
449  * Update an allocation record, merging with the new information
450  *
451  * @param cls a new 'struct AllocationRecord'
452  * @param key identity of the peer associated with the records
453  * @param value the old 'struct AllocationRecord' 
454  * @return GNUNET_YES if the records do not match, 
455  *         GNUNET_NO if the record do match and 'old' was updated
456  */
457 static int
458 update_session (void *cls,
459                 const GNUNET_HashCode *key,
460                 void *value)
461 {
462   struct UpdateSessionContext *usc = cls;
463   struct AllocationRecord *arnew = usc->arnew;
464   struct AllocationRecord *arold = value;
465   int change;
466
467   if (0 != strcmp (arnew->plugin_name, arold->plugin_name))
468     return GNUNET_YES;
469   if ( (arnew->session == arold->session) ||
470        ( (arold->session == NULL) &&
471          (arold->plugin_addr_len == arnew->plugin_addr_len) &&
472          (0 == memcmp (arold->plugin_addr,
473                        arnew->plugin_addr,
474                        arnew->plugin_addr_len)) ) )
475     {
476       change = GNUNET_NO;
477       /* records match */
478       if (arnew->session != arold->session) 
479         {
480           arold->session = arnew->session;
481           change = GNUNET_YES;
482         }
483       if ( (arnew->connected == GNUNET_YES) &&
484            (arold->connected == GNUNET_NO) )
485         {
486           arold->connected = GNUNET_YES;
487           change = GNUNET_YES;
488         }
489       // FIXME: merge ats arrays of (arold, arnew);
490       
491       if (GNUNET_YES == change)
492         update_bandwidth_assignment (usc->atc, arold);
493       return GNUNET_NO;      
494     }
495   return GNUNET_YES;
496 }
497
498
499 /**
500  * Create an allocation record with the given properties.
501  *
502  * @param plugin_name name of the currently used transport plugin
503  * @param session session in use (if available)
504  * @param plugin_addr address in use (if available)
505  * @param plugin_addr_len number of bytes in plugin_addr
506  * @param ats performance data for the connection
507  * @param ats_count number of performance records in 'ats'
508  */
509 static struct AllocationRecord *
510 create_allocation_record (const char *plugin_name,
511                           struct Session *session,
512                           const void *plugin_addr,
513                           size_t plugin_addr_len,
514                           const struct GNUNET_TRANSPORT_ATS_Information *ats,
515                           uint32_t ats_count)
516 {
517   struct AllocationRecord *ar;
518
519   ar = GNUNET_malloc (sizeof (struct AllocationRecord) + plugin_addr_len);
520   ar->plugin_name = GNUNET_strdup (plugin_name);
521   ar->plugin_addr = &ar[1];
522   memcpy (&ar[1], plugin_addr, plugin_addr_len);
523   ar->session = session;
524   ar->plugin_addr_len = plugin_addr_len;
525   GNUNET_array_grow (ar->ats,
526                      ar->ats_count,
527                      ats_count);
528   memcpy (ar->ats, 
529           ats, 
530           ats_count * sizeof (struct GNUNET_TRANSPORT_ATS_Information));
531   return ar;
532 }
533
534
535 /**
536  * Mark all matching allocation records as not connected.
537  *
538  * @param cls 'struct GTS_AtsHandle'
539  * @param key identity of the peer associated with the record
540  * @param value the 'struct AllocationRecord' to clear the 'connected' flag
541  * @return GNUNET_OK (continue to iterate)
542  */
543 static int
544 disconnect_peer (void *cls,
545                  const GNUNET_HashCode *key,
546                  void *value)
547 {
548   struct GNUNET_ATS_Handle *atc = cls;
549   struct AllocationRecord *ar = value;
550
551   if (GNUNET_YES == ar->connected)
552     {
553       ar->connected = GNUNET_NO;     
554       update_bandwidth_assignment (atc, ar);
555     }
556   return GNUNET_OK;
557 }
558
559
560 /**
561  * We established a new connection with a peer (for example, because
562  * core asked for it or because the other peer connected to us).
563  * Calculate bandwidth assignments including the new peer.
564  *
565  * @param atc handle
566  * @param peer identity of the new peer
567  * @param plugin_name name of the currently used transport plugin
568  * @param session session in use (if available)
569  * @param plugin_addr address in use (if available)
570  * @param plugin_addr_len number of bytes in plugin_addr
571  * @param ats performance data for the connection
572  * @param ats_count number of performance records in 'ats'
573  */
574 void
575 GNUNET_ATS_peer_connect (struct GNUNET_ATS_Handle *atc,
576                          const struct GNUNET_PeerIdentity *peer,
577                          const char *plugin_name,
578                          struct Session *session,
579                          const void *plugin_addr,
580                          size_t plugin_addr_len,
581                          const struct GNUNET_TRANSPORT_ATS_Information *ats,
582                          uint32_t ats_count)
583 {
584   struct AllocationRecord *ar;
585   struct UpdateSessionContext usc;
586
587   (void) GNUNET_CONTAINER_multihashmap_iterate (atc->peers,
588                                                 &disconnect_peer,
589                                                 atc);
590   ar = create_allocation_record (plugin_name,
591                                  session,
592                                  plugin_addr,
593                                  plugin_addr_len,
594                                  ats,
595                                  ats_count);
596   ar->connected = GNUNET_YES;
597   usc.atc = atc;
598   usc.arnew = ar;
599   if (GNUNET_SYSERR ==
600       GNUNET_CONTAINER_multihashmap_iterate (atc->peers,
601                                              &update_session,
602                                              &usc))
603     {     
604       destroy_allocation_record (NULL, &peer->hashPubKey, ar);
605       return;
606     }
607   GNUNET_assert (GNUNET_OK ==
608                  GNUNET_CONTAINER_multihashmap_put (atc->peers,
609                                                     &peer->hashPubKey,
610                                                     ar,
611                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));   
612 }
613
614
615 /**
616  * We disconnected from the given peer (for example, because ats, core
617  * or blacklist asked for it or because the other peer disconnected).
618  * Calculate bandwidth assignments without the peer.
619  *
620  * @param atc handle
621  * @param peer identity of the new peer
622  */
623 void
624 GNUNET_ATS_peer_disconnect (struct GNUNET_ATS_Handle *atc,
625                          const struct GNUNET_PeerIdentity *peer)
626 {
627   (void) GNUNET_CONTAINER_multihashmap_get_multiple (atc->peers,
628                                                      &peer->hashPubKey,
629                                                      &disconnect_peer,
630                                                      atc);
631 }
632
633
634 /**
635  * Closure for 'destroy_allocation_record'
636  */
637 struct SessionDestroyContext
638 {
639   /**
640    * Ats handle.
641    */
642   struct GNUNET_ATS_Handle *atc;
643
644   /**
645    * Session being destroyed.
646    */
647   const struct Session *session;
648 };
649
650
651 /**
652  * Free an allocation record matching the given session.
653  *
654  * @param cls the 'struct SessionDestroyContext'
655  * @param key identity of the peer associated with the record
656  * @param value the 'struct AllocationRecord' to free
657  * @return GNUNET_OK (continue to iterate)
658  */
659 static int
660 destroy_session (void *cls,
661                  const GNUNET_HashCode *key,
662                  void *value)
663 {
664   struct SessionDestroyContext *sdc = cls;
665   struct AllocationRecord *ar = value;
666
667   if (ar->session != sdc->session)
668     return GNUNET_OK;
669   ar->session = NULL;
670   if (ar->plugin_addr != NULL)
671     return GNUNET_OK;
672   GNUNET_assert (GNUNET_OK ==
673                  GNUNET_CONTAINER_multihashmap_remove (sdc->atc->peers,
674                                                        key,
675                                                        ar));
676   if (GNUNET_YES == ar->connected);
677     {
678       /* FIXME: is this supposed to be allowed? What to do then? */
679       GNUNET_break (0);
680     }
681   destroy_allocation_record (NULL, key, ar);
682   return GNUNET_OK;
683 }
684
685
686 /**
687  * A session got destroyed, stop including it as a valid address.
688  *
689  * @param atc handle
690  * @param peer identity of the peer
691  * @param session session handle that is no longer valid
692  */
693 void
694 GNUNET_ATS_session_destroyed (struct GNUNET_ATS_Handle *atc,
695                               const struct GNUNET_PeerIdentity *peer,
696                               const struct Session *session)
697 {
698   struct SessionDestroyContext sdc;
699
700   sdc.atc = atc;
701   sdc.session = session;
702   (void) GNUNET_CONTAINER_multihashmap_iterate (atc->peers,
703                                                 &destroy_session,
704                                                 &sdc);
705 }
706
707
708 /**
709  * Notify validation watcher that an entry is now valid
710  *
711  * @param cls 'struct ValidationEntry' that is now valid
712  * @param key peer identity (unused)
713  * @param value a 'GST_ValidationIteratorContext' to notify
714  * @return GNUNET_YES (continue to iterate)
715  */
716 static int
717 notify_valid (void *cls,
718               const GNUNET_HashCode *key,
719               void *value)
720 {
721   struct AllocationRecord *ar = cls;
722   struct GNUNET_ATS_SuggestionContext *asc = value;
723
724   asc->cb (asc->cb_cls,
725            &asc->target,
726            ar->plugin_name,
727            ar->plugin_addr,
728            ar->plugin_addr_len,
729            GNUNET_BANDWIDTH_value_init (asc->atc->total_bps / 32),
730            ar->ats, ar->ats_count);
731   return GNUNET_OK;
732 }
733
734
735 /**
736  * We have updated performance statistics for a given address.  Note
737  * that this function can be called for addresses that are currently
738  * in use as well as addresses that are valid but not actively in use.
739  * Furthermore, the peer may not even be connected to us right now (in
740  * which case the call may be ignored or the information may be stored
741  * for later use).  Update bandwidth assignments.
742  *
743  * @param atc handle
744  * @param peer identity of the peer
745  * @param valid_until how long is the address valid?
746  * @param plugin_name name of the transport plugin
747  * @param session session handle (if available)
748  * @param plugin_addr address  (if available)
749  * @param plugin_addr_len number of bytes in plugin_addr
750  * @param ats performance data for the address
751  * @param ats_count number of performance records in 'ats'
752  */
753 void
754 GNUNET_ATS_address_update (struct GNUNET_ATS_Handle *atc,
755                            const struct GNUNET_PeerIdentity *peer,
756                            struct GNUNET_TIME_Absolute valid_until,
757                            const char *plugin_name,
758                            struct Session *session,
759                            const void *plugin_addr,
760                            size_t plugin_addr_len,
761                            const struct GNUNET_TRANSPORT_ATS_Information *ats,
762                            uint32_t ats_count)
763 {
764   struct AllocationRecord *ar;
765   struct UpdateSessionContext usc;
766
767   ar = create_allocation_record (plugin_name,                            
768                                  session,
769                                  plugin_addr,
770                                  plugin_addr_len,
771                                  ats,
772                                  ats_count);
773   usc.atc = atc;
774   usc.arnew = ar;    
775   if (GNUNET_SYSERR ==
776       GNUNET_CONTAINER_multihashmap_iterate (atc->peers,
777                                              &update_session,
778                                              &usc))
779     {     
780       destroy_allocation_record (NULL, &peer->hashPubKey, ar);
781       return;
782     }
783   GNUNET_assert (GNUNET_OK ==
784                  GNUNET_CONTAINER_multihashmap_put (atc->peers,
785                                                     &peer->hashPubKey,
786                                                     ar,
787                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); 
788   GNUNET_CONTAINER_multihashmap_get_multiple (atc->notify_map,
789                                               &peer->hashPubKey,
790                                               &notify_valid,
791                                               ar);
792 }
793
794 /* end of file gnunet-service-transport_ats.c */