76d88ab848bbc3de45e30e6550c5dd554ccaaab5
[oweals/gnunet.git] / src / transport / transport-testing.c
1 /*
2      This file is part of GNUnet.
3      (C) 2006, 2009 Christian Grothoff (and other contributing authors)
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 2, 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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file transport-testing.c
23  * @brief testing lib for transport service
24  *
25  * @author Matthias Wachs
26  */
27
28 #include "transport-testing.h"
29
30 #define VERBOSE GNUNET_YES
31
32 struct PeerContext *
33 find_peer_context_by_pc ( struct GNUNET_TRANSPORT_TESTING_handle *tth,
34                           struct PeerContext *p)
35 {
36   GNUNET_assert (tth != NULL);
37   struct PeerContext * t = tth->p_head;
38
39   while (t != NULL)
40   {
41     if (p == t)
42       break;
43     t = t->next;
44   }
45
46   return t;
47 }
48
49
50 struct PeerContext *
51 find_peer_context ( struct GNUNET_TRANSPORT_TESTING_handle *tth,
52                     const struct GNUNET_PeerIdentity *peer)
53 {
54   GNUNET_assert (tth != NULL);
55   struct PeerContext * t = tth->p_head;
56
57   while (t != NULL)
58   {
59     if (0 == memcmp (&t->id, peer, sizeof (struct GNUNET_PeerIdentity)))
60       break;
61     t = t->next;
62   }
63
64   return t;
65 }
66
67 struct ConnectingContext *
68 find_connecting_context ( struct GNUNET_TRANSPORT_TESTING_handle *tth,
69                           struct PeerContext *p1,
70                           struct PeerContext * p2)
71 {
72   GNUNET_assert (tth != NULL);
73   struct ConnectingContext * cc = tth->cc_head;
74
75   while (cc != NULL)
76   {
77     if ((cc->p1 == p1) && (cc->p2 == p2))
78         break;
79     if ((cc->p1 == p2) && (cc->p2 == p1))
80       break;
81     cc = cc->next;
82   }
83
84   return cc;
85 }
86
87 static void
88 notify_connect (void *cls, const struct GNUNET_PeerIdentity *peer,
89                 const struct GNUNET_TRANSPORT_ATS_Information *ats,
90                 uint32_t ats_count)
91 {
92   struct PeerContext *p = cls;
93   /* Find PeerContext */
94   GNUNET_assert (p->tth != NULL);
95   struct PeerContext * p2 = find_peer_context (p->tth, peer);
96
97   if (p == NULL)
98     return;
99   if (p->nc != NULL)
100     p->nc (p->cb_cls, peer, ats, ats_count);
101
102 #if VERBOSE
103   char * p2_s;
104   if (p2 != NULL)
105     GNUNET_asprintf(&p2_s, "%u (`%s')", p2->no, GNUNET_i2s (&p2->id));
106   else
107     GNUNET_asprintf(&p2_s, "`%s'", GNUNET_i2s (peer));
108   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-testing",
109       "Peers %s connected to peer %u (`%s')\n",
110       p2_s,
111       p->no, GNUNET_i2s (&p->id));
112   GNUNET_free (p2_s);
113 #endif
114
115
116   /* Find ConnectingContext */
117   struct ConnectingContext * cc = find_connecting_context(p->tth, p, p2);
118   if (cc == NULL)
119     return;
120
121   if (p == cc->p1)
122     cc->p1_c = GNUNET_YES;
123
124   if (p == cc->p2)
125       cc->p2_c = GNUNET_YES;
126
127   if ((cc->p1_c == GNUNET_YES) && (cc->p2_c == GNUNET_YES))
128   {
129     cc->cb (cc->p1, cc->p2, cc->cb_cls);
130     GNUNET_TRANSPORT_TESTING_connect_peers_cancel(p->tth, cc);
131   }
132 }
133
134 static void
135 notify_disconnect (void *cls, const struct GNUNET_PeerIdentity *peer)
136 {
137   struct PeerContext *p = cls;
138   /* Find PeerContext */
139   GNUNET_assert (p->tth != NULL);
140   struct PeerContext * p2 = find_peer_context (p->tth, peer);
141
142   char * p2_s;
143   if (p2 != NULL)
144     GNUNET_asprintf(&p2_s, "%u (`%s')", p2->no, GNUNET_i2s (&p2->id));
145   else
146     GNUNET_asprintf(&p2_s, "`%s'", GNUNET_i2s (peer));
147   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-testing",
148       "Peers %s disconnected from peer %u (`%s')\n",
149       p2_s,
150       p->no, GNUNET_i2s (&p->id));
151   GNUNET_free (p2_s);
152
153   if (p == NULL)
154     return;
155   if (p->nd != NULL)
156     p->nd (p->cb_cls, peer);
157 }
158
159 static void
160 notify_receive (void *cls, const struct GNUNET_PeerIdentity *peer,
161                 const struct GNUNET_MessageHeader *message,
162                 const struct GNUNET_TRANSPORT_ATS_Information *ats,
163                 uint32_t ats_count)
164 {
165   struct PeerContext *p = cls;
166
167   if (p == NULL)
168     return;
169   if (p->rec != NULL)
170     p->rec (p->cb_cls, peer, message, ats, ats_count);
171 }
172
173 static void
174 get_hello (void *cb_cls, const struct GNUNET_MessageHeader *message)
175 {
176   struct PeerContext *p = cb_cls;
177
178   GNUNET_assert (message != NULL);
179   GNUNET_assert (GNUNET_OK ==
180                  GNUNET_HELLO_get_id ((const struct GNUNET_HELLO_Message *)
181                                       message, &p->id));
182
183   if (p->hello != NULL)
184     GNUNET_free (p->hello);
185
186   size_t size = GNUNET_HELLO_size((const struct GNUNET_HELLO_Message *) message);
187   p->hello = GNUNET_malloc (size);
188   memcpy (p->hello,
189         (const struct GNUNET_HELLO_Message *) message,
190         size);
191
192 #if VERBOSE
193     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-testing",
194         "New HELLO for peer %u (`%s') with size %u\n",
195         p->no, GNUNET_i2s (&p->id), size);
196 #endif
197
198   if (p->start_cb != NULL)
199   {
200 #if VERBOSE
201     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-testing",
202         "Peer %u (`%s') successfully started\n",
203         p->no, GNUNET_i2s (&p->id));
204 #endif
205     p->start_cb(p, p->cb_cls);
206     p->start_cb = NULL;
207   }
208 }
209
210
211 static void
212 try_connect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
213 {
214   struct ConnectingContext *cc = cls;
215   struct PeerContext *p1 = cc->p1;
216   struct PeerContext *p2 = cc->p2;
217
218   cc->tct = GNUNET_SCHEDULER_NO_TASK;
219   if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0)
220     return;
221
222   char * p2_s = strdup(GNUNET_i2s (&p2->id));
223   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-testing",
224       "Asking peers %u (`%s') to connect peer %u (`%s')\n",
225       p1->no, GNUNET_i2s (&p1->id), p2->no, p2_s);
226   GNUNET_free (p2_s);
227
228   GNUNET_TRANSPORT_offer_hello (cc->th_p1,
229       (const struct GNUNET_MessageHeader *) cc->p2->hello, NULL, NULL);
230   GNUNET_TRANSPORT_try_connect (cc->th_p1, &p2->id);
231
232   cc->tct =
233       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &try_connect, cc);
234 }
235
236
237 /**
238  * Start a peer with the given configuration
239  * @param rec receive callback
240  * @param nc connect callback
241  * @param nd disconnect callback
242  * @param cb_cls closure for callback
243  * @return the peer context
244  */
245 struct PeerContext *
246 GNUNET_TRANSPORT_TESTING_start_peer (struct GNUNET_TRANSPORT_TESTING_handle * tth,
247                                      const char *cfgname,
248                                      int peer_id,
249                                      GNUNET_TRANSPORT_ReceiveCallback rec,
250                                      GNUNET_TRANSPORT_NotifyConnect nc,
251                                      GNUNET_TRANSPORT_NotifyDisconnect nd,
252                                      GNUNET_TRANSPORT_TESTING_start_cb start_cb,
253                                      void *cb_cls)
254 {
255   GNUNET_assert (tth != NULL);
256   if (GNUNET_DISK_file_test (cfgname) == GNUNET_NO)
257   {
258     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "transport-testing",
259         "File not found: `%s' \n", cfgname);
260     return NULL;
261   }
262
263   struct PeerContext *p = GNUNET_malloc (sizeof (struct PeerContext));
264
265   p->cfg = GNUNET_CONFIGURATION_create ();
266
267   GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (p->cfg, cfgname));
268   if (GNUNET_CONFIGURATION_have_value (p->cfg, "PATHS", "SERVICEHOME"))
269     GNUNET_CONFIGURATION_get_value_string (p->cfg, "PATHS", "SERVICEHOME",
270                                            &p->servicehome);
271   if (NULL != p->servicehome)
272     GNUNET_DISK_directory_remove (p->servicehome);
273   p->arm_proc =
274       GNUNET_OS_start_process (NULL, NULL, "gnunet-service-arm",
275                                "gnunet-service-arm", "-c", cfgname,
276 #if VERBOSE_PEERS
277                                "-L", "DEBUG",
278 #else
279                                "-L", "ERROR",
280 #endif
281                                NULL);
282
283   p->no = peer_id;
284   p->tth = tth;
285   p->nc = nc;
286   p->nd = nd;
287   p->rec = rec;
288   p->start_cb = start_cb;
289   if (cb_cls != NULL)
290     p->cb_cls = cb_cls;
291   else
292     p->cb_cls = p;
293
294   p->th =
295       GNUNET_TRANSPORT_connect (p->cfg, NULL, p, &notify_receive,
296                                 &notify_connect, &notify_disconnect);
297   GNUNET_assert (p->th != NULL);
298
299   p->ghh = GNUNET_TRANSPORT_get_hello (p->th, &get_hello, p);
300   GNUNET_assert (p->ghh != NULL);
301
302   GNUNET_CONTAINER_DLL_insert(tth->p_head, tth->p_tail, p);
303
304   return p;
305 }
306
307 /**
308  * shutdown the given peer
309  * @param p the peer
310  */
311 void
312 GNUNET_TRANSPORT_TESTING_stop_peer (struct GNUNET_TRANSPORT_TESTING_handle * tth,
313                                     struct PeerContext *p)
314 {
315   GNUNET_assert (p != NULL);
316
317   if (p->ghh != NULL)
318     GNUNET_TRANSPORT_get_hello_cancel (p->ghh);
319   p->ghh = NULL;
320
321   if (p->th != NULL)
322     GNUNET_TRANSPORT_disconnect (p->th);
323
324   if (NULL != p->arm_proc)
325   {
326     if (0 != GNUNET_OS_process_kill (p->arm_proc, SIGTERM))
327       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
328     GNUNET_OS_process_wait (p->arm_proc);
329     GNUNET_OS_process_close (p->arm_proc);
330     p->arm_proc = NULL;
331   }
332
333   if (p->servicehome != NULL)
334   {
335     GNUNET_DISK_directory_remove (p->servicehome);
336     GNUNET_free (p->servicehome);
337   }
338
339   if (p->hello != NULL)
340     GNUNET_free (p->hello);
341
342   if (p->cfg != NULL)
343     GNUNET_CONFIGURATION_destroy (p->cfg);
344
345   GNUNET_CONTAINER_DLL_remove (tth->p_head, tth->p_tail, p);
346
347   GNUNET_free (p);
348   p = NULL;
349 }
350
351 /**
352  * Initiate peer p1 to connect to peer p2
353  * Get peer p2's HELLO and offer it to p1
354  * p1 then tries to connect to p2
355  * @param p1 peer 1
356  * @param p2 peer 2
357  * @param cb the callback to call when both peers notified that they are connected
358  * @param cb_cls callback cls
359  * @return connect context
360  */
361 GNUNET_TRANSPORT_TESTING_ConnectRequest
362 GNUNET_TRANSPORT_TESTING_connect_peers (struct GNUNET_TRANSPORT_TESTING_handle * tth,
363                                         struct PeerContext *p1,
364                                         struct PeerContext *p2,
365                                         GNUNET_TRANSPORT_TESTING_connect_cb cb,
366                                         void *cb_cls)
367
368 {
369   GNUNET_assert (tth != NULL);
370
371   struct ConnectingContext *cc =
372       GNUNET_malloc (sizeof (struct ConnectingContext));
373
374   GNUNET_assert (p1 != NULL);
375   GNUNET_assert (p2 != NULL);
376
377   cc->p1 = p1;
378   cc->p2 = p2;
379
380   cc->cb = cb;
381   cc->cb_cls = cb_cls;
382
383   cc->th_p1 = p1->th;
384   cc->th_p2 = p2->th;
385
386   GNUNET_assert (cc->th_p1 != NULL);
387   GNUNET_assert (cc->th_p2 != NULL);
388
389   GNUNET_CONTAINER_DLL_insert (tth->cc_head, tth->cc_tail, cc);
390
391   cc->tct = GNUNET_SCHEDULER_add_now (&try_connect, cc);
392   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-testing",
393       "New connect request %X\n", cc);
394
395   return cc;
396 }
397
398 /**
399  * Cancel the request to connect two peers
400  * Tou MUST cancel the request if you stop the peers before the peers connected succesfully
401  * @param cc a connect request handle
402  */
403 void GNUNET_TRANSPORT_TESTING_connect_peers_cancel
404     (struct GNUNET_TRANSPORT_TESTING_handle * tth,
405         GNUNET_TRANSPORT_TESTING_ConnectRequest ccr)
406 {
407   struct ConnectingContext *cc = ccr;
408
409   GNUNET_assert (tth != NULL);
410
411   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-testing",
412       "Canceling connect request %X!\n", cc);
413   if (cc->tct != GNUNET_SCHEDULER_NO_TASK)
414     GNUNET_SCHEDULER_cancel (cc->tct);
415
416   cc->tct = GNUNET_SCHEDULER_NO_TASK;
417
418   GNUNET_CONTAINER_DLL_remove (tth->cc_head, tth->cc_tail, cc);
419   GNUNET_free (cc);
420 }
421
422
423 /**
424  * Clean up the transport testing
425  * @param tth transport testing handle
426  */
427 void
428 GNUNET_TRANSPORT_TESTING_done (struct GNUNET_TRANSPORT_TESTING_handle * tth)
429 {
430   struct ConnectingContext *cc = tth->cc_head;
431   struct ConnectingContext *ct = NULL;
432   struct PeerContext *p = tth->p_head;
433   struct PeerContext *t = NULL;
434
435   GNUNET_assert (tth != NULL);
436
437   while (cc != tth->cc_tail)
438   {
439     ct = cc->next;
440     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "transport-testing",
441         "Developer forgot to cancel connect request %X!\n", cc);
442     GNUNET_TRANSPORT_TESTING_connect_peers_cancel(tth, cc);
443     cc = ct;
444   }
445
446   while (p != NULL)
447   {
448     t = p->next;
449     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "transport-testing",
450         "Developer forgot to stop peer!\n");
451     GNUNET_TRANSPORT_TESTING_stop_peer (tth, p);
452     p = t;
453   }
454
455   GNUNET_free (tth);
456   tth = NULL;
457 }
458
459 /**
460  * Initialize the transport testing
461  * @return transport testing handle
462  */
463 struct GNUNET_TRANSPORT_TESTING_handle *
464 GNUNET_TRANSPORT_TESTING_init ()
465 {
466   struct GNUNET_TRANSPORT_TESTING_handle * tth = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_TESTING_handle));
467
468   return tth;
469 }
470
471
472 /*
473  * Some utility functions
474  */
475
476 /**
477  * Removes all directory separators from absolute filename
478  * @param file the absolute file name, e.g. as found in argv[0]
479  * @return extracted file name, has to be freed by caller
480  */
481 char *
482 extract_filename (const char *file)
483 {
484   char *pch = strdup (file);
485   char *backup = pch;
486   char *filename = NULL;
487   char *res;
488
489   if (NULL != strstr (pch, "/"))
490   {
491     pch = strtok (pch, "/");
492     while (pch != NULL)
493     {
494       pch = strtok (NULL, "/");
495       if (pch != NULL)
496       {
497         filename = pch;
498       }
499     }
500   }
501   else
502     filename = pch;
503
504   res = strdup (filename);
505   GNUNET_free (backup);
506   return res;
507 }
508
509 /**
510  * Extracts the test filename from an absolute file name and removes the extension
511  * @param file absolute file name
512  * @param dest where to store result
513  */
514 void
515 GNUNET_TRANSPORT_TESTING_get_test_name (const char *file, char **dest)
516 {
517   char *filename = extract_filename (file);
518   char *backup = filename;
519   char *dotexe;
520
521   if (filename == NULL)
522     goto fail;
523
524   /* remove "lt-" */
525   filename = strstr (filename, "tes");
526   if (filename == NULL)
527     goto fail;
528
529   /* remove ".exe" */
530   if (NULL != (dotexe = strstr (filename, ".exe")))
531     dotexe[0] = '\0';
532
533   goto suc;
534
535 fail:
536   (*dest) = NULL;
537   return;
538
539 suc:
540   /* create filename */
541   GNUNET_asprintf (dest, "%s", filename);
542   GNUNET_free (backup);
543 }
544
545
546 /**
547  * Extracts the filename from an absolute file name and removes the extension
548  * @param file absolute file name
549  * @param dest where to store result
550  */
551 void
552 GNUNET_TRANSPORT_TESTING_get_test_source_name (const char *file, char **dest)
553 {
554   char *src = extract_filename (file);
555   char *split;
556
557   split = strstr (src, ".");
558   if (split != NULL)
559   {
560     split[0] = '\0';
561   }
562   GNUNET_asprintf (dest, "%s", src);
563   GNUNET_free (src);
564 }
565
566
567 /**
568  * Extracts the plugin anme from an absolute file name and the test name
569  * @param file absolute file name
570  * @param test test name
571  * @param dest where to store result
572  */
573 void
574 GNUNET_TRANSPORT_TESTING_get_test_plugin_name (const char *file,
575                                                const char *test, char **dest)
576 {
577   char *e = extract_filename (file);
578   char *t = extract_filename (test);
579
580   char *filename = NULL;
581   char *dotexe;
582
583   if (e == NULL)
584     goto fail;
585
586   /* remove "lt-" */
587   filename = strstr (e, "tes");
588   if (filename == NULL)
589     goto fail;
590
591   /* remove ".exe" */
592   if (NULL != (dotexe = strstr (filename, ".exe")))
593     dotexe[0] = '\0';
594
595   /* find last _ */
596   filename = strstr (filename, t);
597   if (filename == NULL)
598     goto fail;
599
600   /* copy plugin */
601   filename += strlen (t);
602   filename++;
603   GNUNET_asprintf (dest, "%s", filename);
604   goto suc;
605
606 fail:
607   (*dest) = NULL;
608 suc:
609   GNUNET_free (t);
610   GNUNET_free (e);
611
612 }
613
614 /**
615  * This function takes the filename (e.g. argv[0), removes a "lt-"-prefix and
616  * if existing ".exe"-prefix and adds the peer-number
617  * @param file filename of the test, e.g. argv[0]
618  * @param cfgname where to write the result
619  * @param count peer number
620  */
621 void
622 GNUNET_TRANSPORT_TESTING_get_config_name (const char *file, char **dest,
623                                           int count)
624 {
625   char *filename = extract_filename (file);
626   char *backup = filename;
627   char *dotexe;
628
629   if (filename == NULL)
630     goto fail;
631
632   /* remove "lt-" */
633   filename = strstr (filename, "tes");
634   if (filename == NULL)
635     goto fail;
636
637   /* remove ".exe" */
638   if (NULL != (dotexe = strstr (filename, ".exe")))
639     dotexe[0] = '\0';
640
641   goto suc;
642
643 fail:
644   (*dest) = NULL;
645   return;
646
647 suc:
648   /* create cfg filename */
649   GNUNET_asprintf (dest, "%s_peer%u.conf", filename, count);
650   GNUNET_free (backup);
651 }
652
653
654
655 /* end of transport_testing.h */