make more use of testing lib
[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->start_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 cb_cls closure for callback
204  * @param start_cb start callback
205  * @param start_cb_cls closure for callback
206  * @return the peer context
207  */
208 struct GNUNET_TRANSPORT_TESTING_PeerContext *
209 GNUNET_TRANSPORT_TESTING_start_peer (struct GNUNET_TRANSPORT_TESTING_Handle *tth,
210                                      const char *cfgname,
211                                      int peer_id,
212                                      GNUNET_TRANSPORT_ReceiveCallback rec,
213                                      GNUNET_TRANSPORT_NotifyConnect nc,
214                                      GNUNET_TRANSPORT_NotifyDisconnect nd,
215                                      void *cb_cls,
216                                      GNUNET_TRANSPORT_TESTING_StartCallback start_cb,
217                                      void *start_cb_cls)
218 {
219   char *emsg = NULL;
220   struct GNUNET_TRANSPORT_TESTING_PeerContext *p;
221   struct GNUNET_PeerIdentity *dummy;
222
223   if (GNUNET_NO == GNUNET_DISK_file_test (cfgname))
224   {
225     LOG (GNUNET_ERROR_TYPE_ERROR,
226          "File not found: `%s'\n",
227          cfgname);
228     return NULL;
229   }
230
231   p = GNUNET_new (struct GNUNET_TRANSPORT_TESTING_PeerContext);
232   p->tth = tth;
233   p->nc = nc;
234   p->nd = nd;
235   p->rec = rec;
236   if (NULL != cb_cls)
237     p->cb_cls = cb_cls;
238   else
239     p->cb_cls = p;
240   p->start_cb = start_cb;
241   p->start_cb_cls = start_cb_cls;
242   GNUNET_CONTAINER_DLL_insert (tth->p_head,
243                                tth->p_tail,
244                                p);
245
246   /* Create configuration and call testing lib to modify it */
247   p->cfg = GNUNET_CONFIGURATION_create ();
248   GNUNET_assert (GNUNET_OK ==
249                  GNUNET_CONFIGURATION_load (p->cfg, cfgname));
250   if (GNUNET_SYSERR ==
251       GNUNET_TESTING_configuration_create (tth->tl_system,
252                                            p->cfg))
253   {
254     LOG (GNUNET_ERROR_TYPE_ERROR,
255          "Testing library failed to create unique configuration based on `%s'\n",
256          cfgname);
257     GNUNET_CONFIGURATION_destroy (p->cfg);
258     GNUNET_free (p);
259     return NULL;
260   }
261
262   p->no = peer_id;
263   /* Configure peer with configuration */
264   p->peer = GNUNET_TESTING_peer_configure (tth->tl_system,
265                                            p->cfg,
266                                            p->no,
267                                            NULL,
268                                            &emsg);
269   if (NULL == p->peer)
270   {
271     LOG (GNUNET_ERROR_TYPE_ERROR,
272          "Testing library failed to create unique configuration based on `%s': `%s'\n",
273          cfgname,
274          emsg);
275     GNUNET_TRANSPORT_TESTING_stop_peer (p);
276     GNUNET_free_non_null (emsg);
277     return NULL;
278   }
279   GNUNET_free_non_null (emsg);
280   if (GNUNET_OK != GNUNET_TESTING_peer_start (p->peer))
281   {
282     LOG (GNUNET_ERROR_TYPE_ERROR,
283          "Testing library failed to create unique configuration based on `%s'\n",
284          cfgname);
285     GNUNET_TRANSPORT_TESTING_stop_peer (p);
286     return NULL;
287   }
288
289   memset (&dummy,
290           '\0',
291           sizeof (dummy));
292   GNUNET_TESTING_peer_get_identity (p->peer,
293                                     &p->id);
294   if (0 == memcmp (&dummy,
295                    &p->id,
296                    sizeof (struct GNUNET_PeerIdentity)))
297   {
298     LOG (GNUNET_ERROR_TYPE_ERROR,
299          "Testing library failed to obtain peer identity for peer %u\n",
300          p->no);
301     GNUNET_TRANSPORT_TESTING_stop_peer (p);
302     return NULL;
303   }
304   LOG (GNUNET_ERROR_TYPE_DEBUG,
305        "Peer %u configured with identity `%s'\n",
306        p->no,
307        GNUNET_i2s_full (&p->id));
308
309   p->th = GNUNET_TRANSPORT_connect (p->cfg,
310                                     NULL,
311                                     p,
312                                     &notify_receive,
313                                     &notify_connect,
314                                     &notify_disconnect);
315   if (NULL == p->th)
316   {
317     LOG (GNUNET_ERROR_TYPE_ERROR,
318          "Failed to connect to transport service for peer `%s': `%s'\n",
319          cfgname,
320          emsg);
321     GNUNET_TRANSPORT_TESTING_stop_peer (p);
322     return NULL;
323   }
324   p->ats = GNUNET_ATS_connectivity_init (p->cfg);
325   if (NULL == p->ats)
326   {
327     LOG (GNUNET_ERROR_TYPE_ERROR,
328          "Failed to connect to ATS service for peer `%s': `%s'\n",
329          cfgname,
330          emsg);
331     GNUNET_TRANSPORT_TESTING_stop_peer (p);
332     return NULL;
333   }
334   p->ghh = GNUNET_TRANSPORT_get_hello (p->cfg,
335                                        &get_hello,
336                                        p);
337   GNUNET_assert (p->ghh != NULL);
338   return p;
339 }
340
341
342 /**
343  * Stops and restarts the given peer, sleeping (!) for 5s in between.
344  *
345  * @param p the peer
346  * @param restart_cb callback to call when restarted
347  * @param restart_cb_cls callback closure
348  * @return #GNUNET_OK in success otherwise #GNUNET_SYSERR
349  */
350 int
351 GNUNET_TRANSPORT_TESTING_restart_peer (struct GNUNET_TRANSPORT_TESTING_PeerContext *p,
352                                        GNUNET_TRANSPORT_TESTING_StartCallback restart_cb,
353                                        void *restart_cb_cls)
354 {
355   /* shutdown */
356   LOG (GNUNET_ERROR_TYPE_DEBUG,
357        "Stopping peer %u (`%s')\n",
358        p->no,
359        GNUNET_i2s (&p->id));
360   if (NULL != p->ghh)
361   {
362     GNUNET_TRANSPORT_get_hello_cancel (p->ghh);
363     p->ghh = NULL;
364   }
365   if (NULL != p->th)
366   {
367     GNUNET_TRANSPORT_disconnect (p->th);
368     p->th = NULL;
369   }
370   if (NULL != p->ats)
371   {
372     GNUNET_ATS_connectivity_done (p->ats);
373     p->ats = NULL;
374   }
375   if (GNUNET_SYSERR ==
376       GNUNET_TESTING_peer_stop (p->peer))
377   {
378     LOG (GNUNET_ERROR_TYPE_ERROR,
379          "Failed to stop peer %u (`%s')\n",
380          p->no,
381          GNUNET_i2s (&p->id));
382     return GNUNET_SYSERR;
383   }
384
385   sleep (5); // YUCK!
386
387   LOG (GNUNET_ERROR_TYPE_DEBUG,
388        "Restarting peer %u (`%s')\n",
389        p->no,
390        GNUNET_i2s (&p->id));
391   /* restart */
392   if (GNUNET_SYSERR == GNUNET_TESTING_peer_start (p->peer))
393   {
394     LOG (GNUNET_ERROR_TYPE_ERROR,
395          "Failed to restart peer %u (`%s')\n",
396          p->no,
397          GNUNET_i2s (&p->id));
398     return GNUNET_SYSERR;
399   }
400
401   GNUNET_assert (NULL == p->start_cb);
402   p->start_cb = restart_cb;
403   p->start_cb_cls = restart_cb_cls;
404
405   p->th = GNUNET_TRANSPORT_connect (p->cfg,
406                                     NULL,
407                                     p,
408                                     &notify_receive,
409                                     &notify_connect,
410                                     &notify_disconnect);
411   GNUNET_assert (NULL != p->th);
412   p->ats = GNUNET_ATS_connectivity_init (p->cfg);
413   p->ghh = GNUNET_TRANSPORT_get_hello (p->cfg,
414                                        &get_hello,
415                                        p);
416   GNUNET_assert (NULL != p->ghh);
417   return GNUNET_OK;
418 }
419
420
421 /**
422  * Shutdown the given peer
423  *
424  * @param p the peer
425  */
426 void
427 GNUNET_TRANSPORT_TESTING_stop_peer (struct GNUNET_TRANSPORT_TESTING_PeerContext *p)
428 {
429   struct GNUNET_TRANSPORT_TESTING_Handle *tth = p->tth;
430
431   if (NULL != p->ghh)
432   {
433     GNUNET_TRANSPORT_get_hello_cancel (p->ghh);
434     p->ghh = NULL;
435   }
436   if (NULL != p->th)
437   {
438     GNUNET_TRANSPORT_disconnect (p->th);
439     p->th = NULL;
440   }
441   if (NULL != p->peer)
442   {
443     if (GNUNET_OK !=
444         GNUNET_TESTING_peer_stop (p->peer))
445     {
446       LOG (GNUNET_ERROR_TYPE_DEBUG,
447            "Testing lib failed to stop peer %u (`%s')\n",
448            p->no,
449            GNUNET_i2s (&p->id));
450     }
451     GNUNET_TESTING_peer_destroy (p->peer);
452     p->peer = NULL;
453   }
454   if (NULL != p->ats)
455   {
456     GNUNET_ATS_connectivity_done (p->ats);
457     p->ats = NULL;
458   }
459   if (NULL != p->hello)
460   {
461     GNUNET_free (p->hello);
462     p->hello = NULL;
463   }
464   if (NULL != p->cfg)
465   {
466     GNUNET_CONFIGURATION_destroy (p->cfg);
467     p->cfg = NULL;
468   }
469   GNUNET_CONTAINER_DLL_remove (tth->p_head,
470                                tth->p_tail,
471                                p);
472   LOG (GNUNET_ERROR_TYPE_DEBUG,
473        "Peer %u (`%s') stopped\n",
474        p->no,
475        GNUNET_i2s (&p->id));
476   GNUNET_free (p);
477 }
478
479
480 /**
481  * Offer the current HELLO of P2 to P1.
482  *
483  * @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectRequest`
484  */
485 static void
486 offer_hello (void *cls);
487
488
489 /**
490  * Function called after the HELLO was passed to the
491  * transport service.
492  */
493 static void
494 hello_offered (void *cls)
495 {
496   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc = cls;
497
498   cc->oh = NULL;
499   cc->tct = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
500                                           &offer_hello,
501                                           cc);
502 }
503
504
505 /**
506  * Offer the current HELLO of P2 to P1.
507  *
508  * @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectRequest`
509  */
510 static void
511 offer_hello (void *cls)
512 {
513   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc = cls;
514   struct GNUNET_TRANSPORT_TESTING_PeerContext *p1 = cc->p1;
515   struct GNUNET_TRANSPORT_TESTING_PeerContext *p2 = cc->p2;
516
517   cc->tct = NULL;
518   {
519     char *p2_s = GNUNET_strdup (GNUNET_i2s (&p2->id));
520
521     LOG (GNUNET_ERROR_TYPE_DEBUG,
522          "Asking peer %u (`%s') to connect peer %u (`%s'), providing HELLO with %u bytes\n",
523          p1->no,
524          GNUNET_i2s (&p1->id),
525          p2->no,
526          p2_s,
527          GNUNET_HELLO_size (cc->p2->hello));
528     GNUNET_free (p2_s);
529   }
530
531   if (NULL != cc->oh)
532     GNUNET_TRANSPORT_offer_hello_cancel (cc->oh);
533   cc->oh =
534     GNUNET_TRANSPORT_offer_hello (cc->p1->cfg,
535                                   (const struct GNUNET_MessageHeader *) cc->p2->hello,
536                                   &hello_offered,
537                                   cc);
538 }
539
540
541 /**
542  * Initiate a connection from p1 to p2 by offering p1 p2's HELLO message
543  *
544  * Remarks: start_peer's notify_connect callback can be called before.
545  *
546  * @param tth transport testing handle
547  * @param p1 peer 1
548  * @param p2 peer 2
549  * @param cb the callback to call when both peers notified that they are connected
550  * @param cls callback cls
551  * @return a connect request handle
552  */
553 struct GNUNET_TRANSPORT_TESTING_ConnectRequest *
554 GNUNET_TRANSPORT_TESTING_connect_peers (struct GNUNET_TRANSPORT_TESTING_PeerContext *p1,
555                                         struct GNUNET_TRANSPORT_TESTING_PeerContext *p2,
556                                         GNUNET_SCHEDULER_TaskCallback cb,
557                                         void *cls)
558 {
559   struct GNUNET_TRANSPORT_TESTING_Handle *tth = p1->tth;
560   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc;
561
562   cc = GNUNET_new (struct GNUNET_TRANSPORT_TESTING_ConnectRequest);
563   cc->p1 = p1;
564   cc->p2 = p2;
565   cc->cb = cb;
566   if (NULL != cls)
567     cc->cb_cls = cls;
568   else
569     cc->cb_cls = cc;
570   GNUNET_CONTAINER_DLL_insert (tth->cc_head,
571                                tth->cc_tail,
572                                cc);
573   cc->tct = GNUNET_SCHEDULER_add_now (&offer_hello,
574                                       cc);
575   cc->ats_sh = GNUNET_ATS_connectivity_suggest (cc->p1->ats,
576                                                 &p2->id,
577                                                 1);
578   LOG (GNUNET_ERROR_TYPE_DEBUG,
579        "New connect request %p\n",
580        cc);
581   return cc;
582 }
583
584
585 /**
586  * Cancel the request to connect two peers
587  * Tou MUST cancel the request if you stop the peers before the peers connected succesfully
588  *
589  * @param tth transport testing handle
590  * @param cc a connect request handle
591  */
592 void
593 GNUNET_TRANSPORT_TESTING_connect_peers_cancel (struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc)
594 {
595   struct GNUNET_TRANSPORT_TESTING_Handle *tth = cc->p1->tth;
596
597   LOG (GNUNET_ERROR_TYPE_DEBUG,
598        "Canceling connect request!\n");
599   if (NULL != cc->tct)
600   {
601     GNUNET_SCHEDULER_cancel (cc->tct);
602     cc->tct = NULL;
603   }
604   if (NULL != cc->oh)
605   {
606     GNUNET_TRANSPORT_offer_hello_cancel (cc->oh);
607     cc->oh = NULL;
608   }
609   if (NULL != cc->ats_sh)
610   {
611     GNUNET_ATS_connectivity_suggest_cancel (cc->ats_sh);
612     cc->ats_sh = NULL;
613   }
614   GNUNET_CONTAINER_DLL_remove (tth->cc_head,
615                                tth->cc_tail,
616                                cc);
617   GNUNET_free (cc);
618 }
619
620
621 /**
622  * Clean up the transport testing
623  *
624  * @param tth transport testing handle
625  */
626 void
627 GNUNET_TRANSPORT_TESTING_done (struct GNUNET_TRANSPORT_TESTING_Handle *tth)
628 {
629   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc;
630   struct GNUNET_TRANSPORT_TESTING_ConnectRequest *ct;
631   struct GNUNET_TRANSPORT_TESTING_PeerContext *p;
632   struct GNUNET_TRANSPORT_TESTING_PeerContext *t;
633
634   if (NULL == tth)
635     return;
636   cc = tth->cc_head;
637   while (NULL != cc)
638   {
639     ct = cc->next;
640     LOG (GNUNET_ERROR_TYPE_ERROR,
641          "Developer forgot to cancel connect request!\n");
642     GNUNET_TRANSPORT_TESTING_connect_peers_cancel (cc);
643     cc = ct;
644   }
645   p = tth->p_head;
646   while (NULL != p)
647   {
648     t = p->next;
649     LOG (GNUNET_ERROR_TYPE_ERROR,
650          "Developer forgot to stop peer!\n");
651     GNUNET_TRANSPORT_TESTING_stop_peer (p);
652     p = t;
653   }
654   GNUNET_TESTING_system_destroy (tth->tl_system,
655                                  GNUNET_YES);
656
657   GNUNET_free (tth);
658 }
659
660
661 /**
662  * Initialize the transport testing
663  *
664  * @return transport testing handle
665  */
666 struct GNUNET_TRANSPORT_TESTING_Handle *
667 GNUNET_TRANSPORT_TESTING_init ()
668 {
669   struct GNUNET_TRANSPORT_TESTING_Handle *tth;
670
671   tth = GNUNET_new (struct GNUNET_TRANSPORT_TESTING_Handle);
672   tth->tl_system = GNUNET_TESTING_system_create ("transport-testing",
673                                                  NULL,
674                                                  NULL,
675                                                  NULL);
676   if (NULL == tth->tl_system)
677   {
678     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
679                 "Failed to initialize testing library!\n");
680     GNUNET_free (tth);
681     return NULL;
682   }
683   return tth;
684 }
685
686 /* end of transport-testing.c */