ae878a79e5daca2501e9c5a5cde91b882883f30a
[oweals/gnunet.git] / src / transport / transport-testing.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2006, 2009, 2015, 2016 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  * @file transport-testing.c
22  * @brief testing lib for transport service
23  * @author Matthias Wachs
24  * @author Christian Grothoff
25  */
26 #include "transport-testing.h"
27
28
29 #define LOG(kind,...) GNUNET_log_from(kind, "transport-testing", __VA_ARGS__)
30
31
32 static struct GNUNET_TRANSPORT_TESTING_PeerContext *
33 find_peer_context (struct GNUNET_TRANSPORT_TESTING_Handle *tth,
34                    const struct GNUNET_PeerIdentity *peer)
35 {
36   struct GNUNET_TRANSPORT_TESTING_PeerContext *t;
37
38   for (t = tth->p_head; NULL != t; t = t->next)
39     if (0 == memcmp (&t->id,
40                      peer,
41                      sizeof (struct GNUNET_PeerIdentity)))
42       return t;
43   return NULL;
44 }
45
46
47 static void
48 notify_connecting_context (struct GNUNET_TRANSPORT_TESTING_Handle *tth,
49                            struct GNUNET_TRANSPORT_TESTING_PeerContext *p1,
50                            struct GNUNET_TRANSPORT_TESTING_PeerContext *p2)
51 {
52   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc;
53   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *ccn;
54
55   for (cc = tth->cc_head; NULL != cc; cc = ccn)
56   {
57     ccn = cc->next;
58     if ( (cc->p1 == p1) &&
59          (cc->p2 == p2) )
60       cc->p1_c = GNUNET_YES;
61     if ( (cc->p1 == p2) &&
62          (cc->p2 == p1) )
63       cc->p2_c = GNUNET_YES;
64     if ( (cc->p1_c == GNUNET_YES) &&
65          (cc->p2_c == GNUNET_YES) )
66     {
67       cc->cb (cc->cb_cls);
68       GNUNET_TRANSPORT_TESTING_connect_peers_cancel (cc);
69     }
70   }
71 }
72
73
74 static void
75 notify_connect (void *cls,
76                 const struct GNUNET_PeerIdentity *peer)
77 {
78   struct GNUNET_TRANSPORT_TESTING_PeerContext *p = cls;
79   char *p2_s;
80   struct GNUNET_TRANSPORT_TESTING_PeerContext *p2;
81
82   p2 = find_peer_context (p->tth,
83                           peer);
84   if (NULL != p->nc)
85     p->nc (p->cb_cls,
86            peer);
87
88   if (p2 != NULL)
89     GNUNET_asprintf (&p2_s,
90                      "%u (`%s')",
91                      p2->no,
92                      GNUNET_i2s (&p2->id));
93   else
94     GNUNET_asprintf (&p2_s,
95                      "`%s'",
96                      GNUNET_i2s (peer));
97   LOG (GNUNET_ERROR_TYPE_DEBUG,
98        "Peers %s connected to peer %u (`%s')\n",
99        p2_s,
100        p->no,
101        GNUNET_i2s (&p->id));
102   GNUNET_free (p2_s);
103   notify_connecting_context (p->tth,
104                              p,
105                              p2);
106 }
107
108
109 static void
110 notify_disconnect (void *cls,
111                    const struct GNUNET_PeerIdentity *peer)
112 {
113   struct GNUNET_TRANSPORT_TESTING_PeerContext *p = cls;
114   char *p2_s;
115   /* Find PeerContext */
116   int no = 0;
117   struct GNUNET_TRANSPORT_TESTING_PeerContext *p2 = NULL;
118
119   if (NULL != p)
120   {
121     p2 = find_peer_context (p->tth,
122                             peer);
123     no = p->no;
124   }
125
126   if (p2 != NULL)
127     GNUNET_asprintf (&p2_s,
128                      "%u (`%s')",
129                      p2->no,
130                      GNUNET_i2s (&p2->id));
131   else
132     GNUNET_asprintf (&p2_s,
133                      "`%s'",
134                      GNUNET_i2s (peer));
135   LOG (GNUNET_ERROR_TYPE_DEBUG,
136        "Peers %s disconnected from peer %u (`%s')\n",
137        p2_s,
138        no,
139        GNUNET_i2s (&p->id));
140   GNUNET_free (p2_s);
141
142   if (NULL == p)
143     return;
144   if (NULL != p->nd)
145     p->nd (p->cb_cls,
146            peer);
147 }
148
149
150 static void
151 notify_receive (void *cls,
152                 const struct GNUNET_PeerIdentity *peer,
153                 const struct GNUNET_MessageHeader *message)
154 {
155   struct GNUNET_TRANSPORT_TESTING_PeerContext *p = cls;
156
157   if (NULL == p)
158     return;
159   if (NULL != p->rec)
160     p->rec (p->cb_cls,
161             peer,
162             message);
163 }
164
165
166 static void
167 get_hello (void *cb_cls,
168            const struct GNUNET_MessageHeader *message)
169 {
170   struct GNUNET_TRANSPORT_TESTING_PeerContext *p = cb_cls;
171   struct GNUNET_PeerIdentity hello_id;
172
173   GNUNET_assert (GNUNET_OK ==
174                  GNUNET_HELLO_get_id ((const struct GNUNET_HELLO_Message *) message,
175                                       &hello_id));
176   GNUNET_assert (0 == memcmp (&hello_id,
177                               &p->id,
178                               sizeof (hello_id)));
179   GNUNET_free_non_null (p->hello);
180   p->hello = (struct GNUNET_HELLO_Message *) GNUNET_copy_message (message);
181
182   if (NULL != p->start_cb)
183   {
184     LOG (GNUNET_ERROR_TYPE_DEBUG,
185          "Peer %u (`%s') successfully started\n",
186          p->no,
187          GNUNET_i2s (&p->id));
188     p->start_cb (p,
189                  p->cb_cls);
190     p->start_cb = NULL;
191   }
192 }
193
194
195 /**
196  * Start a peer with the given configuration
197  * @param tth the testing handle
198  * @param cfgname configuration file
199  * @param peer_id a unique number to identify the peer
200  * @param rec receive callback
201  * @param nc connect callback
202  * @param nd disconnect callback
203  * @param start_cb start callback
204  * @param cb_cls closure for callback
205  * @return the peer context
206  */
207 struct GNUNET_TRANSPORT_TESTING_PeerContext *
208 GNUNET_TRANSPORT_TESTING_start_peer (struct GNUNET_TRANSPORT_TESTING_Handle *tth,
209                                      const char *cfgname,
210                                      int peer_id,
211                                      GNUNET_TRANSPORT_ReceiveCallback rec,
212                                      GNUNET_TRANSPORT_NotifyConnect nc,
213                                      GNUNET_TRANSPORT_NotifyDisconnect nd,
214                                      GNUNET_TRANSPORT_TESTING_StartCallback start_cb,
215                                      void *cb_cls)
216 {
217   char *emsg = NULL;
218   struct GNUNET_TRANSPORT_TESTING_PeerContext *p;
219   struct GNUNET_PeerIdentity *dummy;
220
221   if (GNUNET_NO == GNUNET_DISK_file_test (cfgname))
222   {
223     LOG (GNUNET_ERROR_TYPE_ERROR,
224          "File not found: `%s'\n",
225          cfgname);
226     return NULL;
227   }
228
229   p = GNUNET_new (struct GNUNET_TRANSPORT_TESTING_PeerContext);
230   p->tth = tth;
231   p->nc = nc;
232   p->nd = nd;
233   p->rec = rec;
234   p->start_cb = start_cb;
235   if (cb_cls != NULL)
236     p->cb_cls = cb_cls;
237   else
238     p->cb_cls = p;
239   GNUNET_CONTAINER_DLL_insert (tth->p_head,
240                                tth->p_tail,
241                                p);
242
243   /* Create configuration and call testing lib to modify it */
244   p->cfg = GNUNET_CONFIGURATION_create ();
245   GNUNET_assert (GNUNET_OK ==
246                  GNUNET_CONFIGURATION_load (p->cfg, cfgname));
247   if (GNUNET_SYSERR ==
248       GNUNET_TESTING_configuration_create (tth->tl_system,
249                                            p->cfg))
250   {
251     LOG (GNUNET_ERROR_TYPE_ERROR,
252          "Testing library failed to create unique configuration based on `%s'\n",
253          cfgname);
254     GNUNET_CONFIGURATION_destroy (p->cfg);
255     GNUNET_free (p);
256     return NULL;
257   }
258
259   p->no = peer_id;
260   /* Configure peer with configuration */
261   p->peer = GNUNET_TESTING_peer_configure (tth->tl_system,
262                                            p->cfg,
263                                            p->no,
264                                            NULL,
265                                            &emsg);
266   if (NULL == p->peer)
267   {
268     LOG (GNUNET_ERROR_TYPE_ERROR,
269          "Testing library failed to create unique configuration based on `%s': `%s'\n",
270          cfgname,
271          emsg);
272     GNUNET_TRANSPORT_TESTING_stop_peer (p);
273     GNUNET_free_non_null (emsg);
274     return NULL;
275   }
276   GNUNET_free_non_null (emsg);
277   if (GNUNET_OK != GNUNET_TESTING_peer_start (p->peer))
278   {
279     LOG (GNUNET_ERROR_TYPE_ERROR,
280          "Testing library failed to create unique configuration based on `%s'\n",
281          cfgname);
282     GNUNET_TRANSPORT_TESTING_stop_peer (p);
283     return NULL;
284   }
285
286   memset (&dummy,
287           '\0',
288           sizeof (dummy));
289   GNUNET_TESTING_peer_get_identity (p->peer,
290                                     &p->id);
291   if (0 == memcmp (&dummy,
292                    &p->id,
293                    sizeof (struct GNUNET_PeerIdentity)))
294   {
295     LOG (GNUNET_ERROR_TYPE_ERROR,
296          "Testing library failed to obtain peer identity for peer %u\n",
297          p->no);
298     GNUNET_TRANSPORT_TESTING_stop_peer (p);
299     return NULL;
300   }
301   LOG (GNUNET_ERROR_TYPE_DEBUG,
302        "Peer %u configured with identity `%s'\n",
303        p->no,
304        GNUNET_i2s_full (&p->id));
305
306   p->th = GNUNET_TRANSPORT_connect (p->cfg,
307                                     NULL,
308                                     p,
309                                     &notify_receive,
310                                     &notify_connect,
311                                     &notify_disconnect);
312   if (NULL == p->th)
313   {
314     LOG (GNUNET_ERROR_TYPE_ERROR,
315          "Failed to connect to transport service for peer `%s': `%s'\n",
316          cfgname,
317          emsg);
318     GNUNET_TRANSPORT_TESTING_stop_peer (p);
319     return NULL;
320   }
321   p->ats = GNUNET_ATS_connectivity_init (p->cfg);
322   if (NULL == p->ats)
323   {
324     LOG (GNUNET_ERROR_TYPE_ERROR,
325          "Failed to connect to ATS service for peer `%s': `%s'\n",
326          cfgname,
327          emsg);
328     GNUNET_TRANSPORT_TESTING_stop_peer (p);
329     return NULL;
330   }
331   p->ghh = GNUNET_TRANSPORT_get_hello (p->cfg,
332                                        &get_hello,
333                                        p);
334   GNUNET_assert (p->ghh != NULL);
335   return p;
336 }
337
338
339 /**
340  * Stops and restarts the given peer, sleeping (!) for 5s in between.
341  *
342  * @param p the peer
343  * @param restart_cb callback to call when restarted
344  * @param cb_cls callback closure
345  * @return #GNUNET_OK in success otherwise #GNUNET_SYSERR
346  */
347 int
348 GNUNET_TRANSPORT_TESTING_restart_peer (struct GNUNET_TRANSPORT_TESTING_PeerContext *p,
349                                        GNUNET_TRANSPORT_TESTING_StartCallback restart_cb,
350                                        void *cb_cls)
351 {
352   /* shutdown */
353   LOG (GNUNET_ERROR_TYPE_DEBUG,
354        "Stopping peer %u (`%s')\n",
355        p->no,
356        GNUNET_i2s (&p->id));
357   if (NULL != p->ghh)
358   {
359     GNUNET_TRANSPORT_get_hello_cancel (p->ghh);
360     p->ghh = NULL;
361   }
362   if (NULL != p->th)
363   {
364     GNUNET_TRANSPORT_disconnect (p->th);
365     p->th = NULL;
366   }
367   if (NULL != p->ats)
368   {
369     GNUNET_ATS_connectivity_done (p->ats);
370     p->ats = NULL;
371   }
372   if (GNUNET_SYSERR ==
373       GNUNET_TESTING_peer_stop (p->peer))
374   {
375     LOG (GNUNET_ERROR_TYPE_ERROR,
376          "Failed to stop peer %u (`%s')\n",
377          p->no,
378          GNUNET_i2s (&p->id));
379     return GNUNET_SYSERR;
380   }
381
382   sleep (5); // YUCK!
383
384   LOG (GNUNET_ERROR_TYPE_DEBUG,
385        "Restarting peer %u (`%s')\n",
386        p->no,
387        GNUNET_i2s (&p->id));
388   /* restart */
389   if (GNUNET_SYSERR == GNUNET_TESTING_peer_start (p->peer))
390   {
391     LOG (GNUNET_ERROR_TYPE_ERROR,
392          "Failed to restart peer %u (`%s')\n",
393          p->no,
394          GNUNET_i2s (&p->id));
395     return GNUNET_SYSERR;
396   }
397
398   GNUNET_assert (NULL == p->start_cb);
399   p->start_cb = restart_cb;
400   p->cb_cls = cb_cls;
401
402   p->th = GNUNET_TRANSPORT_connect (p->cfg,
403                                     NULL,
404                                     p,
405                                     &notify_receive,
406                                     &notify_connect,
407                                     &notify_disconnect);
408   GNUNET_assert (NULL != p->th);
409   p->ats = GNUNET_ATS_connectivity_init (p->cfg);
410   p->ghh = GNUNET_TRANSPORT_get_hello (p->cfg,
411                                        &get_hello,
412                                        p);
413   GNUNET_assert (NULL != p->ghh);
414   return GNUNET_OK;
415 }
416
417
418 /**
419  * Shutdown the given peer
420  *
421  * @param p the peer
422  */
423 void
424 GNUNET_TRANSPORT_TESTING_stop_peer (struct GNUNET_TRANSPORT_TESTING_PeerContext *p)
425 {
426   struct GNUNET_TRANSPORT_TESTING_Handle *tth = p->tth;
427
428   if (NULL != p->ghh)
429   {
430     GNUNET_TRANSPORT_get_hello_cancel (p->ghh);
431     p->ghh = NULL;
432   }
433   if (NULL != p->th)
434   {
435     GNUNET_TRANSPORT_disconnect (p->th);
436     p->th = NULL;
437   }
438   if (NULL != p->peer)
439   {
440     if (GNUNET_OK !=
441         GNUNET_TESTING_peer_stop (p->peer))
442     {
443       LOG (GNUNET_ERROR_TYPE_DEBUG,
444            "Testing lib failed to stop peer %u (`%s')\n",
445            p->no,
446            GNUNET_i2s (&p->id));
447     }
448     GNUNET_TESTING_peer_destroy (p->peer);
449     p->peer = NULL;
450   }
451   if (NULL != p->ats)
452   {
453     GNUNET_ATS_connectivity_done (p->ats);
454     p->ats = NULL;
455   }
456   if (NULL != p->hello)
457   {
458     GNUNET_free (p->hello);
459     p->hello = NULL;
460   }
461   if (NULL != p->cfg)
462   {
463     GNUNET_CONFIGURATION_destroy (p->cfg);
464     p->cfg = NULL;
465   }
466   GNUNET_CONTAINER_DLL_remove (tth->p_head,
467                                tth->p_tail,
468                                p);
469   LOG (GNUNET_ERROR_TYPE_DEBUG,
470        "Peer %u (`%s') stopped\n",
471        p->no,
472        GNUNET_i2s (&p->id));
473   GNUNET_free (p);
474 }
475
476
477 /**
478  * Offer the current HELLO of P2 to P1.
479  *
480  * @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectRequest`
481  */
482 static void
483 offer_hello (void *cls);
484
485
486 /**
487  * Function called after the HELLO was passed to the
488  * transport service.
489  */
490 static void
491 hello_offered (void *cls)
492 {
493   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc = cls;
494
495   cc->oh = NULL;
496   cc->tct = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
497                                           &offer_hello,
498                                           cc);
499 }
500
501
502 /**
503  * Offer the current HELLO of P2 to P1.
504  *
505  * @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectRequest`
506  */
507 static void
508 offer_hello (void *cls)
509 {
510   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc = cls;
511   struct GNUNET_TRANSPORT_TESTING_PeerContext *p1 = cc->p1;
512   struct GNUNET_TRANSPORT_TESTING_PeerContext *p2 = cc->p2;
513
514   cc->tct = NULL;
515   {
516     char *p2_s = GNUNET_strdup (GNUNET_i2s (&p2->id));
517
518     LOG (GNUNET_ERROR_TYPE_DEBUG,
519          "Asking peer %u (`%s') to connect peer %u (`%s'), providing HELLO with %u bytes\n",
520          p1->no,
521          GNUNET_i2s (&p1->id),
522          p2->no,
523          p2_s,
524          GNUNET_HELLO_size (cc->p2->hello));
525     GNUNET_free (p2_s);
526   }
527
528   if (NULL != cc->oh)
529     GNUNET_TRANSPORT_offer_hello_cancel (cc->oh);
530   cc->oh =
531     GNUNET_TRANSPORT_offer_hello (cc->p1->cfg,
532                                   (const struct GNUNET_MessageHeader *) cc->p2->hello,
533                                   &hello_offered,
534                                   cc);
535 }
536
537
538 /**
539  * Initiate a connection from p1 to p2 by offering p1 p2's HELLO message
540  *
541  * Remarks: start_peer's notify_connect callback can be called before.
542  *
543  * @param tth transport testing handle
544  * @param p1 peer 1
545  * @param p2 peer 2
546  * @param cb the callback to call when both peers notified that they are connected
547  * @param cls callback cls
548  * @return a connect request handle
549  */
550 struct GNUNET_TRANSPORT_TESTING_ConnectRequest *
551 GNUNET_TRANSPORT_TESTING_connect_peers (struct GNUNET_TRANSPORT_TESTING_PeerContext *p1,
552                                         struct GNUNET_TRANSPORT_TESTING_PeerContext *p2,
553                                         GNUNET_SCHEDULER_TaskCallback cb,
554                                         void *cls)
555 {
556   struct GNUNET_TRANSPORT_TESTING_Handle *tth = p1->tth;
557   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc;
558
559   cc = GNUNET_new (struct GNUNET_TRANSPORT_TESTING_ConnectRequest);
560   cc->p1 = p1;
561   cc->p2 = p2;
562   cc->cb = cb;
563   if (NULL != cls)
564     cc->cb_cls = cls;
565   else
566     cc->cb_cls = cc;
567   GNUNET_CONTAINER_DLL_insert (tth->cc_head,
568                                tth->cc_tail,
569                                cc);
570   cc->tct = GNUNET_SCHEDULER_add_now (&offer_hello,
571                                       cc);
572   cc->ats_sh = GNUNET_ATS_connectivity_suggest (cc->p1->ats,
573                                                 &p2->id,
574                                                 1);
575   LOG (GNUNET_ERROR_TYPE_DEBUG,
576        "New connect request %p\n",
577        cc);
578   return cc;
579 }
580
581
582 /**
583  * Cancel the request to connect two peers
584  * Tou MUST cancel the request if you stop the peers before the peers connected succesfully
585  *
586  * @param tth transport testing handle
587  * @param cc a connect request handle
588  */
589 void
590 GNUNET_TRANSPORT_TESTING_connect_peers_cancel (struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc)
591 {
592   struct GNUNET_TRANSPORT_TESTING_Handle *tth = cc->p1->tth;
593
594   LOG (GNUNET_ERROR_TYPE_DEBUG,
595        "Canceling connect request!\n");
596   if (NULL != cc->tct)
597   {
598     GNUNET_SCHEDULER_cancel (cc->tct);
599     cc->tct = NULL;
600   }
601   if (NULL != cc->oh)
602   {
603     GNUNET_TRANSPORT_offer_hello_cancel (cc->oh);
604     cc->oh = NULL;
605   }
606   if (NULL != cc->ats_sh)
607   {
608     GNUNET_ATS_connectivity_suggest_cancel (cc->ats_sh);
609     cc->ats_sh = NULL;
610   }
611   GNUNET_CONTAINER_DLL_remove (tth->cc_head,
612                                tth->cc_tail,
613                                cc);
614   GNUNET_free (cc);
615 }
616
617
618 /**
619  * Clean up the transport testing
620  *
621  * @param tth transport testing handle
622  */
623 void
624 GNUNET_TRANSPORT_TESTING_done (struct GNUNET_TRANSPORT_TESTING_Handle *tth)
625 {
626   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc;
627   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *ct;
628   struct GNUNET_TRANSPORT_TESTING_PeerContext *p;
629   struct GNUNET_TRANSPORT_TESTING_PeerContext *t;
630
631   if (NULL == tth)
632     return;
633   cc = tth->cc_head;
634   while (NULL != cc)
635   {
636     ct = cc->next;
637     LOG (GNUNET_ERROR_TYPE_ERROR,
638          "Developer forgot to cancel connect request!\n");
639     GNUNET_TRANSPORT_TESTING_connect_peers_cancel (cc);
640     cc = ct;
641   }
642   p = tth->p_head;
643   while (NULL != p)
644   {
645     t = p->next;
646     LOG (GNUNET_ERROR_TYPE_ERROR,
647          "Developer forgot to stop peer!\n");
648     GNUNET_TRANSPORT_TESTING_stop_peer (p);
649     p = t;
650   }
651   GNUNET_TESTING_system_destroy (tth->tl_system,
652                                  GNUNET_YES);
653
654   GNUNET_free (tth);
655 }
656
657
658 /**
659  * Initialize the transport testing
660  *
661  * @return transport testing handle
662  */
663 struct GNUNET_TRANSPORT_TESTING_Handle *
664 GNUNET_TRANSPORT_TESTING_init ()
665 {
666   struct GNUNET_TRANSPORT_TESTING_Handle *tth;
667
668   tth = GNUNET_new (struct GNUNET_TRANSPORT_TESTING_Handle);
669   tth->tl_system = GNUNET_TESTING_system_create ("transport-testing",
670                                                  NULL,
671                                                  NULL,
672                                                  NULL);
673   if (NULL == tth->tl_system)
674   {
675     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
676                 "Failed to initialize testing library!\n");
677     GNUNET_free (tth);
678     return NULL;
679   }
680   return tth;
681 }
682
683 /* end of transport-testing.c */