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