-fix #3141
[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    * Channel to the other peer.
125    */
126   struct GNUNET_MESH_Channel *channel;
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 channel for creating outbound channels.
157  */
158 static struct GNUNET_MESH_Handle *mesh_handle;
159
160 /**
161  * Map from peer identities to 'struct MeshHandles' with mesh
162  * channels to those peers.
163  */
164 static struct GNUNET_CONTAINER_MultiPeerMap *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   struct GNUNET_MESH_Channel *channel = mh->channel;
217
218   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
219               "Resetting mesh channel to %s\n",
220               GNUNET_i2s (&mh->target));
221   mh->channel = NULL;
222   if (NULL != channel)
223     GNUNET_MESH_channel_destroy (channel);
224   GNUNET_CONTAINER_multihashmap_iterate (mh->waiting_map,
225                                          &move_to_pending,
226                                          mh);
227   mh->channel = GNUNET_MESH_channel_create (mesh_handle,
228                                           mh,
229                                           &mh->target,
230                                           GNUNET_APPLICATION_TYPE_FS_BLOCK_TRANSFER,
231                                           GNUNET_NO,
232                                           GNUNET_YES);
233   transmit_pending (mh);
234 }
235
236
237 /**
238  * Task called when it is time to destroy an inactive mesh channel.
239  *
240  * @param cls the 'struct MeshHandle' to tear down
241  * @param tc scheduler context, unused
242  */
243 static void
244 mesh_timeout (void *cls,
245               const struct GNUNET_SCHEDULER_TaskContext *tc)
246 {
247   struct MeshHandle *mh = cls;
248   struct GNUNET_MESH_Channel *tun;
249
250   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
251               "Timeout on mesh channel to %s\n",
252               GNUNET_i2s (&mh->target));
253   mh->timeout_task = GNUNET_SCHEDULER_NO_TASK;
254   tun = mh->channel;
255   mh->channel = NULL;
256   GNUNET_MESH_channel_destroy (tun);
257 }
258
259
260 /**
261  * Task called when it is time to reset an mesh.
262  *
263  * @param cls the 'struct MeshHandle' to tear down
264  * @param tc scheduler context, unused
265  */
266 static void
267 reset_mesh_task (void *cls,
268                  const struct GNUNET_SCHEDULER_TaskContext *tc)
269 {
270   struct MeshHandle *mh = cls;
271
272   mh->reset_task = GNUNET_SCHEDULER_NO_TASK;
273   reset_mesh (mh);
274 }
275
276
277 /**
278  * We had a serious error, tear down and re-create mesh from scratch,
279  * but do so asynchronously.
280  *
281  * @param mh mesh to reset
282  */
283 static void
284 reset_mesh_async (struct MeshHandle *mh)
285 {
286   if (GNUNET_SCHEDULER_NO_TASK != mh->reset_task)
287     GNUNET_SCHEDULER_cancel (mh->reset_task);
288   mh->reset_task = GNUNET_SCHEDULER_add_now (&reset_mesh_task,
289                                              mh);
290 }
291
292
293 /**
294  * Functions of this signature are called whenever we are ready to transmit
295  * query via a mesh.
296  *
297  * @param cls the struct MeshHandle for which we did the write call
298  * @param size the number of bytes that can be written to @a buf
299  * @param buf where to write the message
300  * @return number of bytes written to @a buf
301  */
302 static size_t
303 transmit_sqm (void *cls,
304               size_t size,
305               void *buf)
306 {
307   struct MeshHandle *mh = cls;
308   struct MeshQueryMessage sqm;
309   struct GSF_MeshRequest *sr;
310
311   mh->wh = NULL;
312   if (NULL == buf)
313   {
314     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
315                 "Mesh channel to %s failed during transmission attempt, rebuilding\n",
316                 GNUNET_i2s (&mh->target));
317     reset_mesh_async (mh);
318     return 0;
319   }
320   sr = mh->pending_head;
321   if (NULL == sr)
322     return 0;
323   GNUNET_assert (size >= sizeof (struct MeshQueryMessage));
324   GNUNET_CONTAINER_DLL_remove (mh->pending_head,
325                                mh->pending_tail,
326                                sr);
327   GNUNET_assert (GNUNET_OK ==
328                  GNUNET_CONTAINER_multihashmap_put (mh->waiting_map,
329                                                     &sr->query,
330                                                     sr,
331                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
332   sr->was_transmitted = GNUNET_YES;
333   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
334               "Sending query for %s via mesh to %s\n",
335               GNUNET_h2s (&sr->query),
336               GNUNET_i2s (&mh->target));
337   sqm.header.size = htons (sizeof (sqm));
338   sqm.header.type = htons (GNUNET_MESSAGE_TYPE_FS_MESH_QUERY);
339   sqm.type = htonl (sr->type);
340   sqm.query = sr->query;
341   memcpy (buf, &sqm, sizeof (sqm));
342   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
343               "Successfully transmitted %u bytes via mesh to %s\n",
344               (unsigned int) size,
345               GNUNET_i2s (&mh->target));
346   transmit_pending (mh);
347   return sizeof (sqm);
348 }
349
350
351 /**
352  * Transmit pending requests via the mesh.
353  *
354  * @param mh mesh to process
355  */
356 static void
357 transmit_pending (struct MeshHandle *mh)
358 {
359   if (NULL == mh->channel)
360     return;
361   if (NULL != mh->wh)
362     return;
363   mh->wh = GNUNET_MESH_notify_transmit_ready (mh->channel, GNUNET_YES /* allow cork */,
364                                               GNUNET_TIME_UNIT_FOREVER_REL,
365                                               sizeof (struct MeshQueryMessage),
366                                               &transmit_sqm, mh);
367 }
368
369
370 /**
371  * Closure for 'handle_reply'.
372  */
373 struct HandleReplyClosure
374 {
375
376   /**
377    * Reply payload.
378    */
379   const void *data;
380
381   /**
382    * Expiration time for the block.
383    */
384   struct GNUNET_TIME_Absolute expiration;
385
386   /**
387    * Number of bytes in 'data'.
388    */
389   size_t data_size;
390
391   /**
392    * Type of the block.
393    */
394   enum GNUNET_BLOCK_Type type;
395
396   /**
397    * Did we have a matching query?
398    */
399   int found;
400 };
401
402
403 /**
404  * Iterator called on each entry in a waiting map to
405  * process a result.
406  *
407  * @param cls the 'struct HandleReplyClosure'
408  * @param key the key of the entry in the map (the query)
409  * @param value the 'struct GSF_MeshRequest' to handle result for
410  * @return #GNUNET_YES (continue to iterate)
411  */
412 static int
413 handle_reply (void *cls,
414               const struct GNUNET_HashCode *key,
415               void *value)
416 {
417   struct HandleReplyClosure *hrc = cls;
418   struct GSF_MeshRequest *sr = value;
419
420   sr->proc (sr->proc_cls,
421             hrc->type,
422             hrc->expiration,
423             hrc->data_size,
424             hrc->data);
425   sr->proc = NULL;
426   GSF_mesh_query_cancel (sr);
427   hrc->found = GNUNET_YES;
428   return GNUNET_YES;
429 }
430
431
432 /**
433  * Functions with this signature are called whenever a complete reply
434  * is received.
435  *
436  * @param cls closure with the 'struct MeshHandle'
437  * @param channel channel handle
438  * @param channel_ctx channel context
439  * @param message the actual message
440  * @return #GNUNET_OK on success, #GNUNET_SYSERR to stop further processing
441  */
442 static int
443 reply_cb (void *cls,
444           struct GNUNET_MESH_Channel *channel,
445           void **channel_ctx,
446           const struct GNUNET_MessageHeader *message)
447 {
448   struct MeshHandle *mh = *channel_ctx;
449   const struct MeshReplyMessage *srm;
450   struct HandleReplyClosure hrc;
451   uint16_t msize;
452   enum GNUNET_BLOCK_Type type;
453   struct GNUNET_HashCode query;
454
455   msize = ntohs (message->size);
456   if (sizeof (struct MeshReplyMessage) > msize)
457   {
458     GNUNET_break_op (0);
459     reset_mesh_async (mh);
460     return GNUNET_SYSERR;
461   }
462   srm = (const struct MeshReplyMessage *) message;
463   msize -= sizeof (struct MeshReplyMessage);
464   type = (enum GNUNET_BLOCK_Type) ntohl (srm->type);
465   if (GNUNET_YES !=
466       GNUNET_BLOCK_get_key (GSF_block_ctx,
467                             type,
468                             &srm[1], msize, &query))
469   {
470     GNUNET_break_op (0);
471     reset_mesh_async (mh);
472     return GNUNET_SYSERR;
473   }
474   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
475               "Received reply `%s' via mesh from peer %s\n",
476               GNUNET_h2s (&query),
477               GNUNET_i2s (&mh->target));
478   GNUNET_MESH_receive_done (channel);
479   GNUNET_STATISTICS_update (GSF_stats,
480                             gettext_noop ("# replies received via mesh"), 1,
481                             GNUNET_NO);
482   hrc.data = &srm[1];
483   hrc.data_size = msize;
484   hrc.expiration = GNUNET_TIME_absolute_ntoh (srm->expiration);
485   hrc.type = type;
486   hrc.found = GNUNET_NO;
487   GNUNET_CONTAINER_multihashmap_get_multiple (mh->waiting_map,
488                                               &query,
489                                               &handle_reply,
490                                               &hrc);
491   if (GNUNET_NO == hrc.found)
492   {
493     GNUNET_STATISTICS_update (GSF_stats,
494                               gettext_noop ("# replies received via mesh dropped"), 1,
495                               GNUNET_NO);
496     return GNUNET_OK;
497   }
498   return GNUNET_OK;
499 }
500
501
502 /**
503  * Get (or create) a mesh to talk to the given peer.
504  *
505  * @param target peer we want to communicate with
506  */
507 static struct MeshHandle *
508 get_mesh (const struct GNUNET_PeerIdentity *target)
509 {
510   struct MeshHandle *mh;
511
512   mh = GNUNET_CONTAINER_multipeermap_get (mesh_map,
513                                           target);
514   if (NULL != mh)
515   {
516     if (GNUNET_SCHEDULER_NO_TASK != mh->timeout_task)
517     {
518       GNUNET_SCHEDULER_cancel (mh->timeout_task);
519       mh->timeout_task = GNUNET_SCHEDULER_NO_TASK;
520     }
521     return mh;
522   }
523   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
524               "Creating mesh channel to %s\n",
525               GNUNET_i2s (target));
526   mh = GNUNET_new (struct MeshHandle);
527   mh->reset_task = GNUNET_SCHEDULER_add_delayed (CLIENT_RETRY_TIMEOUT,
528                                                  &reset_mesh_task,
529                                                  mh);
530   mh->waiting_map = GNUNET_CONTAINER_multihashmap_create (16, GNUNET_YES);
531   mh->target = *target;
532   GNUNET_assert (GNUNET_OK ==
533                  GNUNET_CONTAINER_multipeermap_put (mesh_map,
534                                                     &mh->target,
535                                                     mh,
536                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
537   mh->channel = GNUNET_MESH_channel_create (mesh_handle,
538                                             mh,
539                                             &mh->target,
540                                             GNUNET_APPLICATION_TYPE_FS_BLOCK_TRANSFER,
541                                             GNUNET_NO,
542                                             GNUNET_YES);
543   GNUNET_assert (mh ==
544                  GNUNET_CONTAINER_multipeermap_get (mesh_map,
545                                                     target));
546   return mh;
547 }
548
549
550 /**
551  * Look for a block by directly contacting a particular peer.
552  *
553  * @param target peer that should have the block
554  * @param query hash to query for the block
555  * @param type desired type for the block
556  * @param proc function to call with result
557  * @param proc_cls closure for @a proc
558  * @return handle to cancel the operation
559  */
560 struct GSF_MeshRequest *
561 GSF_mesh_query (const struct GNUNET_PeerIdentity *target,
562                 const struct GNUNET_HashCode *query,
563                 enum GNUNET_BLOCK_Type type,
564                 GSF_MeshReplyProcessor proc, void *proc_cls)
565 {
566   struct MeshHandle *mh;
567   struct GSF_MeshRequest *sr;
568
569   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
570               "Preparing to send query for %s via mesh to %s\n",
571               GNUNET_h2s (query),
572               GNUNET_i2s (target));
573   mh = get_mesh (target);
574   sr = GNUNET_new (struct GSF_MeshRequest);
575   sr->mh = mh;
576   sr->proc = proc;
577   sr->proc_cls = proc_cls;
578   sr->type = type;
579   sr->query = *query;
580   GNUNET_CONTAINER_DLL_insert (mh->pending_head,
581                                mh->pending_tail,
582                                sr);
583   transmit_pending (mh);
584   return sr;
585 }
586
587
588 /**
589  * Cancel an active request; must not be called after 'proc'
590  * was calld.
591  *
592  * @param sr request to cancel
593  */
594 void
595 GSF_mesh_query_cancel (struct GSF_MeshRequest *sr)
596 {
597   struct MeshHandle *mh = sr->mh;
598   GSF_MeshReplyProcessor p;
599
600   p = sr->proc;
601   sr->proc = NULL;
602   if (NULL != p)
603   {
604     /* signal failure / cancellation to callback */
605     p (sr->proc_cls, GNUNET_BLOCK_TYPE_ANY,
606        GNUNET_TIME_UNIT_ZERO_ABS,
607        0, NULL);
608   }
609   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
610               "Cancelled query for %s via mesh to %s\n",
611               GNUNET_h2s (&sr->query),
612               GNUNET_i2s (&sr->mh->target));
613   if (GNUNET_YES == sr->was_transmitted)
614     GNUNET_assert (GNUNET_OK ==
615                    GNUNET_CONTAINER_multihashmap_remove (mh->waiting_map,
616                                                          &sr->query,
617                                                          sr));
618   else
619     GNUNET_CONTAINER_DLL_remove (mh->pending_head,
620                                  mh->pending_tail,
621                                  sr);
622   GNUNET_free (sr);
623   if ( (0 == GNUNET_CONTAINER_multihashmap_size (mh->waiting_map)) &&
624        (NULL == mh->pending_head) )
625     mh->timeout_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
626                                                      &mesh_timeout,
627                                                      mh);
628 }
629
630
631 /**
632  * Iterator called on each entry in a waiting map to
633  * call the 'proc' continuation and release associated
634  * resources.
635  *
636  * @param cls the `struct MeshHandle`
637  * @param key the key of the entry in the map (the query)
638  * @param value the `struct GSF_MeshRequest` to clean up
639  * @return #GNUNET_YES (continue to iterate)
640  */
641 static int
642 free_waiting_entry (void *cls,
643                     const struct GNUNET_HashCode *key,
644                     void *value)
645 {
646   struct GSF_MeshRequest *sr = value;
647
648   GSF_mesh_query_cancel (sr);
649   return GNUNET_YES;
650 }
651
652
653 /**
654  * Function called by mesh when a client disconnects.
655  * Cleans up our `struct MeshClient` of that channel.
656  *
657  * @param cls NULL
658  * @param channel channel of the disconnecting client
659  * @param channel_ctx our `struct MeshClient`
660  */
661 static void
662 cleaner_cb (void *cls,
663             const struct GNUNET_MESH_Channel *channel,
664             void *channel_ctx)
665 {
666   struct MeshHandle *mh = channel_ctx;
667   struct GSF_MeshRequest *sr;
668
669   if (NULL == mh->channel)
670     return; /* being destroyed elsewhere */
671   GNUNET_assert (channel == mh->channel);
672   mh->channel = NULL;
673   while (NULL != (sr = mh->pending_head))
674     GSF_mesh_query_cancel (sr);
675   GNUNET_CONTAINER_multihashmap_iterate (mh->waiting_map,
676                                          &free_waiting_entry,
677                                          mh);
678   if (NULL != mh->wh)
679     GNUNET_MESH_notify_transmit_ready_cancel (mh->wh);
680   if (GNUNET_SCHEDULER_NO_TASK != mh->timeout_task)
681     GNUNET_SCHEDULER_cancel (mh->timeout_task);
682   if (GNUNET_SCHEDULER_NO_TASK != mh->reset_task)
683     GNUNET_SCHEDULER_cancel (mh->reset_task);
684   GNUNET_assert (GNUNET_OK ==
685                  GNUNET_CONTAINER_multipeermap_remove (mesh_map,
686                                                        &mh->target,
687                                                        mh));
688   GNUNET_CONTAINER_multihashmap_destroy (mh->waiting_map);
689   GNUNET_free (mh);
690 }
691
692
693 /**
694  * Initialize subsystem for non-anonymous file-sharing.
695  */
696 void
697 GSF_mesh_start_client ()
698 {
699   static const struct GNUNET_MESH_MessageHandler handlers[] = {
700     { &reply_cb, GNUNET_MESSAGE_TYPE_FS_MESH_REPLY, 0 },
701     { NULL, 0, 0 }
702   };
703
704   mesh_map = GNUNET_CONTAINER_multipeermap_create (16, GNUNET_YES);
705   mesh_handle = GNUNET_MESH_connect (GSF_cfg,
706                                      NULL,
707                                      NULL,
708                                      &cleaner_cb,
709                                      handlers,
710                                      NULL);
711 }
712
713
714 /**
715  * Function called on each active meshs to shut them down.
716  *
717  * @param cls NULL
718  * @param key target peer, unused
719  * @param value the `struct MeshHandle` to destroy
720  * @return #GNUNET_YES (continue to iterate)
721  */
722 static int
723 release_meshs (void *cls,
724                const struct GNUNET_PeerIdentity *key,
725                void *value)
726 {
727   struct MeshHandle *mh = value;
728
729   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
730               "Timeout on mesh channel to %s\n",
731               GNUNET_i2s (&mh->target));
732   if (NULL != mh->channel)
733     GNUNET_MESH_channel_destroy (mh->channel);
734   return GNUNET_YES;
735 }
736
737
738 /**
739  * Shutdown subsystem for non-anonymous file-sharing.
740  */
741 void
742 GSF_mesh_stop_client ()
743 {
744   GNUNET_CONTAINER_multipeermap_iterate (mesh_map,
745                                          &release_meshs,
746                                          NULL);
747   GNUNET_CONTAINER_multipeermap_destroy (mesh_map);
748   mesh_map = NULL;
749   if (NULL != mesh_handle)
750   {
751     GNUNET_MESH_disconnect (mesh_handle);
752     mesh_handle = NULL;
753   }
754 }
755
756
757 /* end of gnunet-service-fs_mesh_client.c */