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