-use GPLv3+ consistently
[oweals/gnunet.git] / src / fs / gnunet-service-fs_mesh_client.c
1 /*
2      This file is part of GNUnet.
3      (C) 2012, 2013 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 3, 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 fs/gnunet-service-fs_mesh_client.c
23  * @brief non-anonymous file-transfer
24  * @author Christian Grothoff
25  *
26  * TODO:
27  * - PORT is set to old application type, unsure if we should keep
28  *   it that way (fine for now)
29  */
30 #include "platform.h"
31 #include "gnunet_constants.h"
32 #include "gnunet_util_lib.h"
33 #include "gnunet_mesh_service.h"
34 #include "gnunet_protocols.h"
35 #include "gnunet_applications.h"
36 #include "gnunet-service-fs.h"
37 #include "gnunet-service-fs_indexing.h"
38 #include "gnunet-service-fs_mesh.h"
39
40
41 /**
42  * After how long do we reset connections without replies?
43  */
44 #define CLIENT_RETRY_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
45
46
47 /** 
48  * Handle for a mesh to another peer.
49  */
50 struct MeshHandle;
51
52
53 /**
54  * Handle for a request that is going out via mesh API.
55  */
56 struct GSF_MeshRequest
57 {
58
59   /**
60    * DLL.
61    */
62   struct GSF_MeshRequest *next;
63
64   /**
65    * DLL.
66    */
67   struct GSF_MeshRequest *prev;
68
69   /**
70    * Which mesh is this request associated with?
71    */
72   struct MeshHandle *mh;
73
74   /**
75    * Function to call with the result.
76    */
77   GSF_MeshReplyProcessor proc;
78
79   /**
80    * Closure for 'proc'
81    */
82   void *proc_cls;
83
84   /**
85    * Query to transmit to the other peer.
86    */
87   struct GNUNET_HashCode query;
88
89   /**
90    * Desired type for the reply.
91    */
92   enum GNUNET_BLOCK_Type type;
93
94   /**
95    * Did we transmit this request already? YES if we are
96    * in the 'waiting_map', NO if we are in the 'pending' DLL.
97    */
98   int was_transmitted;
99 };
100
101
102 /** 
103  * Handle for a mesh to another peer.
104  */
105 struct MeshHandle
106 {
107   /**
108    * Head of DLL of pending requests on this mesh.
109    */
110   struct GSF_MeshRequest *pending_head;
111
112   /**
113    * Tail of DLL of pending requests on this mesh.
114    */
115   struct GSF_MeshRequest *pending_tail;
116
117   /**
118    * Map from query to 'struct GSF_MeshRequest's waiting for
119    * a reply.
120    */
121   struct GNUNET_CONTAINER_MultiHashMap *waiting_map;
122
123   /**
124    * Tunnel to the other peer.
125    */
126   struct GNUNET_MESH_Tunnel *tunnel;
127
128   /**
129    * Handle for active write operation, or NULL.
130    */ 
131   struct GNUNET_MESH_TransmitHandle *wh;
132
133   /**
134    * Which peer does this mesh go to?
135    */ 
136   struct GNUNET_PeerIdentity target;
137
138   /**
139    * Task to kill inactive meshs (we keep them around for
140    * a few seconds to give the application a chance to give
141    * us another query).
142    */
143   GNUNET_SCHEDULER_TaskIdentifier timeout_task;
144
145   /**
146    * Task to reset meshs that had errors (asynchronously,
147    * as we may not be able to do it immediately during a
148    * callback from the mesh API).
149    */
150   GNUNET_SCHEDULER_TaskIdentifier reset_task;
151
152 };
153
154
155 /**
156  * Mesh tunnel for creating outbound tunnels.
157  */
158 static struct GNUNET_MESH_Handle *mesh_handle;
159
160 /**
161  * Map from peer identities to 'struct MeshHandles' with mesh
162  * tunnels to those peers.
163  */
164 static struct GNUNET_CONTAINER_MultiHashMap *mesh_map;
165
166
167 /* ********************* client-side code ************************* */
168
169
170 /**
171  * Transmit pending requests via the mesh.
172  *
173  * @param mh mesh to process
174  */
175 static void
176 transmit_pending (struct MeshHandle *mh);
177
178
179 /**
180  * Iterator called on each entry in a waiting map to 
181  * move it back to the pending list.
182  *
183  * @param cls the 'struct MeshHandle'
184  * @param key the key of the entry in the map (the query)
185  * @param value the 'struct GSF_MeshRequest' to move to pending
186  * @return GNUNET_YES (continue to iterate)
187  */
188 static int
189 move_to_pending (void *cls,
190                  const struct GNUNET_HashCode *key,
191                  void *value)
192 {
193   struct MeshHandle *mh = cls;
194   struct GSF_MeshRequest *sr = value;
195   
196   GNUNET_assert (GNUNET_YES ==
197                  GNUNET_CONTAINER_multihashmap_remove (mh->waiting_map,
198                                                        key,
199                                                        value));
200   GNUNET_CONTAINER_DLL_insert (mh->pending_head,
201                                mh->pending_tail,
202                                sr);
203   sr->was_transmitted = GNUNET_NO;
204   return GNUNET_YES;
205 }
206
207
208 /**
209  * We had a serious error, tear down and re-create mesh from scratch.
210  *
211  * @param mh mesh to reset
212  */
213 static void
214 reset_mesh (struct MeshHandle *mh)
215 {
216   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
217               "Resetting mesh tunnel to %s\n",
218               GNUNET_i2s (&mh->target));
219   GNUNET_MESH_tunnel_destroy (mh->tunnel);
220   GNUNET_CONTAINER_multihashmap_iterate (mh->waiting_map,
221                                          &move_to_pending,
222                                          mh);
223   mh->tunnel = GNUNET_MESH_tunnel_create (mesh_handle,
224                                           mh,
225                                           &mh->target,
226                                           GNUNET_APPLICATION_TYPE_FS_BLOCK_TRANSFER,
227                                           GNUNET_NO,
228                                           GNUNET_YES);
229   transmit_pending (mh);
230 }
231
232
233 /**
234  * Task called when it is time to destroy an inactive mesh tunnel.
235  *
236  * @param cls the 'struct MeshHandle' to tear down
237  * @param tc scheduler context, unused
238  */
239 static void
240 mesh_timeout (void *cls,
241               const struct GNUNET_SCHEDULER_TaskContext *tc)
242 {
243   struct MeshHandle *mh = cls;
244   struct GNUNET_MESH_Tunnel *tun;
245
246   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
247               "Timeout on mesh tunnel to %s\n",
248               GNUNET_i2s (&mh->target));
249   mh->timeout_task = GNUNET_SCHEDULER_NO_TASK;
250   tun = mh->tunnel;
251   mh->tunnel = NULL;
252   GNUNET_MESH_tunnel_destroy (tun);
253 }
254
255
256 /**
257  * Task called when it is time to reset an mesh.
258  *
259  * @param cls the 'struct MeshHandle' to tear down
260  * @param tc scheduler context, unused
261  */
262 static void
263 reset_mesh_task (void *cls,
264                  const struct GNUNET_SCHEDULER_TaskContext *tc)
265 {
266   struct MeshHandle *mh = cls;
267
268   mh->reset_task = GNUNET_SCHEDULER_NO_TASK;
269   reset_mesh (mh);
270 }
271
272
273 /**
274  * We had a serious error, tear down and re-create mesh from scratch,
275  * but do so asynchronously.
276  *
277  * @param mh mesh to reset
278  */
279 static void
280 reset_mesh_async (struct MeshHandle *mh)
281 {
282   if (GNUNET_SCHEDULER_NO_TASK != mh->reset_task)
283     GNUNET_SCHEDULER_cancel (mh->reset_task);
284   mh->reset_task = GNUNET_SCHEDULER_add_now (&reset_mesh_task,
285                                              mh);
286 }
287
288
289 /**
290  * Functions of this signature are called whenever we are ready to transmit
291  * query via a mesh.
292  *
293  * @param cls the struct MeshHandle for which we did the write call
294  * @param size the number of bytes that can be written to 'buf'
295  * @param buf where to write the message
296  * @return number of bytes written to 'buf'
297  */
298 static size_t
299 transmit_sqm (void *cls,
300               size_t size,
301               void *buf)
302 {
303   struct MeshHandle *mh = cls;
304   struct MeshQueryMessage sqm;
305   struct GSF_MeshRequest *sr;
306
307   mh->wh = NULL;
308   if (NULL == buf)
309   {
310     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
311                 "Mesh tunnel to %s failed during transmission attempt, rebuilding\n",
312                 GNUNET_i2s (&mh->target));
313     reset_mesh_async (mh);
314     return 0;
315   }
316   sr = mh->pending_head;
317   if (NULL == sr)
318     return 0;
319   GNUNET_assert (size >= sizeof (struct MeshQueryMessage));
320   GNUNET_CONTAINER_DLL_remove (mh->pending_head,
321                                mh->pending_tail,
322                                sr);
323   GNUNET_assert (GNUNET_OK ==
324                  GNUNET_CONTAINER_multihashmap_put (mh->waiting_map,
325                                                     &sr->query,
326                                                     sr,
327                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
328   sr->was_transmitted = GNUNET_YES;
329   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
330               "Sending query for %s via mesh to %s\n",
331               GNUNET_h2s (&sr->query),
332               GNUNET_i2s (&mh->target));
333   sqm.header.size = htons (sizeof (sqm));
334   sqm.header.type = htons (GNUNET_MESSAGE_TYPE_FS_MESH_QUERY);
335   sqm.type = htonl (sr->type);
336   sqm.query = sr->query;
337   memcpy (buf, &sqm, sizeof (sqm));
338   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
339               "Successfully transmitted %u bytes via mesh to %s\n",
340               (unsigned int) size,
341               GNUNET_i2s (&mh->target));
342   transmit_pending (mh);
343   return sizeof (sqm);
344 }
345           
346
347 /**
348  * Transmit pending requests via the mesh.
349  *
350  * @param mh mesh to process
351  */
352 static void
353 transmit_pending (struct MeshHandle *mh)
354 {
355   if (NULL == mh->tunnel)
356     return;
357   if (NULL != mh->wh)
358     return;
359   mh->wh = GNUNET_MESH_notify_transmit_ready (mh->tunnel, GNUNET_YES /* allow cork */,
360                                               GNUNET_TIME_UNIT_FOREVER_REL,
361                                               sizeof (struct MeshQueryMessage),
362                                               &transmit_sqm, mh);
363 }
364
365
366 /**
367  * Closure for 'handle_reply'.
368  */
369 struct HandleReplyClosure
370 {
371
372   /**
373    * Reply payload.
374    */ 
375   const void *data;
376
377   /**
378    * Expiration time for the block.
379    */
380   struct GNUNET_TIME_Absolute expiration;
381
382   /**
383    * Number of bytes in 'data'.
384    */
385   size_t data_size;
386
387   /** 
388    * Type of the block.
389    */
390   enum GNUNET_BLOCK_Type type;
391   
392   /**
393    * Did we have a matching query?
394    */
395   int found;
396 };
397
398
399 /**
400  * Iterator called on each entry in a waiting map to 
401  * process a result.
402  *
403  * @param cls the 'struct HandleReplyClosure'
404  * @param key the key of the entry in the map (the query)
405  * @param value the 'struct GSF_MeshRequest' to handle result for
406  * @return GNUNET_YES (continue to iterate)
407  */
408 static int
409 handle_reply (void *cls,
410               const struct GNUNET_HashCode *key,
411               void *value)
412 {
413   struct HandleReplyClosure *hrc = cls;
414   struct GSF_MeshRequest *sr = value;
415   
416   sr->proc (sr->proc_cls,
417             hrc->type,
418             hrc->expiration,
419             hrc->data_size,
420             hrc->data);
421   GSF_mesh_query_cancel (sr);
422   hrc->found = GNUNET_YES;
423   return GNUNET_YES;
424 }
425
426
427 /**
428  * Functions with this signature are called whenever a complete reply
429  * is received.
430  *
431  * @param cls closure with the 'struct MeshHandle'
432  * @param tunnel tunnel handle
433  * @param tunnel_ctx tunnel context
434  * @param message the actual message
435  * @return GNUNET_OK on success, GNUNET_SYSERR to stop further processing
436  */
437 static int
438 reply_cb (void *cls,
439           struct GNUNET_MESH_Tunnel *tunnel,
440           void **tunnel_ctx,
441           const struct GNUNET_MessageHeader *message)
442 {
443   struct MeshHandle *mh = *tunnel_ctx;
444   const struct MeshReplyMessage *srm;
445   struct HandleReplyClosure hrc;
446   uint16_t msize;
447   enum GNUNET_BLOCK_Type type;
448   struct GNUNET_HashCode query;
449
450   msize = ntohs (message->size);
451   if (sizeof (struct MeshReplyMessage) > msize)
452   {
453     GNUNET_break_op (0);
454     reset_mesh_async (mh);
455     return GNUNET_SYSERR;
456   }
457   srm = (const struct MeshReplyMessage *) message;
458   msize -= sizeof (struct MeshReplyMessage);
459   type = (enum GNUNET_BLOCK_Type) ntohl (srm->type);
460   if (GNUNET_YES !=
461       GNUNET_BLOCK_get_key (GSF_block_ctx,
462                             type,
463                             &srm[1], msize, &query))
464   {
465     GNUNET_break_op (0); 
466     reset_mesh_async (mh);
467     return GNUNET_SYSERR;
468   }
469   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
470               "Received reply `%s' via mesh from peer %s\n",
471               GNUNET_h2s (&query),
472               GNUNET_i2s (&mh->target));
473   GNUNET_MESH_receive_done (tunnel);
474   GNUNET_STATISTICS_update (GSF_stats,
475                             gettext_noop ("# replies received via mesh"), 1,
476                             GNUNET_NO);
477   hrc.data = &srm[1];
478   hrc.data_size = msize;
479   hrc.expiration = GNUNET_TIME_absolute_ntoh (srm->expiration);
480   hrc.type = type;
481   hrc.found = GNUNET_NO;
482   GNUNET_CONTAINER_multihashmap_get_multiple (mh->waiting_map,
483                                               &query,
484                                               &handle_reply,
485                                               &hrc);
486   if (GNUNET_NO == hrc.found)
487   {
488     GNUNET_STATISTICS_update (GSF_stats,
489                               gettext_noop ("# replies received via mesh dropped"), 1,
490                               GNUNET_NO);
491     return GNUNET_OK;
492   }
493   return GNUNET_OK;
494 }
495
496
497 /**
498  * Get (or create) a mesh to talk to the given peer.
499  *
500  * @param target peer we want to communicate with
501  */
502 static struct MeshHandle *
503 get_mesh (const struct GNUNET_PeerIdentity *target)
504 {
505   struct MeshHandle *mh;
506
507   mh = GNUNET_CONTAINER_multihashmap_get (mesh_map,
508                                           &target->hashPubKey);
509   if (NULL != mh)
510   {
511     if (GNUNET_SCHEDULER_NO_TASK != mh->timeout_task)
512     {
513       GNUNET_SCHEDULER_cancel (mh->timeout_task);
514       mh->timeout_task = GNUNET_SCHEDULER_NO_TASK;
515     }
516     return mh;
517   }
518   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
519               "Creating mesh tunnel to %s\n",
520               GNUNET_i2s (target));
521   mh = GNUNET_new (struct MeshHandle);
522   mh->reset_task = GNUNET_SCHEDULER_add_delayed (CLIENT_RETRY_TIMEOUT,
523                                                  &reset_mesh_task,
524                                                  mh);
525   mh->waiting_map = GNUNET_CONTAINER_multihashmap_create (16, GNUNET_YES);
526   mh->target = *target;
527   mh->tunnel = GNUNET_MESH_tunnel_create (mesh_handle,
528                                           mh,
529                                           &mh->target,
530                                           GNUNET_APPLICATION_TYPE_FS_BLOCK_TRANSFER,
531                                           GNUNET_NO,
532                                           GNUNET_YES);
533   GNUNET_assert (GNUNET_OK ==
534                  GNUNET_CONTAINER_multihashmap_put (mesh_map,
535                                                     &mh->target.hashPubKey,
536                                                     mh,
537                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
538   return mh;
539 }
540
541
542 /**
543  * Look for a block by directly contacting a particular peer.
544  *
545  * @param target peer that should have the block
546  * @param query hash to query for the block
547  * @param type desired type for the block
548  * @param proc function to call with result
549  * @param proc_cls closure for 'proc'
550  * @return handle to cancel the operation
551  */
552 struct GSF_MeshRequest *
553 GSF_mesh_query (const struct GNUNET_PeerIdentity *target,
554                 const struct GNUNET_HashCode *query,
555                 enum GNUNET_BLOCK_Type type,
556                 GSF_MeshReplyProcessor proc, void *proc_cls)
557 {
558   struct MeshHandle *mh;
559   struct GSF_MeshRequest *sr;
560
561   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
562               "Preparing to send query for %s via mesh to %s\n",
563               GNUNET_h2s (query),
564               GNUNET_i2s (target));
565   mh = get_mesh (target);
566   sr = GNUNET_new (struct GSF_MeshRequest);
567   sr->mh = mh;
568   sr->proc = proc;
569   sr->proc_cls = proc_cls;
570   sr->type = type;
571   sr->query = *query;
572   GNUNET_CONTAINER_DLL_insert (mh->pending_head,
573                                mh->pending_tail,
574                                sr);
575   transmit_pending (mh);
576   return sr;
577 }
578
579
580 /**
581  * Cancel an active request; must not be called after 'proc'
582  * was calld.
583  *
584  * @param sr request to cancel
585  */
586 void
587 GSF_mesh_query_cancel (struct GSF_MeshRequest *sr)
588 {
589   struct MeshHandle *mh = sr->mh;
590
591   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
592               "Cancelled query for %s via mesh to %s\n",
593               GNUNET_h2s (&sr->query),
594               GNUNET_i2s (&sr->mh->target));
595   if (GNUNET_YES == sr->was_transmitted)
596     GNUNET_assert (GNUNET_OK ==
597                    GNUNET_CONTAINER_multihashmap_remove (mh->waiting_map,
598                                                          &sr->query,
599                                                          sr));
600   else
601     GNUNET_CONTAINER_DLL_remove (mh->pending_head,
602                                  mh->pending_tail,
603                                  sr);
604   GNUNET_free (sr);
605   if ( (0 == GNUNET_CONTAINER_multihashmap_size (mh->waiting_map)) &&
606        (NULL == mh->pending_head) )
607     mh->timeout_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
608                                                      &mesh_timeout,
609                                                      mh);
610 }
611
612
613 /**
614  * Iterator called on each entry in a waiting map to 
615  * call the 'proc' continuation and release associated
616  * resources.
617  *
618  * @param cls the 'struct MeshHandle'
619  * @param key the key of the entry in the map (the query)
620  * @param value the 'struct GSF_MeshRequest' to clean up
621  * @return GNUNET_YES (continue to iterate)
622  */
623 static int
624 free_waiting_entry (void *cls,
625                     const struct GNUNET_HashCode *key,
626                     void *value)
627 {
628   struct GSF_MeshRequest *sr = value;
629
630   sr->proc (sr->proc_cls, GNUNET_BLOCK_TYPE_ANY,
631             GNUNET_TIME_UNIT_FOREVER_ABS,
632             0, NULL);
633   GSF_mesh_query_cancel (sr);
634   return GNUNET_YES;
635 }
636
637
638 /**
639  * Function called by mesh when a client disconnects.
640  * Cleans up our 'struct MeshClient' of that tunnel.
641  *
642  * @param cls NULL
643  * @param tunnel tunnel of the disconnecting client
644  * @param tunnel_ctx our 'struct MeshClient' 
645  */
646 static void
647 cleaner_cb (void *cls,
648             const struct GNUNET_MESH_Tunnel *tunnel,
649             void *tunnel_ctx)
650 {
651   struct MeshHandle *mh = tunnel_ctx;
652   struct GSF_MeshRequest *sr;
653
654   mh->tunnel = NULL;
655   while (NULL != (sr = mh->pending_head))
656   {
657     sr->proc (sr->proc_cls, GNUNET_BLOCK_TYPE_ANY,
658               GNUNET_TIME_UNIT_FOREVER_ABS,
659               0, NULL);
660     GSF_mesh_query_cancel (sr);
661   }
662   GNUNET_CONTAINER_multihashmap_iterate (mh->waiting_map,
663                                          &free_waiting_entry,
664                                          mh);
665   if (NULL != mh->wh)
666     GNUNET_MESH_notify_transmit_ready_cancel (mh->wh);
667   if (GNUNET_SCHEDULER_NO_TASK != mh->timeout_task)
668     GNUNET_SCHEDULER_cancel (mh->timeout_task);
669   if (GNUNET_SCHEDULER_NO_TASK != mh->reset_task)
670     GNUNET_SCHEDULER_cancel (mh->reset_task);
671   GNUNET_assert (GNUNET_OK ==
672                  GNUNET_CONTAINER_multihashmap_remove (mesh_map,
673                                                        &mh->target.hashPubKey,
674                                                        mh));
675   GNUNET_CONTAINER_multihashmap_destroy (mh->waiting_map);
676   GNUNET_free (mh);
677 }
678
679
680 /**
681  * Initialize subsystem for non-anonymous file-sharing.
682  */
683 void
684 GSF_mesh_start_client ()
685 {
686   static const struct GNUNET_MESH_MessageHandler handlers[] = {
687     { &reply_cb, GNUNET_MESSAGE_TYPE_FS_MESH_REPLY, 0 },
688     { NULL, 0, 0 }
689   };
690
691   mesh_map = GNUNET_CONTAINER_multihashmap_create (16, GNUNET_YES);
692   mesh_handle = GNUNET_MESH_connect (GSF_cfg,
693                                      NULL,
694                                      NULL,
695                                      &cleaner_cb,
696                                      handlers,
697                                      NULL);
698 }
699
700
701 /**
702  * Function called on each active meshs to shut them down.
703  *
704  * @param cls NULL
705  * @param key target peer, unused
706  * @param value the 'struct MeshHandle' to destroy
707  * @return GNUNET_YES (continue to iterate)
708  */
709 static int
710 release_meshs (void *cls,
711                const struct GNUNET_HashCode *key,
712                void *value)
713 {
714   struct MeshHandle *mh = value;
715   struct GNUNET_MESH_Tunnel *tun;
716
717   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
718               "Timeout on mesh tunnel to %s\n",
719               GNUNET_i2s (&mh->target));
720   tun = mh->tunnel;
721   mh->tunnel = NULL;
722   if (NULL != tun)
723     GNUNET_MESH_tunnel_destroy (tun);
724   return GNUNET_YES;
725 }
726
727
728 /**
729  * Shutdown subsystem for non-anonymous file-sharing.
730  */
731 void
732 GSF_mesh_stop_client ()
733 {
734   GNUNET_CONTAINER_multihashmap_iterate (mesh_map,
735                                          &release_meshs,
736                                          NULL);
737   GNUNET_CONTAINER_multihashmap_destroy (mesh_map);
738   mesh_map = NULL;
739   if (NULL != mesh_handle)
740   {
741     GNUNET_MESH_disconnect (mesh_handle);
742     mesh_handle = NULL;
743   }
744 }
745
746
747 /* end of gnunet-service-fs_mesh_client.c */