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