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