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