- fix
[oweals/gnunet.git] / src / testbed / gnunet-service-testbed_hc.c
1 /*
2   This file is part of GNUnet.
3   (C) 2012 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 2, 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 /**
22  * @file testbed/gnunet-service-testbed_hc.h
23  * @brief testbed cache implementation
24  * @author Sree Harsha Totakura
25  */
26
27 #include "gnunet-service-testbed.h"
28
29
30 #ifdef LOG
31 #undef LOG
32 #endif
33
34 #define LOG(kind,...)                                   \
35   GNUNET_log_from (kind, "testbed-cache", __VA_ARGS__)
36
37 /* #define LOG_DEBUG(...)                          \ */
38 /*   LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) */
39
40
41 enum CacheGetType
42 {    
43   CGT_TRANSPORT_HANDLE = 1
44 };
45
46
47 struct GSTCacheGetHandle
48 {
49   struct GSTCacheGetHandle *next;
50
51   struct GSTCacheGetHandle *prev;
52
53   struct CacheEntry *entry;
54   
55   GST_cache_callback cb;
56    
57   void *cb_cls;
58
59   enum CacheGetType type;
60
61   int notify_called;
62 };
63
64 /**
65  * Cache entry
66  */
67 struct CacheEntry 
68 {
69   /**
70    * DLL next ptr for least recently used cache entries
71    */
72   struct CacheEntry *next;
73
74   /**
75    * DLL prev ptr for least recently used cache entries
76    */
77   struct CacheEntry *prev;
78
79   /**
80    * The transport handle to the peer corresponding to this entry; can be NULL
81    */
82   struct GNUNET_TRANSPORT_Handle *transport_handle;
83
84   /**
85    * The operation handle for transport handle
86    */
87   struct GNUNET_TESTBED_Operation *transport_op;
88
89   /**
90    * The configuration of the peer. Should be not NULL as long as the core_handle
91    * or transport_handle are valid
92    */
93   struct GNUNET_CONFIGURATION_Handle *cfg;
94
95   /**
96    * The key for this entry
97    */
98   struct GNUNET_HashCode key;
99
100   /**
101    * The HELLO message
102    */
103   struct GNUNET_MessageHeader *hello;
104
105   /**
106    * the head of the CacheGetHandle queue
107    */
108   struct GSTCacheGetHandle *cghq_head;
109
110   /**
111    * the tail of the CacheGetHandle queue
112    */
113   struct GSTCacheGetHandle *cghq_tail;
114
115   /**
116    * The task that calls the cache callback
117    */
118   GNUNET_SCHEDULER_TaskIdentifier notify_task;
119
120   /**
121    * Number of operations this cache entry is being used
122    */
123   unsigned int demand;
124
125   /**
126    * The id of the peer this entry corresponds to
127    */
128   unsigned int peer_id;
129 };
130
131 /**
132  * Hashmap to maintain cache
133  */
134 static struct GNUNET_CONTAINER_MultiHashMap *cache;
135
136 /**
137  * DLL head for least recently used cache entries; least recently used
138  * cache items are at the head. The cache enties are added to this queue when
139  * their demand becomes zero. They are removed from the queue when they are
140  * needed by any operation.
141  */
142 static struct CacheEntry *lru_cache_head;
143
144 /**
145  * DLL tail for least recently used cache entries; recently used cache
146  * items are at the tail.The cache enties are added to this queue when
147  * their demand becomes zero. They are removed from the queue when they are
148  * needed by any operation.
149  */
150 static struct CacheEntry *lru_cache_tail;
151
152 /**
153  * the size of the LRU queue
154  */
155 static unsigned int lru_cache_size;
156
157 /**
158  * the threshold size for the LRU queue
159  */
160 static unsigned int lru_cache_threshold_size;
161
162 /**
163  * The total number of elements in cache
164  */
165 static unsigned int cache_size;
166
167
168 /**
169  * Looks up in the cache and returns the entry
170  *
171  * @param id the peer identity of the peer whose corresponding entry has to be looked up
172  * @return the HELLO message; NULL if not found
173  */
174 static struct CacheEntry *
175 cache_lookup (const struct GNUNET_HashCode *key)
176 {
177   struct CacheEntry *entry;
178
179   if (NULL == cache)
180     return NULL;
181   entry = GNUNET_CONTAINER_multihashmap_get (cache, key);
182   return entry;
183 }
184
185
186 static struct CacheEntry *
187 cache_lookup_handles (const struct GNUNET_HashCode *pid,
188                       struct GNUNET_TRANSPORT_Handle **th)
189 {
190   struct CacheEntry *entry;
191   
192   GNUNET_assert (NULL != th);
193   entry = cache_lookup (pid);  
194   if (NULL == entry)
195     return NULL;
196   if (NULL != entry->transport_handle)
197     *th = entry->transport_handle;
198   return entry;
199 }
200
201
202 static void
203 cache_remove (struct CacheEntry *entry)
204 {
205   /* We keep the entry in the hash table so that the HELLO can still be found
206      in cache; we will however disconnect the core and transport handles */
207   GNUNET_assert (0 == entry->demand);
208   if ((NULL != entry->next) || (NULL != entry->prev))
209     GNUNET_CONTAINER_DLL_remove (lru_cache_head, lru_cache_tail, entry);
210   LOG_DEBUG ("Cleaning up handles from an entry in cache\n");
211   if (NULL != entry->transport_handle)
212   {
213     GNUNET_assert (NULL != entry->transport_op);
214     GNUNET_TESTBED_operation_done (entry->transport_op);
215     entry->transport_op = NULL;
216   }
217   if (NULL != entry->cfg)
218   {
219     GNUNET_CONFIGURATION_destroy (entry->cfg);
220     entry->cfg = NULL;
221   }
222 }
223
224
225 static struct CacheEntry *
226 add_entry (const struct GNUNET_HashCode *key, unsigned int peer_id)
227 {
228   struct CacheEntry *entry;
229
230   entry = GNUNET_malloc (sizeof (struct CacheEntry));
231   entry->peer_id = peer_id;
232   memcpy (&entry->key, key, sizeof (struct GNUNET_HashCode));
233   GNUNET_assert (GNUNET_OK ==
234                  GNUNET_CONTAINER_multihashmap_put (cache, &entry->key,
235                                                     entry,
236                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST));
237   cache_size++;
238   return entry;
239 }
240
241
242 static void
243 call_cgh_cb (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
244 {
245   struct CacheEntry *entry = cls;
246   struct GSTCacheGetHandle *cgh;
247
248   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != entry->notify_task);
249   entry->notify_task = GNUNET_SCHEDULER_NO_TASK;
250   cgh = entry->cghq_head;
251   GNUNET_assert (GNUNET_NO == cgh->notify_called);
252   GNUNET_CONTAINER_DLL_remove (entry->cghq_head, entry->cghq_tail, cgh);
253   cgh->notify_called = GNUNET_YES;
254   GNUNET_CONTAINER_DLL_insert_tail (entry->cghq_head, entry->cghq_tail, cgh);
255   if (GNUNET_NO == entry->cghq_head->notify_called)
256     entry->notify_task = GNUNET_SCHEDULER_add_now (&call_cgh_cb, entry);
257   switch (cgh->type)
258   {
259   case CGT_TRANSPORT_HANDLE:
260     cgh->cb (cgh->cb_cls, NULL, entry->transport_handle);
261     break;
262   }
263 }
264
265
266 static void
267 opstart_get_handle_transport (void *cls)
268 {
269   struct CacheEntry *entry = cls;
270
271   GNUNET_assert (NULL != entry);
272   LOG_DEBUG ("Opening a transport connection to peer %u\n", entry->peer_id);
273   entry->transport_handle = GNUNET_TRANSPORT_connect (entry->cfg,
274                                                       NULL, NULL,
275                                                       NULL,
276                                                       NULL,
277                                                       NULL);
278   if (NULL == entry->transport_handle)
279   {
280     GNUNET_break (0);
281     return;
282   }
283   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == entry->notify_task);
284   if (0 == entry->demand)
285     return;
286   if (GNUNET_NO == entry->cghq_head->notify_called)
287     entry->notify_task = GNUNET_SCHEDULER_add_now (&call_cgh_cb, entry);
288 }
289
290
291 static void
292 oprelease_get_handle_transport (void *cls)
293 {
294   struct CacheEntry *entry = cls;
295
296   if (NULL == entry->transport_handle)
297     return;
298   GNUNET_TRANSPORT_disconnect (entry->transport_handle);
299   entry->transport_handle = NULL;
300 }
301
302
303 static struct GSTCacheGetHandle *
304 cache_get_handle (unsigned int peer_id,
305                   struct GSTCacheGetHandle *cgh,
306                   const struct GNUNET_CONFIGURATION_Handle *cfg)
307 {
308   struct GNUNET_HashCode key;
309   void *handle;
310   struct CacheEntry *entry;
311
312   GNUNET_assert (0 != cgh->type);
313   GNUNET_CRYPTO_hash (&peer_id, sizeof (peer_id), &key);
314   handle = NULL;
315   entry = NULL;
316   switch (cgh->type)
317   {
318   case CGT_TRANSPORT_HANDLE:
319     entry = cache_lookup_handles (&key, (struct GNUNET_TRANSPORT_Handle **) &handle);
320     break;
321   }
322   if (NULL != handle)
323   {
324     GNUNET_assert (NULL != entry);
325     LOG_DEBUG ("Found existing transport handle in cache\n");
326     if (0 == entry->demand)
327       GNUNET_CONTAINER_DLL_remove (lru_cache_head, lru_cache_tail, entry);
328   }
329   if (NULL == entry)
330     entry = add_entry (&key, peer_id);
331   if (NULL == entry->cfg)
332     entry->cfg = GNUNET_CONFIGURATION_dup (cfg);
333   entry->demand++;
334   cgh->entry = entry;
335   GNUNET_CONTAINER_DLL_insert (entry->cghq_head, entry->cghq_tail, cgh);
336   if ((NULL != entry->transport_handle) || (NULL != entry->transport_op))
337   {
338     if (GNUNET_SCHEDULER_NO_TASK == entry->notify_task)
339       entry->notify_task = GNUNET_SCHEDULER_add_now (&call_cgh_cb, entry);
340     return cgh;
341   }
342   switch (cgh->type)
343   {
344   case CGT_TRANSPORT_HANDLE:
345     GNUNET_assert (NULL == entry->transport_op);
346     entry->transport_op = GNUNET_TESTBED_operation_create_ (entry, &opstart_get_handle_transport,
347                                                             &oprelease_get_handle_transport);
348     GNUNET_TESTBED_operation_queue_insert_ (GST_opq_openfds,
349                                             entry->transport_op);
350     GNUNET_TESTBED_operation_begin_wait_ (entry->transport_op);
351     break;
352   }
353   return cgh;
354 }
355
356 /**
357  * Iterator over hash map entries.
358  *
359  * @param cls closure
360  * @param key current key code
361  * @param value value in the hash map
362  * @return GNUNET_YES if we should continue to
363  *         iterate,
364  *         GNUNET_NO if not.
365  */
366 static int
367 cache_clear_iterator (void *cls,
368                       const struct GNUNET_HashCode * key,
369                       void *value)
370 {
371   struct CacheEntry *entry = value;
372   static unsigned int ncleared;
373
374   GNUNET_assert (NULL != entry);
375   GNUNET_break (0 == entry->demand);
376   LOG_DEBUG ("Clearing entry %u of %u\n", ++ncleared, cache_size);
377   GNUNET_CONTAINER_multihashmap_remove (cache, key, value);
378   if (0 == entry->demand)
379     cache_remove (entry);
380   GNUNET_free_non_null (entry->hello);
381   GNUNET_break (NULL == entry->transport_handle);
382   GNUNET_break (NULL == entry->cfg);
383   GNUNET_free (entry);
384   return GNUNET_YES;
385 }
386
387
388 /**
389  * Clear cache
390  */
391 void
392 GST_cache_clear ()
393 {
394   GNUNET_CONTAINER_multihashmap_iterate (cache, &cache_clear_iterator, NULL);
395   GNUNET_assert (0 == GNUNET_CONTAINER_multihashmap_size (cache));
396   GNUNET_CONTAINER_multihashmap_destroy (cache);
397 }
398
399
400 /**
401  * Initializes the cache
402  *
403  * @param size the size of the cache
404  */
405 void
406 GST_cache_init (unsigned int size)
407 {
408   if (0 == size)
409     return;
410   lru_cache_threshold_size = size;
411   if (size > 1)
412     size = size / 2;
413   cache = GNUNET_CONTAINER_multihashmap_create (size, GNUNET_YES);
414 }
415
416
417 /**
418  * Mark the GetCacheHandle as being done if a handle has been provided already
419  * or as being cancelled if the callback for the handle hasn't been called.
420  *
421  * @param cgh the CacheGetHandle handle
422  */
423 void
424 GST_cache_get_handle_done (struct GSTCacheGetHandle *cgh)
425 {
426   GNUNET_assert (NULL != cgh->entry);
427   GNUNET_assert (0 < cgh->entry->demand);
428   cgh->entry->demand--;
429   if (GNUNET_SCHEDULER_NO_TASK != cgh->entry->notify_task)
430   {
431     GNUNET_SCHEDULER_cancel (cgh->entry->notify_task);
432     cgh->entry->notify_task = GNUNET_SCHEDULER_NO_TASK;
433   }
434   GNUNET_CONTAINER_DLL_remove (cgh->entry->cghq_head,
435                                cgh->entry->cghq_tail,
436                                cgh);  
437   if (0 == cgh->entry->demand)
438   {
439     GNUNET_CONTAINER_DLL_insert_tail (lru_cache_head, lru_cache_tail, cgh->entry);
440     if (lru_cache_size > lru_cache_threshold_size)
441       cache_remove (lru_cache_head);
442   }
443   else
444   {
445     if (GNUNET_NO == cgh->entry->cghq_head->notify_called)
446       cgh->entry->notify_task = GNUNET_SCHEDULER_add_now (&call_cgh_cb, cgh->entry);
447   }
448   GNUNET_free (cgh);
449 }
450
451
452 /**
453  * Get a transport handle with the given configuration. If the handle is already
454  * cached before, it will be retured in the given callback; the peer_id is used to lookup in the
455  * cache. If not a new operation is started to open the transport handle and
456  * will be given in the callback when it is available.
457  *
458  * @param peer_id the index of the peer
459  * @param cfg the configuration with which the transport handle has to be
460  *          created if it was not present in the cache
461  * @param cb the callback to notify when the transport handle is available
462  * @param cb_cls the closure for the above callback
463  * @return the handle which can be used cancel or mark that the handle is no
464  *           longer being used
465  */
466 struct GSTCacheGetHandle *
467 GST_cache_get_handle_transport (unsigned int peer_id,
468                                 const struct GNUNET_CONFIGURATION_Handle *cfg,
469                                 GST_cache_callback cb,
470                                 void *cb_cls)
471 {
472   struct GSTCacheGetHandle *cgh;
473
474   cgh = GNUNET_malloc (sizeof (struct GSTCacheGetHandle));
475   cgh->cb = cb;
476   cgh->cb_cls = cb_cls;
477   cgh->type = CGT_TRANSPORT_HANDLE;
478   return cache_get_handle (peer_id, cgh, cfg);
479 }
480
481
482 /**
483  * Looks up in the hello cache and returns the HELLO of the given peer
484  *
485  * @param peer_id the index of the peer whose HELLO has to be looked up
486  * @return the HELLO message; NULL if not found
487  */
488 const struct GNUNET_MessageHeader *
489 GST_cache_lookup_hello (const unsigned int peer_id)
490 {
491   struct CacheEntry *entry;
492   struct GNUNET_HashCode key;
493   
494   LOG_DEBUG ("Looking up HELLO for peer %u\n", peer_id);
495   GNUNET_CRYPTO_hash (&peer_id, sizeof (peer_id), &key);
496   entry = cache_lookup (&key);
497   if (NULL == entry)
498     return NULL;
499   if (NULL != entry->hello)
500     LOG_DEBUG ("HELLO found for peer %u\n", peer_id);
501   return entry->hello;
502 }
503
504
505 /**
506  * Caches the HELLO of the given peer. Updates the HELLO if it was already
507  * cached before
508  *
509  * @param id the peer identity of the peer whose HELLO has to be cached
510  * @param hello the HELLO message
511  */
512 void
513 GST_cache_add_hello (const unsigned int peer_id,
514                      const struct GNUNET_MessageHeader *hello)
515 {
516   struct CacheEntry *entry;
517   struct GNUNET_HashCode key;
518
519   GNUNET_CRYPTO_hash (&peer_id, sizeof (peer_id), &key);
520   entry = GNUNET_CONTAINER_multihashmap_get (cache, &key);
521   if (NULL == entry)
522     entry = add_entry (&key, peer_id);
523   GNUNET_free_non_null (entry->hello);
524   entry->hello = GNUNET_copy_message (hello);
525 }
526
527 /* end of gnunet-service-testbed_hc.c */