doxygen
[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 struct ConnectingContext
31 {
32   struct PeerContext *p1;
33   struct PeerContext *p2;
34   GNUNET_SCHEDULER_TaskIdentifier tct;
35   GNUNET_TRANSPORT_TESTING_connect_cb cb;
36   void *cb_cls;
37   struct GNUNET_TRANSPORT_Handle *th_p1;
38   struct GNUNET_TRANSPORT_Handle *th_p2;
39   int p1_c;
40   int p2_c;
41 };
42
43 static void
44 exchange_hello_last (void *cb_cls, const struct GNUNET_MessageHeader *message);
45 static void
46 exchange_hello (void *cb_cls, const struct GNUNET_MessageHeader *message);
47
48 static void
49 notify_connect_internal (void *cls, const struct GNUNET_PeerIdentity *peer,
50                          const struct GNUNET_TRANSPORT_ATS_Information *ats,
51                          uint32_t ats_count)
52 {
53   struct ConnectingContext *cc = cls;
54
55   GNUNET_assert (cc != NULL);
56
57   if (0 ==
58       memcmp (&(*peer).hashPubKey, &cc->p1->id.hashPubKey,
59               sizeof (GNUNET_HashCode)))
60   {
61     if (cc->p1_c == GNUNET_NO)
62       cc->p1_c = GNUNET_YES;
63   }
64   if (0 ==
65       memcmp (&(*peer).hashPubKey, &cc->p2->id.hashPubKey,
66               sizeof (GNUNET_HashCode)))
67   {
68     if (cc->p2_c == GNUNET_NO)
69       cc->p2_c = GNUNET_YES;
70   }
71
72   if ((cc->p2_c == GNUNET_YES) && (cc->p2_c == GNUNET_YES))
73   {
74     /* clean up */
75     GNUNET_TRANSPORT_get_hello_cancel (cc->p1->ghh);
76     GNUNET_TRANSPORT_get_hello_cancel (cc->p2->ghh);
77
78     if (cc->tct != GNUNET_SCHEDULER_NO_TASK)
79       GNUNET_SCHEDULER_cancel (cc->tct);
80
81     cc->tct = GNUNET_SCHEDULER_NO_TASK;
82
83     GNUNET_TRANSPORT_disconnect (cc->th_p1);
84     GNUNET_TRANSPORT_disconnect (cc->th_p2);
85
86     if (cc->cb != NULL)
87       cc->cb (cc->p1, cc->p2, cc->cb_cls);
88
89     GNUNET_free (cc);
90   }
91 }
92
93 static void
94 notify_connect (void *cls, const struct GNUNET_PeerIdentity *peer,
95                 const struct GNUNET_TRANSPORT_ATS_Information *ats,
96                 uint32_t ats_count)
97 {
98   struct PeerContext *p = cls;
99
100   if (p == NULL)
101     return;
102   if (p->nc != NULL)
103     p->nc (p->cb_cls, peer, ats, ats_count);
104 }
105
106 static void
107 notify_disconnect (void *cls, const struct GNUNET_PeerIdentity *peer)
108 {
109   struct PeerContext *p = cls;
110
111   if (p == NULL)
112     return;
113   if (p->nd != NULL)
114     p->nd (p->cb_cls, peer);
115 }
116
117 static void
118 notify_receive (void *cls, const struct GNUNET_PeerIdentity *peer,
119                 const struct GNUNET_MessageHeader *message,
120                 const struct GNUNET_TRANSPORT_ATS_Information *ats,
121                 uint32_t ats_count)
122 {
123   struct PeerContext *p = cls;
124
125   if (p == NULL)
126     return;
127   if (p->rec != NULL)
128     p->rec (p->cb_cls, peer, message, ats, ats_count);
129 }
130
131
132 static void
133 exchange_hello_last (void *cb_cls, const struct GNUNET_MessageHeader *message)
134 {
135   struct ConnectingContext *cc = cb_cls;
136   struct PeerContext *me = cc->p2;
137
138   //struct PeerContext *p1 = cc->p1;
139
140   GNUNET_assert (message != NULL);
141   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
142               "Exchanging HELLO of size %d with peer (%s)!\n",
143               (int) GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message *)
144                                        message), GNUNET_i2s (&me->id));
145   GNUNET_assert (GNUNET_OK ==
146                  GNUNET_HELLO_get_id ((const struct GNUNET_HELLO_Message *)
147                                       message, &me->id));
148   GNUNET_TRANSPORT_offer_hello (cc->th_p1, message, NULL, NULL);
149 }
150
151
152 static void
153 exchange_hello (void *cb_cls, const struct GNUNET_MessageHeader *message)
154 {
155   struct ConnectingContext *cc = cb_cls;
156   struct PeerContext *me = cc->p1;
157
158   //struct PeerContext *p2 = cc->p2;
159
160   GNUNET_assert (message != NULL);
161   GNUNET_assert (GNUNET_OK ==
162                  GNUNET_HELLO_get_id ((const struct GNUNET_HELLO_Message *)
163                                       message, &me->id));
164   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
165               "Exchanging HELLO of size %d from peer %s!\n",
166               (int) GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message *)
167                                        message), GNUNET_i2s (&me->id));
168   GNUNET_TRANSPORT_offer_hello (cc->th_p2, message, NULL, NULL);
169 }
170
171 static void
172 try_connect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
173 {
174   struct ConnectingContext *cc = cls;
175   struct PeerContext *p1 = cc->p1;
176   struct PeerContext *p2 = cc->p2;
177
178   cc->tct = GNUNET_SCHEDULER_NO_TASK;
179   if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0)
180     return;
181
182   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Asking peers to connect...\n");
183   /* FIXME: 'pX.id' may still be all-zeros here... */
184   GNUNET_TRANSPORT_try_connect (cc->th_p1, &p2->id);
185   GNUNET_TRANSPORT_try_connect (cc->th_p2, &p1->id);
186
187   cc->tct =
188       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &try_connect, cc);
189 }
190
191
192 /**
193  * Start a peer with the given configuration
194  * @param rec receive callback
195  * @param nc connect callback
196  * @param nd disconnect callback
197  * @param cb_cls closure for callback
198  * @return the peer context
199  */
200 struct PeerContext *
201 GNUNET_TRANSPORT_TESTING_start_peer (const char *cfgname,
202                                      GNUNET_TRANSPORT_ReceiveCallback rec,
203                                      GNUNET_TRANSPORT_NotifyConnect nc,
204                                      GNUNET_TRANSPORT_NotifyDisconnect nd,
205                                      void *cb_cls)
206 {
207   if (GNUNET_DISK_file_test (cfgname) == GNUNET_NO)
208   {
209     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "File not found: `%s' \n", cfgname);
210     return NULL;
211   }
212
213   struct PeerContext *p = GNUNET_malloc (sizeof (struct PeerContext));
214
215   p->cfg = GNUNET_CONFIGURATION_create ();
216
217   GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (p->cfg, cfgname));
218   if (GNUNET_CONFIGURATION_have_value (p->cfg, "PATHS", "SERVICEHOME"))
219     GNUNET_CONFIGURATION_get_value_string (p->cfg, "PATHS", "SERVICEHOME",
220                                            &p->servicehome);
221   if (NULL != p->servicehome)
222     GNUNET_DISK_directory_remove (p->servicehome);
223   p->arm_proc =
224       GNUNET_OS_start_process (NULL, NULL, "gnunet-service-arm",
225                                "gnunet-service-arm", "-c", cfgname,
226 #if VERBOSE_PEERS
227                                "-L", "DEBUG",
228 #else
229                                "-L", "ERROR",
230 #endif
231                                NULL);
232   p->nc = nc;
233   p->nd = nd;
234   p->rec = rec;
235   if (cb_cls != NULL)
236     p->cb_cls = cb_cls;
237   else
238     p->cb_cls = p;
239
240   p->th =
241       GNUNET_TRANSPORT_connect (p->cfg, NULL, p, &notify_receive,
242                                 &notify_connect, &notify_disconnect);
243   GNUNET_assert (p->th != NULL);
244   return p;
245 }
246
247 /**
248  * shutdown the given peer
249  * @param p the peer
250  */
251 void
252 GNUNET_TRANSPORT_TESTING_stop_peer (struct PeerContext *p)
253 {
254   GNUNET_assert (p != NULL);
255   if (p->th != NULL)
256     GNUNET_TRANSPORT_disconnect (p->th);
257
258   if (NULL != p->arm_proc)
259   {
260     if (0 != GNUNET_OS_process_kill (p->arm_proc, SIGTERM))
261       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
262     GNUNET_OS_process_wait (p->arm_proc);
263     GNUNET_OS_process_close (p->arm_proc);
264     p->arm_proc = NULL;
265   }
266   if (p->servicehome != NULL)
267   {
268     GNUNET_DISK_directory_remove (p->servicehome);
269     GNUNET_free (p->servicehome);
270   }
271
272   if (p->cfg != NULL)
273     GNUNET_CONFIGURATION_destroy (p->cfg);
274   GNUNET_free (p);
275   p = NULL;
276 }
277
278 /**
279  * Connect the two given peers and call the callback when both peers report the
280  * inbound connect. Remarks: start_peer's notify_connect callback can be called
281  * before.
282  * @param p1 peer 1
283  * @param p2 peer 2
284  * @param cb the callback to call
285  * @param cb_cls callback cls
286  * @return connect context
287  */
288 GNUNET_TRANSPORT_TESTING_ConnectRequest
289 GNUNET_TRANSPORT_TESTING_connect_peers (struct PeerContext *p1,
290                                         struct PeerContext *p2,
291                                         GNUNET_TRANSPORT_TESTING_connect_cb cb,
292                                         void *cb_cls)
293 {
294   struct ConnectingContext *cc =
295       GNUNET_malloc (sizeof (struct ConnectingContext));
296
297   GNUNET_assert (p1 != NULL);
298   GNUNET_assert (p2 != NULL);
299
300   cc->p1 = p1;
301   cc->p2 = p2;
302
303   cc->cb = cb;
304   cc->cb_cls = cb_cls;
305
306   cc->th_p1 =
307       GNUNET_TRANSPORT_connect (cc->p1->cfg, NULL, cc, NULL,
308                                 &notify_connect_internal, NULL);
309
310   cc->th_p2 =
311       GNUNET_TRANSPORT_connect (cc->p2->cfg, NULL, cc, NULL,
312                                 &notify_connect_internal, NULL);
313
314   GNUNET_assert (cc->th_p1 != NULL);
315   GNUNET_assert (cc->th_p2 != NULL);
316
317   p1->ghh = GNUNET_TRANSPORT_get_hello (cc->th_p1, &exchange_hello, cc);
318   p2->ghh = GNUNET_TRANSPORT_get_hello (cc->th_p2, &exchange_hello_last, cc);
319
320   cc->tct = GNUNET_SCHEDULER_add_now (&try_connect, cc);
321   return cc;
322 }
323
324 /**
325  * Cancel the request to connect two peers
326  * Tou MUST cancel the request if you stop the peers before the peers connected succesfully
327  * @param cc a connect request handle
328  */
329 void GNUNET_TRANSPORT_TESTING_connect_peers_cancel
330     (GNUNET_TRANSPORT_TESTING_ConnectRequest ccr)
331 {
332   struct ConnectingContext *cc = ccr;
333
334   /* clean up */
335   GNUNET_TRANSPORT_get_hello_cancel (cc->p1->ghh);
336   GNUNET_TRANSPORT_get_hello_cancel (cc->p2->ghh);
337
338   if (cc->tct != GNUNET_SCHEDULER_NO_TASK)
339     GNUNET_SCHEDULER_cancel (cc->tct);
340
341   cc->tct = GNUNET_SCHEDULER_NO_TASK;
342
343   GNUNET_TRANSPORT_disconnect (cc->th_p1);
344   GNUNET_TRANSPORT_disconnect (cc->th_p2);
345
346   GNUNET_free (cc);
347 }
348
349
350 /*
351  * Some utility functions
352  */
353
354 /**
355  * Removes all directory separators from absolute filename
356  * @param file the absolute file name, e.g. as found in argv[0]
357  * @return extracted file name, has to be freed by caller
358  */
359 char *
360 extract_filename (const char *file)
361 {
362   char *pch = strdup (file);
363   char *backup = pch;
364   char *filename = NULL;
365   char *res;
366
367   if (NULL != strstr (pch, "/"))
368   {
369     pch = strtok (pch, "/");
370     while (pch != NULL)
371     {
372       pch = strtok (NULL, "/");
373       if (pch != NULL)
374       {
375         filename = pch;
376       }
377     }
378   }
379   else
380     filename = pch;
381
382   res = strdup (filename);
383   GNUNET_free (backup);
384   return res;
385 }
386
387 /**
388  * Extracts the test filename from an absolute file name and removes the extension
389  * @param file absolute file name
390  * @param dest where to store result
391  */
392 void
393 GNUNET_TRANSPORT_TESTING_get_test_name (const char *file, char **dest)
394 {
395   char *filename = extract_filename (file);
396   char *backup = filename;
397   char *dotexe;
398
399   if (filename == NULL)
400     goto fail;
401
402   /* remove "lt-" */
403   filename = strstr (filename, "tes");
404   if (filename == NULL)
405     goto fail;
406
407   /* remove ".exe" */
408   if (NULL != (dotexe = strstr (filename, ".exe")))
409     dotexe[0] = '\0';
410
411   if (filename == NULL)
412     goto fail;
413   goto suc;
414
415 fail:
416   (*dest) = NULL;
417   return;
418
419 suc:
420   /* create filename */
421   GNUNET_asprintf (dest, "%s", filename);
422   GNUNET_free (backup);
423 }
424
425
426 /**
427  * Extracts the filename from an absolute file name and removes the extension
428  * @param file absolute file name
429  * @param dest where to store result
430  */
431 void
432 GNUNET_TRANSPORT_TESTING_get_test_source_name (const char *file, char **dest)
433 {
434   char *src = extract_filename (file);
435   char *split;
436
437   split = strstr (src, ".");
438   if (split != NULL)
439   {
440     split[0] = '\0';
441   }
442   GNUNET_asprintf (dest, "%s", src);
443   GNUNET_free (src);
444 }
445
446
447 /**
448  * Extracts the plugin anme from an absolute file name and the test name
449  * @param file absolute file name
450  * @param test test name
451  * @param dest where to store result
452  */
453 void
454 GNUNET_TRANSPORT_TESTING_get_test_plugin_name (const char *file,
455                                                const char *test, char **dest)
456 {
457   char *e = extract_filename (file);
458   char *t = extract_filename (test);
459
460   char *filename = NULL;
461   char *dotexe;
462
463   if (e == NULL)
464     goto fail;
465
466   /* remove "lt-" */
467   filename = strstr (e, "tes");
468   if (filename == NULL)
469     goto fail;
470
471   /* remove ".exe" */
472   if (NULL != (dotexe = strstr (filename, ".exe")))
473     dotexe[0] = '\0';
474
475   /* find last _ */
476   filename = strstr (filename, t);
477   if (filename == NULL)
478     goto fail;
479
480   /* copy plugin */
481   filename += strlen (t);
482   filename++;
483   GNUNET_asprintf (dest, "%s", filename);
484   goto suc;
485
486 fail:
487   (*dest) = NULL;
488 suc:
489   GNUNET_free (t);
490   GNUNET_free (e);
491
492 }
493
494 /**
495  * This function takes the filename (e.g. argv[0), removes a "lt-"-prefix and
496  * if existing ".exe"-prefix and adds the peer-number
497  * @param file filename of the test, e.g. argv[0]
498  * @param cfgname where to write the result
499  * @param count peer number
500  */
501 void
502 GNUNET_TRANSPORT_TESTING_get_config_name (const char *file, char **dest,
503                                           int count)
504 {
505   char *filename = extract_filename (file);
506   char *backup = filename;
507   char *dotexe;
508
509   if (filename == NULL)
510     goto fail;
511
512   /* remove "lt-" */
513   filename = strstr (filename, "tes");
514   if (filename == NULL)
515     goto fail;
516
517   /* remove ".exe" */
518   if (NULL != (dotexe = strstr (filename, ".exe")))
519     dotexe[0] = '\0';
520
521   if (filename == NULL)
522     goto fail;
523   goto suc;
524
525 fail:
526   (*dest) = NULL;
527   return;
528
529 suc:
530   /* create cfg filename */
531   GNUNET_asprintf (dest, "%s_peer%u.conf", filename, count);
532   GNUNET_free (backup);
533 }
534
535
536
537 /* end of transport_testing.h */