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