more work on simple plugin
[oweals/gnunet.git] / src / ats / plugin_ats2_simple.c
1 /*
2  This file is part of GNUnet.
3  Copyright (C) 2011-2015, 2018 GNUnet e.V.
4
5  GNUnet is free software: you can redistribute it and/or modify it
6  under the terms of the GNU Affero General Public License as published
7  by the Free Software Foundation, either version 3 of the License,
8  or (at your 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  Affero General Public License for more details.
14
15  You should have received a copy of the GNU Affero General Public License
16  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 /**
19  * @file ats/plugin_ats2_simple.c
20  * @brief ATS simple solver
21  * @author Matthias Wachs
22  * @author Christian Grothoff
23  *
24  * TODO:
25  * - subscribe to PEERSTORE when short on HELLOs (given application preferences!)
26  * - keep track of HELLOs and when we tried them last => re-suggest
27  * - sum up preferences per peer, keep totals! => PeerMap pid -> [preferences + sessions + addrs!]
28  * - sum up preferences overall, keep global sum => starting point for "proportional"
29  * - store DLL of available sessions per peer
30  */
31 #include "platform.h"
32 #include "gnunet_ats_plugin_new.h"
33 #include "gnunet_peerstore_service.h"
34
35 #define LOG(kind,...) GNUNET_log_from (kind, "ats-simple",__VA_ARGS__)
36
37
38 /**
39  * A handle for the proportional solver
40  */
41 struct SimpleHandle;
42
43
44 /**
45  * Entry in list of addresses we could try per peer.
46  */
47 struct Hello
48 {
49
50   /**
51    * Kept in a DLL.
52    */
53   struct Hello *next;
54
55   /**
56    * Kept in a DLL.
57    */
58   struct Hello *prev;
59
60   /**
61    * The address we could try.
62    */
63   const char *address;
64
65   /**
66    * When did we try it last?
67    */
68   struct GNUNET_TIME_Absolute last_attempt;
69
70   /**
71    * Current exponential backoff value.
72    */
73   struct GNUNET_TIME_Relative backoff;
74
75   /**
76    * Is a session with this address already up?
77    * If not, set to NULL.
78    */
79   struct GNUNET_ATS_SessionHandle *sh;
80
81 };
82
83
84 /**
85  * Information about preferences and sessions we track
86  * per peer.
87  */
88 struct Peer;
89
90
91 /**
92  * Internal representation of a session by the plugin.
93  * (If desired, plugin may just use NULL.)
94  */
95 struct GNUNET_ATS_SessionHandle
96 {
97
98   /**
99    * Kept in DLL per peer.
100    */
101   struct GNUNET_ATS_SessionHandle *next;
102
103   /**
104    * Kept in DLL per peer.
105    */
106   struct GNUNET_ATS_SessionHandle *prev;
107
108   /**
109    * The session in the main ATS service.
110    */
111   struct GNUNET_ATS_Session *session;
112
113   /**
114    * Current performance data for this @e session
115    */
116   const struct GNUNET_ATS_SessionData *data;
117
118   /**
119    * Hello matching this session, or NULL for none.
120    */
121   struct Hello *hello;
122
123   /**
124    * Peer this session is for.
125    */
126   struct Peer *peer;
127
128   /**
129    * Address used by this session (largely for debugging).
130    */
131   const char *address;
132
133   /**
134    * Last BW-in allocation given to the transport service.
135    */
136   struct GNUNET_BANDWIDTH_Value32NBO bw_in;
137
138   /**
139    * Last BW-out allocation given to the transport service.
140    */
141   struct GNUNET_BANDWIDTH_Value32NBO bw_out;
142
143 };
144
145
146 /**
147  * Information about preferences and sessions we track
148  * per peer.
149  */
150 struct Peer
151 {
152
153   /**
154    * Kept in DLL per peer.
155    */
156   struct GNUNET_ATS_SessionHandle *sh_head;
157
158   /**
159    * Kept in DLL per peer.
160    */
161   struct GNUNET_ATS_SessionHandle *sh_tail;
162
163   /**
164    * Kept in a DLL.
165    */
166   struct Hello *h_head;
167
168   /**
169    * Kept in a DLL.
170    */
171   struct Hello *h_tail;
172
173   /**
174    * The handle for the proportional solver
175    */
176   struct SimpleHandle *h;
177
178   /**
179    * Which peer is this for?
180    */
181   struct GNUNET_PeerIdentity pid;
182
183   /**
184    * Array where we sum up the bandwidth requests received indexed
185    * by preference kind (see `enum GNUNET_MQ_PreferenceKind`)
186    */
187   uint64_t bw_by_pk[GNUNET_MQ_PREFERENCE_COUNT];
188
189   /**
190    * Watch context where we are currently looking for HELLOs for
191    * this peer.
192    */
193   struct GNUNET_PEERSTORE_WatchContext *wc;
194
195   /**
196    * Task used to try again to suggest an address for this peer.
197    */
198   struct GNUNET_SCHEDULER_Task *task;
199
200 };
201
202
203 /**
204  * Representation of a network (to be expanded...)
205  */
206 struct Network
207 {
208
209   /**
210    * Total inbound quota
211    */
212   unsigned long long total_quota_in;
213
214   /**
215    * Total outbound quota
216    */
217   unsigned long long total_quota_out;
218
219   /**
220    * ATS network type
221    */
222   enum GNUNET_NetworkType type;
223
224 };
225
226
227 /**
228  * A handle for the proportional solver
229  */
230 struct SimpleHandle
231 {
232
233   /**
234    * Our execution environment.
235    */
236   struct GNUNET_ATS_PluginEnvironment *env;
237
238   /**
239    * Information we track for each peer.
240    */
241   struct GNUNET_CONTAINER_MultiPeerMap *peers;
242
243   /**
244    * Information we track per network type (quotas).
245    */
246   struct Network networks[GNUNET_NT_COUNT];
247
248   /**
249    * Handle to the peerstore service.
250    */
251   struct GNUNET_PEERSTORE_Handle *ps;
252
253 };
254
255
256 /**
257  * Lookup peer in the peers map.
258  *
259  * @param h handle to look up in
260  * @param pid peer identity to look up by
261  * @return NULL for not found
262  */
263 struct Peer *
264 lookup_peer (struct SimpleHandle *h,
265              const struct GNUNET_PeerIdentity *pid)
266 {
267   return GNUNET_CONTAINER_multipeermap_get (h->peers,
268                                             pid);
269 }
270
271
272 /**
273  * Check if there is _any_ interesting information left we
274  * store about the peer in @a p.
275  *
276  * @param p peer to test if we can drop the data structure
277  * @return #GNUNET_YES if no information is left in @a p
278  */
279 static int
280 peer_test_dead (struct Peer *p)
281 {
282   for (enum GNUNET_MQ_PreferenceKind pk = 0;
283        pk < GNUNET_MQ_PREFERENCE_COUNT;
284        pk++)
285     if (0 != p->bw_by_pk[pk])
286       return GNUNET_NO;
287   if (NULL != p->sh_head)
288     return GNUNET_NO;
289   return GNUNET_YES;
290 }
291
292
293 /**
294  * Function called by PEERSTORE for each matching record.
295  *
296  * @param cls closure with a `struct Peer`
297  * @param record peerstore record information
298  * @param emsg error message, or NULL if no errors
299  */
300 static void
301 watch_cb (void *cls,
302           const struct GNUNET_PEERSTORE_Record *record,
303           const char *emsg)
304 {
305   struct Peer *p = cls;
306
307   // FIXME: process hello!
308   // check for expiration
309   // (add to p's doubly-linked list)
310
311   if (NULL == p->task)
312   {
313     // start suggestion task!
314   }
315 }
316
317
318 /**
319  * Find or add peer if necessary.
320  *
321  * @param h our plugin handle
322  * @param pid the peer identity to add/look for
323  * @return a peer handle
324  */
325 static struct Peer *
326 peer_add (struct SimpleHandle *h,
327           const struct GNUNET_PeerIdentity *pid)
328 {
329   struct Peer *p = lookup_peer (h,
330                                 pid);
331
332   if (NULL != p)
333     return p;
334   p = GNUNET_new (struct Peer);
335   p->h = h;
336   p->pid = *pid;
337   p->wc = GNUNET_PEERSTORE_watch (h->ps,
338                                   "transport",
339                                   &p->pid,
340                                   "HELLO" /* key */,
341                                   &watch_cb,
342                                   p);
343   GNUNET_assert (GNUNET_YES ==
344                  GNUNET_CONTAINER_multipeermap_put (h->peers,
345                                                     &p->pid,
346                                                     p,
347                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
348
349   return p;
350 }
351
352
353 /**
354  * Free the entry (and associated tasks) of peer @a p.
355  * Note that @a p must be dead already (see #peer_test_dead()).
356  *
357  * @param p the peer to free
358  */
359 static void
360 peer_free (struct Peer *p)
361 {
362   struct SimpleHandle *h = p->h;
363   struct Hello *hello;
364
365   GNUNET_assert (NULL == p->sh_head);
366   while (NULL != (hello = p->h_head))
367   {
368     GNUNET_CONTAINER_DLL_remove (p->h_head,
369                                  p->h_tail,
370                                  hello);
371     GNUNET_assert (NULL == hello->sh);
372     GNUNET_free (hello);
373   }
374   if (NULL != p->task)
375   {
376     GNUNET_SCHEDULER_cancel (p->task);
377     p->task = NULL;
378   }
379   if (NULL != p->wc)
380   {
381     GNUNET_PEERSTORE_watch_cancel (p->wc);
382     p->wc = NULL;
383   }
384   GNUNET_assert (GNUNET_YES ==
385                  GNUNET_CONTAINER_multipeermap_remove (h->peers,
386                                                        &p->pid,
387                                                        p));
388   GNUNET_free (p);
389 }
390
391
392 /**
393  * The world changed, recalculate our allocations.
394  */
395 static void
396 update (struct SimpleHandle *h)
397 {
398   // recalculate allocations
399   // notify transport if it makes sense (delta significant)
400 }
401
402
403 /**
404  * The plugin should begin to respect a new preference.
405  *
406  * @param cls the closure
407  * @param pref the preference to add
408  * @return plugin's internal representation, or NULL
409  */
410 static struct GNUNET_ATS_PreferenceHandle *
411 simple_preference_add (void *cls,
412                        const struct GNUNET_ATS_Preference *pref)
413 {
414   struct SimpleHandle *h = cls;
415   struct Peer *p = peer_add (h,
416                              &pref->peer);
417
418   GNUNET_assert (pref->pk < GNUNET_MQ_PREFERENCE_COUNT);
419   p->bw_by_pk[pref->pk] += ntohl (pref->bw.value__);
420   update (h);
421   return NULL;
422 }
423
424
425 /**
426  * The plugin should end respecting a preference.
427  *
428  * @param cls the closure
429  * @param ph whatever @e preference_add returned
430  * @param pref the preference to delete
431  * @return plugin's internal representation, or NULL
432  */
433 static void
434 simple_preference_del (void *cls,
435                        struct GNUNET_ATS_PreferenceHandle *ph,
436                        const struct GNUNET_ATS_Preference *pref)
437 {
438   struct SimpleHandle *h = cls;
439   struct Peer *p = lookup_peer (h,
440                                 &pref->peer);
441
442   GNUNET_assert (NULL != p);
443   GNUNET_assert (pref->pk < GNUNET_MQ_PREFERENCE_COUNT);
444   p->bw_by_pk[pref->pk] -= ntohl (pref->bw.value__);
445   if ( (0 == p->bw_by_pk[pref->pk]) &&
446        (GNUNET_YES == peer_test_dead (p)) )
447     peer_free (p);
448   update (h);
449 }
450
451
452 /**
453  * Transport established a new session with performance
454  * characteristics given in @a data.
455  *
456  * @param cls closure
457  * @param data performance characteristics of @a sh
458  * @param address address information (for debugging)
459  * @return handle by which the plugin will identify this session
460  */
461 static struct GNUNET_ATS_SessionHandle *
462 simple_session_add (void *cls,
463                     const struct GNUNET_ATS_SessionData *data,
464                     const char *address)
465 {
466   struct SimpleHandle *h = cls;
467   struct Peer *p = peer_add (h,
468                              &data->peer);
469   struct Hello *hello;
470   size_t alen;
471   struct GNUNET_ATS_SessionHandle *sh;
472
473   /* setup session handle */
474   if (NULL == address)
475     alen = 0;
476   else
477     alen = strlen (address) + 1;
478   sh = GNUNET_malloc (sizeof (struct GNUNET_ATS_SessionHandle) + alen);
479   sh->peer = p;
480   sh->session = data->session;
481   sh->data = data;
482   if (NULL == address)
483   {
484     sh->address = NULL;
485   }
486   else
487   {
488     memcpy (&sh[1],
489             address,
490             alen);
491     sh->address = (const char *) &sh[1];
492   }
493   GNUNET_CONTAINER_DLL_insert (p->sh_head,
494                                p->sh_tail,
495                                sh);
496   /* match HELLO */
497   hello = p->h_head;
498   while ( (NULL != hello) &&
499           (0 != strcmp (address,
500                         hello->address)) )
501     hello = hello->next;
502   if (NULL != hello)
503   {
504     hello->sh = sh;
505     sh->hello = hello;
506   }
507   update (h);
508   return NULL;
509 }
510
511
512 /**
513  * @a data changed for a given @a sh, solver should consider
514  * the updated performance characteristics.
515  *
516  * @param cls closure
517  * @param sh session this is about
518  * @param data performance characteristics of @a sh
519  */
520 static void
521 simple_session_update (void *cls,
522                        struct GNUNET_ATS_SessionHandle *sh,
523                        const struct GNUNET_ATS_SessionData *data)
524 {
525   struct SimpleHandle *h = cls;
526
527   sh->data = data; /* this statement should not really do anything... */
528   update (h);
529 }
530
531
532 /**
533  * A session went away. Solver should update accordingly.
534  *
535  * @param cls closure
536  * @param sh session this is about
537  * @param data (last) performance characteristics of @a sh
538  */
539 static void
540 simple_session_del (void *cls,
541                     struct GNUNET_ATS_SessionHandle *sh,
542                     const struct GNUNET_ATS_SessionData *data)
543 {
544   struct SimpleHandle *h = cls;
545   struct Peer *p = sh->peer;
546
547   // FIXME: tear down session
548   // del peer if otherwise dead
549
550
551   if ( (NULL == p->sh_head) &&
552        (GNUNET_YES == peer_test_dead (p)) )
553     peer_free (p);
554   update (h);
555 }
556
557
558 #include "plugin_ats2_common.c"
559
560
561 /**
562  * Function invoked when the plugin is loaded.
563  *
564  * @param[in,out] cls the `struct GNUNET_ATS_PluginEnvironment *` to use;
565  *            modified to return the API functions (ugh).
566  * @return the `struct SimpleHandle` to pass as a closure
567  */
568 void *
569 libgnunet_plugin_ats2_simple_init (void *cls)
570 {
571   static struct GNUNET_ATS_SolverFunctions sf;
572   struct GNUNET_ATS_PluginEnvironment *env = cls;
573   struct SimpleHandle *s;
574
575   s = GNUNET_new (struct SimpleHandle);
576   s->env = env;
577   s->peers = GNUNET_CONTAINER_multipeermap_create (128,
578                                                    GNUNET_YES);
579   s->ps = GNUNET_PEERSTORE_connect (env->cfg);
580   sf.cls = s;
581   sf.preference_add = &simple_preference_add;
582   sf.preference_del = &simple_preference_del;
583   sf.session_add = &simple_session_add;
584   sf.session_update = &simple_session_update;
585   sf.session_del = &simple_session_del;
586   for (enum GNUNET_NetworkType nt = 0;
587        nt < GNUNET_NT_COUNT;
588        nt++)
589   {
590     const char *name = GNUNET_NT_to_string (nt);
591
592     if (NULL == name)
593     {
594       GNUNET_break (0);
595       break;
596     }
597     get_quota (env->cfg,
598                name,
599                "IN",
600                &s->networks[nt].total_quota_in);
601     get_quota (env->cfg,
602                name,
603                "OUT",
604                &s->networks[nt].total_quota_out);
605     s->networks[nt].type = nt;
606   }
607   return &sf;
608 }
609
610
611 /**
612  * Function used to unload the plugin.
613  *
614  * @param cls return value from #libgnunet_plugin_ats_proportional_init()
615  */
616 void *
617 libgnunet_plugin_ats2_simple_done (void *cls)
618 {
619   struct GNUNET_ATS_SolverFunctions *sf = cls;
620   struct SimpleHandle *s = sf->cls;
621
622   // FIXME: iterate over peers and clean up!
623   GNUNET_CONTAINER_multipeermap_destroy (s->peers);
624   GNUNET_PEERSTORE_disconnect (s->ps,
625                                GNUNET_NO);
626   GNUNET_free (s);
627   return NULL;
628 }
629
630
631 /* end of plugin_ats2_simple.c */