completed big block refactoring in preparation for SET-BLOCK integration
[oweals/gnunet.git] / src / dht / gnunet-service-dht_routing.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2011 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file dht/gnunet-service-dht_routing.c
23  * @brief GNUnet DHT tracking of requests for routing replies
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet-service-dht_neighbours.h"
28 #include "gnunet-service-dht_routing.h"
29 #include "gnunet-service-dht.h"
30
31
32 /**
33  * Number of requests we track at most (for routing replies).
34  */
35 #define DHT_MAX_RECENT (1024 * 16)
36
37
38 /**
39  * Information we keep about all recent GET requests
40  * so that we can route replies.
41  */
42 struct RecentRequest
43 {
44
45   /**
46    * The peer this request was received from.
47    */
48   struct GNUNET_PeerIdentity peer;
49
50   /**
51    * Key of this request.
52    */
53   struct GNUNET_HashCode key;
54
55   /**
56    * Position of this node in the min heap.
57    */
58   struct GNUNET_CONTAINER_HeapNode *heap_node;
59
60   /**
61    * Block group for filtering replies.
62    */
63   struct GNUNET_BLOCK_Group *bg;
64
65   /**
66    * Type of the requested block.
67    */
68   enum GNUNET_BLOCK_Type type;
69
70   /**
71    * extended query (see gnunet_block_lib.h).  Allocated at the
72    * end of this struct.
73    */
74   const void *xquery;
75
76   /**
77    * Number of bytes in xquery.
78    */
79   size_t xquery_size;
80
81   /**
82    * Request options.
83    */
84   enum GNUNET_DHT_RouteOption options;
85
86 };
87
88
89 /**
90  * Recent requests by time inserted.
91  */
92 static struct GNUNET_CONTAINER_Heap *recent_heap;
93
94 /**
95  * Recently seen requests by key.
96  */
97 static struct GNUNET_CONTAINER_MultiHashMap *recent_map;
98
99
100 /**
101  * Closure for the 'process' function.
102  */
103 struct ProcessContext
104 {
105   /**
106    * Path of the original PUT
107    */
108   const struct GNUNET_PeerIdentity *put_path;
109
110   /**
111    * Path of the reply.
112    */
113   const struct GNUNET_PeerIdentity *get_path;
114
115   /**
116    * Payload of the reply.
117    */
118   const void *data;
119
120   /**
121    * Expiration time of the result.
122    */
123   struct GNUNET_TIME_Absolute expiration_time;
124
125   /**
126    * Number of entries in @e put_path.
127    */
128   unsigned int put_path_length;
129
130   /**
131    * Number of entries in @e get_path.
132    */
133   unsigned int get_path_length;
134
135   /**
136    * Number of bytes in @e data.
137    */
138   size_t data_size;
139
140   /**
141    * Type of the reply.
142    */
143   enum GNUNET_BLOCK_Type type;
144
145 };
146
147
148 /**
149  * Forward the result to the given peer if it matches the request.
150  *
151  * @param cls the `struct ProcessContext` with the result
152  * @param key the query
153  * @param value the `struct RecentRequest` with the request
154  * @return #GNUNET_OK (continue to iterate),
155  *         #GNUNET_SYSERR if the result is malformed or type unsupported
156  */
157 static int
158 process (void *cls,
159          const struct GNUNET_HashCode *key,
160          void *value)
161 {
162   struct ProcessContext *pc = cls;
163   struct RecentRequest *rr = value;
164   enum GNUNET_BLOCK_EvaluationResult eval;
165   unsigned int gpl;
166   unsigned int ppl;
167   struct GNUNET_HashCode hc;
168   const struct GNUNET_HashCode *eval_key;
169
170   if ( (rr->type != GNUNET_BLOCK_TYPE_ANY) &&
171        (rr->type != pc->type) )
172     return GNUNET_OK;           /* type missmatch */
173
174   if (0 != (rr->options & GNUNET_DHT_RO_RECORD_ROUTE))
175   {
176     gpl = pc->get_path_length;
177     ppl = pc->put_path_length;
178   }
179   else
180   {
181     gpl = 0;
182     ppl = 0;
183   }
184   if ( (0 != (rr->options & GNUNET_DHT_RO_FIND_PEER)) &&
185        (pc->type == GNUNET_BLOCK_TYPE_DHT_HELLO) )
186   {
187     /* key may not match HELLO, which is OK since
188      * the search is approximate.  Still, the evaluation
189      * would fail since the match is not exact.  So
190      * we fake it by changing the key to the actual PID ... */
191     GNUNET_BLOCK_get_key (GDS_block_context,
192                           GNUNET_BLOCK_TYPE_DHT_HELLO,
193                           pc->data,
194                           pc->data_size,
195                           &hc);
196     eval_key = &hc;
197   }
198   else
199   {
200     eval_key = key;
201   }
202   eval
203     = GNUNET_BLOCK_evaluate (GDS_block_context,
204                              pc->type,
205                              rr->bg,
206                              GNUNET_BLOCK_EO_NONE,
207                              eval_key,
208                              rr->xquery,
209                              rr->xquery_size,
210                              pc->data,
211                              pc->data_size);
212   switch (eval)
213   {
214   case GNUNET_BLOCK_EVALUATION_OK_MORE:
215   case GNUNET_BLOCK_EVALUATION_OK_LAST:
216     GNUNET_STATISTICS_update (GDS_stats,
217                               gettext_noop
218                               ("# Good REPLIES matched against routing table"),
219                               1, GNUNET_NO);
220     GDS_NEIGHBOURS_handle_reply (&rr->peer,
221                                  pc->type,
222                                  pc->expiration_time,
223                                  key,
224                                  ppl, pc->put_path,
225                                  gpl, pc->get_path,
226                                  pc->data,
227                                  pc->data_size);
228     break;
229   case GNUNET_BLOCK_EVALUATION_OK_DUPLICATE:
230     GNUNET_STATISTICS_update (GDS_stats,
231                               gettext_noop
232                               ("# Duplicate REPLIES matched against routing table"),
233                               1, GNUNET_NO);
234     return GNUNET_OK;
235   case GNUNET_BLOCK_EVALUATION_RESULT_INVALID:
236     GNUNET_STATISTICS_update (GDS_stats,
237                               gettext_noop
238                               ("# Invalid REPLIES matched against routing table"),
239                               1, GNUNET_NO);
240     return GNUNET_SYSERR;
241   case GNUNET_BLOCK_EVALUATION_RESULT_IRRELEVANT:
242     GNUNET_STATISTICS_update (GDS_stats,
243                               gettext_noop
244                               ("# Irrelevant REPLIES matched against routing table"),
245                               1, GNUNET_NO);
246     return GNUNET_OK;
247   case GNUNET_BLOCK_EVALUATION_REQUEST_VALID:
248     GNUNET_break (0);
249     return GNUNET_OK;
250   case GNUNET_BLOCK_EVALUATION_REQUEST_INVALID:
251     GNUNET_break (0);
252     return GNUNET_OK;
253   case GNUNET_BLOCK_EVALUATION_TYPE_NOT_SUPPORTED:
254     GNUNET_STATISTICS_update (GDS_stats,
255                               gettext_noop
256                               ("# Unsupported REPLIES matched against routing table"),
257                               1, GNUNET_NO);
258     return GNUNET_SYSERR;
259   default:
260     GNUNET_break (0);
261     return GNUNET_SYSERR;
262   }
263   return GNUNET_OK;
264 }
265
266
267 /**
268  * Handle a reply (route to origin).  Only forwards the reply back to
269  * other peers waiting for it.  Does not do local caching or
270  * forwarding to local clients.  Essentially calls
271  * GDS_NEIGHBOURS_handle_reply for all peers that sent us a matching
272  * request recently.
273  *
274  * @param type type of the block
275  * @param expiration_time when does the content expire
276  * @param key key for the content
277  * @param put_path_length number of entries in @a put_path
278  * @param put_path peers the original PUT traversed (if tracked)
279  * @param get_path_length number of entries in @a get_path
280  * @param get_path peers this reply has traversed so far (if tracked)
281  * @param data payload of the reply
282  * @param data_size number of bytes in data
283  */
284 void
285 GDS_ROUTING_process (void *cls,
286                      enum GNUNET_BLOCK_Type type,
287                      struct GNUNET_TIME_Absolute expiration_time,
288                      const struct GNUNET_HashCode *key,
289                      unsigned int put_path_length,
290                      const struct GNUNET_PeerIdentity *put_path,
291                      unsigned int get_path_length,
292                      const struct GNUNET_PeerIdentity *get_path,
293                      const void *data,
294                      size_t data_size)
295 {
296   struct ProcessContext pc;
297
298   pc.type = type;
299   pc.expiration_time = expiration_time;
300   pc.put_path_length = put_path_length;
301   pc.put_path = put_path;
302   pc.get_path_length = get_path_length;
303   pc.get_path = get_path;
304   pc.data = data;
305   pc.data_size = data_size;
306   if (NULL == data)
307   {
308     /* Some apps might have an 'empty' reply as a valid reply; however,
309        'process' will call GNUNET_BLOCK_evaluate' which treats a 'NULL'
310        reply as request-validation (but we need response-validation).
311        So we set 'data' to a 0-byte non-NULL value just to be sure */
312     GNUNET_break (0 == data_size);
313     pc.data_size = 0;
314     pc.data = ""; /* something not null */
315   }
316   GNUNET_CONTAINER_multihashmap_get_multiple (recent_map,
317                                               key,
318                                               &process,
319                                               &pc);
320 }
321
322
323 /**
324  * Remove the oldest entry from the DHT routing table.  Must only
325  * be called if it is known that there is at least one entry
326  * in the heap and hashmap.
327  */
328 static void
329 expire_oldest_entry ()
330 {
331   struct RecentRequest *recent_req;
332
333   GNUNET_STATISTICS_update (GDS_stats,
334                             gettext_noop
335                             ("# Entries removed from routing table"), 1,
336                             GNUNET_NO);
337   recent_req = GNUNET_CONTAINER_heap_peek (recent_heap);
338   GNUNET_assert (recent_req != NULL);
339   GNUNET_CONTAINER_heap_remove_node (recent_req->heap_node);
340   GNUNET_BLOCK_group_destroy (recent_req->bg);
341   GNUNET_assert (GNUNET_YES ==
342                  GNUNET_CONTAINER_multihashmap_remove (recent_map,
343                                                        &recent_req->key,
344                                                        recent_req));
345   GNUNET_free (recent_req);
346 }
347
348
349 /**
350  * Try to combine multiple recent requests for the same value
351  * (if they come from the same peer).
352  *
353  * @param cls the new 'struct RecentRequest' (to discard upon successful combination)
354  * @param key the query
355  * @param value the existing 'struct RecentRequest' (to update upon successful combination)
356  * @return #GNUNET_OK (continue to iterate),
357  *         #GNUNET_SYSERR if the request was successfully combined
358  */
359 static int
360 try_combine_recent (void *cls,
361                     const struct GNUNET_HashCode *key,
362                     void *value)
363 {
364   struct RecentRequest *in = cls;
365   struct RecentRequest *rr = value;
366
367   if ( (0 != memcmp (&in->peer,
368                      &rr->peer,
369                      sizeof (struct GNUNET_PeerIdentity))) ||
370        (in->type != rr->type) ||
371        (in->xquery_size != rr->xquery_size) ||
372        (0 != memcmp (in->xquery,
373                      rr->xquery,
374                      in->xquery_size)) )
375     return GNUNET_OK;
376   GNUNET_break (GNUNET_SYSERR !=
377                 GNUNET_BLOCK_group_merge (in->bg,
378                                           rr->bg));
379   rr->bg = in->bg;
380   GNUNET_free (in);
381   return GNUNET_SYSERR;
382 }
383
384
385 /**
386  * Add a new entry to our routing table.
387  *
388  * @param sender peer that originated the request
389  * @param type type of the block
390  * @param options options for processing
391  * @param key key for the content
392  * @param xquery extended query
393  * @param xquery_size number of bytes in @a xquery
394  * @param reply_bf bloomfilter to filter duplicates
395  * @param reply_bf_mutator mutator for @a reply_bf
396  */
397 void
398 GDS_ROUTING_add (const struct GNUNET_PeerIdentity *sender,
399                  enum GNUNET_BLOCK_Type type,
400                  struct GNUNET_BLOCK_Group *bg,
401                  enum GNUNET_DHT_RouteOption options,
402                  const struct GNUNET_HashCode *key,
403                  const void *xquery,
404                  size_t xquery_size)
405 {
406   struct RecentRequest *recent_req;
407
408   while (GNUNET_CONTAINER_heap_get_size (recent_heap) >= DHT_MAX_RECENT)
409     expire_oldest_entry ();
410   GNUNET_STATISTICS_update (GDS_stats,
411                             gettext_noop ("# Entries added to routing table"),
412                             1,
413                             GNUNET_NO);
414   recent_req = GNUNET_malloc (sizeof (struct RecentRequest) + xquery_size);
415   recent_req->peer = *sender;
416   recent_req->key = *key;
417   recent_req->bg = bg;
418   recent_req->type = type;
419   recent_req->options = options;
420   recent_req->xquery = &recent_req[1];
421   GNUNET_memcpy (&recent_req[1],
422                  xquery,
423                  xquery_size);
424   recent_req->xquery_size = xquery_size;
425   if (GNUNET_SYSERR ==
426       GNUNET_CONTAINER_multihashmap_get_multiple (recent_map,
427                                                   key,
428                                                   &try_combine_recent,
429                                                   recent_req))
430   {
431     GNUNET_STATISTICS_update (GDS_stats,
432                               gettext_noop
433                               ("# DHT requests combined"),
434                               1, GNUNET_NO);
435     return;
436   }
437   recent_req->heap_node
438     = GNUNET_CONTAINER_heap_insert (recent_heap,
439                                     recent_req,
440                                     GNUNET_TIME_absolute_get ().abs_value_us);
441   GNUNET_CONTAINER_multihashmap_put (recent_map,
442                                      key,
443                                      recent_req,
444                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
445 }
446
447
448 /**
449  * Initialize routing subsystem.
450  */
451 void
452 GDS_ROUTING_init ()
453 {
454   recent_heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
455   recent_map = GNUNET_CONTAINER_multihashmap_create (DHT_MAX_RECENT * 4 / 3, GNUNET_NO);
456 }
457
458
459 /**
460  * Shutdown routing subsystem.
461  */
462 void
463 GDS_ROUTING_done ()
464 {
465   while (GNUNET_CONTAINER_heap_get_size (recent_heap) > 0)
466     expire_oldest_entry ();
467   GNUNET_assert (0 == GNUNET_CONTAINER_heap_get_size (recent_heap));
468   GNUNET_CONTAINER_heap_destroy (recent_heap);
469   recent_heap = NULL;
470   GNUNET_assert (0 == GNUNET_CONTAINER_multihashmap_size (recent_map));
471   GNUNET_CONTAINER_multihashmap_destroy (recent_map);
472   recent_map = NULL;
473 }
474
475 /* end of gnunet-service-dht_routing.c */