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