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