7b159022cc7d8c9bcee7f758943f1d8562cb1387
[oweals/gnunet.git] / src / core / gnunet-service-core_sessions.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009, 2010, 2011 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 core/gnunet-service-core_sessions.c
23  * @brief code for managing of 'encrypted' sessions (key exchange done) 
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet-service-core.h"
28 #include "gnunet-service-core_neighbours.h"
29 #include "gnunet-service-core_kx.h"
30 #include "gnunet-service-core_typemap.h"
31 #include "gnunet-service-core_sessions.h"
32 #include "gnunet-service-core_clients.h"
33 #include "gnunet_constants.h"
34
35 /**
36  * Message ready for encryption.  This struct is followed by the
37  * actual content of the message.
38  */
39 struct SessionMessageEntry
40 {
41
42   /**
43    * We keep messages in a doubly linked list.
44    */
45   struct SessionMessageEntry *next;
46
47   /**
48    * We keep messages in a doubly linked list.
49    */
50   struct SessionMessageEntry *prev;
51
52   /**
53    * Deadline for transmission, 1s after we received it (if we
54    * are not corking), otherwise "now".  Note that this message
55    * does NOT expire past its deadline.
56    */
57   struct GNUNET_TIME_Absolute deadline;
58
59   /**
60    * How long is the message? (number of bytes following the "struct
61    * MessageEntry", but not including the size of "struct
62    * MessageEntry" itself!)
63    */
64   size_t size;
65
66 };
67
68
69 /**
70  * Data kept per session.
71  */
72 struct Session
73 {
74   /**
75    * Identity of the other peer.
76    */
77   struct GNUNET_PeerIdentity peer;
78
79   /**
80    * Head of list of requests from clients for transmission to
81    * this peer.
82    */
83   struct GSC_ClientActiveRequest *active_client_request_head;
84
85   /**
86    * Tail of list of requests from clients for transmission to
87    * this peer.
88    */
89   struct GSC_ClientActiveRequest *active_client_request_tail;
90
91   /**
92    * Head of list of messages ready for encryption.
93    */
94   struct SessionMessageEntry *sme_head;
95
96   /**
97    * Tail of list of messages ready for encryption.
98    */
99   struct SessionMessageEntry *sme_tail;
100
101   /**
102    * Information about the key exchange with the other peer.
103    */
104   struct GSC_KeyExchangeInfo *kxinfo;
105
106   /**
107    * Current type map for this peer.
108    */
109   struct GSC_TypeMap *tmap;
110
111   /**
112    * At what time did we initially establish this session?
113    * (currently unused, should be integrated with ATS in the
114    * future...).
115    */
116   struct GNUNET_TIME_Absolute time_established;
117
118   /**
119    * Task to transmit corked messages with a delay.
120    */
121   GNUNET_SCHEDULER_TaskIdentifier cork_task;
122
123   /**
124    * Is the neighbour queue empty and thus ready for us
125    * to transmit an encrypted message?  
126    */
127   int ready_to_transmit;
128
129 };
130
131
132 /**
133  * Map of peer identities to 'struct Session'.
134  */
135 static struct GNUNET_CONTAINER_MultiHashMap *sessions;
136
137
138 /**
139  * Find the session for the given peer.
140  *
141  * @param peer identity of the peer
142  * @return NULL if we are not connected, otherwise the
143  *         session handle
144  */
145 static struct Session *
146 find_session (const struct GNUNET_PeerIdentity *peer)
147 {
148   return GNUNET_CONTAINER_multihashmap_get (sessions, &peer->hashPubKey);
149 }
150
151
152 /**
153  * End the session with the given peer (we are no longer
154  * connected). 
155  *
156  * @param pid identity of peer to kill session with
157  */
158 void
159 GSC_SESSIONS_end (const struct GNUNET_PeerIdentity *pid)
160 {
161   struct Session *session;
162   struct GSC_ClientActiveRequest *car;
163
164   session = find_session (pid);
165   if (NULL == session)
166     return;
167 #if DEBUG_CORE
168   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
169               "Destroying session for peer `%4s'\n",
170               GNUNET_i2s (&session->peer));
171 #endif
172   if (GNUNET_SCHEDULER_NO_TASK != session->cork_task)
173   {
174     GNUNET_SCHEDULER_cancel (session->cork_task);
175     session->cork_task = GNUNET_SCHEDULER_NO_TASK;
176   }
177   GNUNET_assert (GNUNET_YES ==
178                  GNUNET_CONTAINER_multihashmap_remove (sessions,
179                                                        &session->peer.hashPubKey, session));
180   while (NULL != (car = session->active_client_request_head))
181   {
182     GNUNET_CONTAINER_DLL_remove (session->active_client_request_head,
183                                  session->active_client_request_tail,
184                                  car);
185     GSC_CLIENTS_reject_request (car);
186   }
187   GNUNET_STATISTICS_set (GSC_stats, 
188                          gettext_noop ("# established sessions"),
189                          GNUNET_CONTAINER_multihashmap_size (sessions), 
190                          GNUNET_NO);
191   GNUNET_free (session);
192 }
193
194
195 /**
196  * Create a session, a key exchange was just completed.
197  *
198  * @param peer peer that is now connected
199  * @param kx key exchange that completed
200  */
201 void
202 GSC_SESSIONS_create (const struct GNUNET_PeerIdentity *peer,
203                      struct GSC_KeyExchangeInfo *kx)
204 {
205   struct GNUNET_MessageHeader *hdr;
206   struct Session *session;
207
208 #if DEBUG_CORE
209   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
210               "Creating session for peer `%4s'\n", GNUNET_i2s (peer));
211 #endif
212   session = GNUNET_malloc (sizeof (struct Session));
213   session->peer = *peer;
214   session->kxinfo = kx;
215   session->time_established = GNUNET_TIME_absolute_get ();
216   GNUNET_assert (GNUNET_OK ==
217                  GNUNET_CONTAINER_multihashmap_put (sessions,
218                                                     &peer->hashPubKey, session,
219                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
220   GNUNET_STATISTICS_update (GSC_stats, 
221                             gettext_noop ("# established sessions"),
222                             GNUNET_CONTAINER_multihashmap_size (sessions), 
223                             GNUNET_NO);
224   /* FIXME: we should probably do this periodically (in case
225      type map message is lost...) */
226   hdr = GSC_TYPEMAP_compute_type_map_message ();
227   GSC_KX_encrypt_and_transmit (kx, 
228                                hdr,
229                                ntohs (hdr->size));
230   GNUNET_free (hdr);
231 }
232
233
234 /**
235  * Notify the given client about the session (client is new).
236  *
237  * @param cls the 'struct GSC_Client'
238  * @param key peer identity 
239  * @param value the 'struct Session'
240  * @return GNUNET_OK (continue to iterate)
241  */
242 static int
243 notify_client_about_session (void *cls,
244                              const GNUNET_HashCode *key,
245                              void *value)
246 {
247   struct GSC_Client *client = cls;
248   struct Session *session = value;
249
250   GDS_CLIENTS_notify_client_about_neighbour (client,
251                                              &session->peer,
252                                              NULL, 0, /* FIXME: ATS!? */
253                                              NULL, /* old TMAP: none */
254                                              session->tmap);
255   return GNUNET_OK;
256 }
257
258
259 /**
260  * We have a new client, notify it about all current sessions.
261  *
262  * @param client the new client
263  */
264 void
265 GSC_SESSIONS_notify_client_about_sessions (struct GSC_Client *client)
266 {
267   /* notify new client about existing sessions */
268   GNUNET_CONTAINER_multihashmap_iterate (sessions,
269                                          &notify_client_about_session, client);
270 }
271
272
273 /**
274  * Try to perform a transmission on the given session.  Will solicit
275  * additional messages if the 'sme' queue is not full enough.
276  *
277  * @param session session to transmit messages from
278  */
279 static void
280 try_transmission (struct Session *session);
281
282
283 /**
284  * Queue a request from a client for transmission to a particular peer.
285  *
286  * @param car request to queue; this handle is then shared between
287  *         the caller (CLIENTS subsystem) and SESSIONS and must not
288  *         be released by either until either 'GNUNET_SESSIONS_dequeue',
289  *         'GNUNET_SESSIONS_transmit' or 'GNUNET_CLIENTS_failed'
290  *         have been invoked on it
291  */
292 void
293 GSC_SESSIONS_queue_request (struct GSC_ClientActiveRequest *car)
294 {
295   struct Session *session;
296
297   session = find_session (&car->target);
298   if (session == NULL)
299   {
300     /* neighbour must have disconnected since request was issued,
301      * ignore (client will realize it once it processes the
302      * disconnect notification) */
303 #if DEBUG_CORE
304     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
305                 "Dropped client request for transmission (am disconnected)\n");
306 #endif
307     GNUNET_STATISTICS_update (GSC_stats,
308                               gettext_noop
309                               ("# send requests dropped (disconnected)"), 1,
310                               GNUNET_NO);
311     GSC_CLIENTS_reject_request (car);
312     return;
313   }
314   if (car->msize > GNUNET_CONSTANTS_MAX_ENCRYPTED_MESSAGE_SIZE)
315   {
316     GNUNET_break (0);
317     GSC_CLIENTS_reject_request (car);
318     return;
319   }
320 #if DEBUG_CORE
321   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
322               "Received client transmission request. queueing\n");
323 #endif
324   GNUNET_CONTAINER_DLL_insert (session->active_client_request_head,
325                                session->active_client_request_tail, car);
326   try_transmission (session);
327 }
328
329
330 /**
331  * Dequeue a request from a client from transmission to a particular peer.
332  *
333  * @param car request to dequeue; this handle will then be 'owned' by
334  *        the caller (CLIENTS sysbsystem)
335  */
336 void
337 GSC_SESSIONS_dequeue_request (struct GSC_ClientActiveRequest *car)
338 {
339   struct Session *s;
340
341   s = find_session (&car->target);
342   GNUNET_CONTAINER_DLL_remove (s->active_client_request_head,
343                                s->active_client_request_tail, car);
344 }
345
346
347 /**
348  * Discard all expired active transmission requests from clients.
349  *
350  * @param session session to clean up
351  */
352 static void
353 discard_expired_requests (struct Session *session)
354 {
355   struct GSC_ClientActiveRequest *pos;
356   struct GSC_ClientActiveRequest *nxt;
357   struct GNUNET_TIME_Absolute now;
358   
359   now = GNUNET_TIME_absolute_get ();
360   pos = NULL;
361   nxt = session->active_client_request_head;
362   while (NULL != nxt)
363   {
364     pos = nxt;
365     nxt = pos->next;
366     if ( (pos->deadline.abs_value < now.abs_value) &&
367          (GNUNET_YES != pos->was_solicited) )
368     {
369       GNUNET_STATISTICS_update (GSC_stats,
370                                 gettext_noop
371                                 ("# messages discarded (expired prior to transmission)"),
372                                 1, GNUNET_NO);
373       GNUNET_CONTAINER_DLL_remove (session->active_client_request_head,
374                                    session->active_client_request_tail,
375                                    pos);
376       GSC_CLIENTS_reject_request (pos);
377     }
378   }
379 }
380
381
382 /**
383  * Solicit messages for transmission.
384  *
385  * @param session session to solict messages for
386  */
387 static void
388 solicit_messages (struct Session *session)
389 {
390   struct GSC_ClientActiveRequest *car;
391   size_t so_size;
392
393   discard_expired_requests (session); 
394   so_size = 0;
395   for (car = session->active_client_request_head; NULL != car; car = car->next)
396   {
397     if (so_size + car->msize > GNUNET_CONSTANTS_MAX_ENCRYPTED_MESSAGE_SIZE)
398       break;
399     so_size += car->msize;
400     if (car->was_solicited == GNUNET_YES)
401       continue;
402     car->was_solicited = GNUNET_YES;
403     GSC_CLIENTS_solicit_request (car);
404   }
405 }
406
407
408 /**
409  * Some messages were delayed (corked), but the timeout has now expired.  
410  * Send them now.
411  *
412  * @param cls 'struct Session' with the messages to transmit now
413  * @param tc scheduler context (unused)
414  */
415 static void
416 pop_cork_task (void *cls,
417                const struct GNUNET_SCHEDULER_TaskContext *tc)
418 {
419   struct Session *session = session;
420
421   session->cork_task = GNUNET_SCHEDULER_NO_TASK;
422   try_transmission (session);
423 }
424
425
426 /**
427  * Try to perform a transmission on the given session. Will solicit
428  * additional messages if the 'sme' queue is not full enough.
429  *
430  * @param session session to transmit messages from
431  */
432 static void
433 try_transmission (struct Session *session)
434 {
435   struct SessionMessageEntry *pos;
436   size_t msize;
437   struct GNUNET_TIME_Absolute now;
438   struct GNUNET_TIME_Absolute min_deadline;
439
440   if (GNUNET_YES != session->ready_to_transmit)
441     return;
442   msize = 0;
443   min_deadline = GNUNET_TIME_UNIT_FOREVER_ABS;
444   /* check 'ready' messages */
445   pos = session->sme_head;
446   while ( (NULL != pos) &&
447           (msize + pos->size <= GNUNET_CONSTANTS_MAX_ENCRYPTED_MESSAGE_SIZE) )
448   {
449     GNUNET_assert (pos->size < GNUNET_CONSTANTS_MAX_ENCRYPTED_MESSAGE_SIZE);
450     msize += pos->size;
451     min_deadline = GNUNET_TIME_absolute_min (min_deadline,
452                                              pos->deadline);
453     pos = pos->next;
454   }
455   now = GNUNET_TIME_absolute_get ();
456   if ( (msize == 0) ||
457        ( (msize < GNUNET_CONSTANTS_MAX_ENCRYPTED_MESSAGE_SIZE / 2) &&
458          (min_deadline.abs_value > now.abs_value) ) )
459   {
460     /* not enough ready yet, try to solicit more */
461     solicit_messages (session);
462     if (msize > 0)
463     {
464       /* if there is data to send, just not yet, make sure we do transmit
465          it once the deadline is reached */
466       if (session->cork_task != GNUNET_SCHEDULER_NO_TASK)
467         GNUNET_SCHEDULER_cancel (session->cork_task);
468       session->cork_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining (min_deadline),
469                                                          &pop_cork_task,
470                                                          session);
471     }
472     return;
473   }
474   /* create plaintext buffer of all messages, encrypt and transmit */
475   {
476     static unsigned long long total_bytes;
477     static unsigned int total_msgs;
478     char pbuf[msize];    /* plaintext */
479     size_t used;
480
481     used = 0;
482     pos = session->sme_head;
483     while ( (NULL != pos) &&
484             (used + pos->size <= msize) )
485     {
486       memcpy (&pbuf[used], &pos[1], pos->size);
487       used += pos->size;
488     }
489     /* compute average payload size */
490     total_bytes += used;
491     total_msgs++;
492     if (0 == total_msgs)
493     {
494       /* 2^32 messages, wrap around... */
495       total_msgs = 1;
496       total_bytes = used;
497     }
498     GNUNET_STATISTICS_set (GSC_stats, 
499                            "# avg payload per encrypted message",
500                            total_bytes / total_msgs,
501                            GNUNET_NO);
502     /* now actually transmit... */
503     session->ready_to_transmit = GNUNET_NO;
504     GSC_KX_encrypt_and_transmit (session->kxinfo,
505                                  pbuf,
506                                  used);
507   }
508 }
509
510
511 /**
512  * Send a message to the neighbour now.
513  *
514  * @param cls the message
515  * @param key neighbour's identity
516  * @param value 'struct Neighbour' of the target
517  * @return always GNUNET_OK
518  */
519 static int
520 do_send_message (void *cls, const GNUNET_HashCode * key, void *value)
521 {
522   const struct GNUNET_MessageHeader *hdr = cls;
523   struct Session *session = value;
524   struct SessionMessageEntry *m;
525   uint16_t size;
526
527   size = ntohs (hdr->size);
528   m = GNUNET_malloc (sizeof (struct SessionMessageEntry) + size);
529   memcpy (&m[1], hdr, size);
530   m->size = size;
531   GNUNET_CONTAINER_DLL_insert (session->sme_head,
532                                session->sme_tail,
533                                m);
534   try_transmission (session);
535   return GNUNET_OK;
536 }
537
538
539 /**
540  * Broadcast a message to all neighbours.
541  *
542  * @param msg message to transmit
543  */
544 void
545 GSC_SESSIONS_broadcast (const struct GNUNET_MessageHeader *msg)
546 {
547   if (NULL == sessions)
548     return;
549   GNUNET_CONTAINER_multihashmap_iterate (sessions,
550                                          &do_send_message, (void*) msg);
551 }
552
553
554 /**
555  * Traffic is being solicited for the given peer.  This means that the
556  * message queue on the transport-level (NEIGHBOURS subsystem) is now
557  * empty and it is now OK to transmit another (non-control) message.
558  *
559  * @param pid identity of peer ready to receive data
560  */
561 void
562 GSC_SESSIONS_solicit (const struct GNUNET_PeerIdentity *pid)
563 {
564   struct Session *session;
565
566   session = find_session (pid);
567   if (NULL == session)
568     return;
569   session->ready_to_transmit = GNUNET_YES;
570   try_transmission (session);
571 }
572
573
574 /**
575  * Transmit a message to a particular peer.
576  *
577  * @param car original request that was queued and then solicited;
578  *            this handle will now be 'owned' by the SESSIONS subsystem
579  * @param msg message to transmit
580  * @param cork is corking allowed?
581  */
582 void
583 GSC_SESSIONS_transmit (struct GSC_ClientActiveRequest *car,
584                        const struct GNUNET_MessageHeader *msg,
585                        int cork)
586 {
587   struct Session *session;
588   struct SessionMessageEntry *sme;
589   size_t msize;
590
591   session = find_session (&car->target);
592   msize = ntohs (msg->size);
593   sme = GNUNET_malloc (sizeof (struct SessionMessageEntry) + msize);
594   memcpy (&sme[1], msg, msize);
595   sme->size = msize;
596   if (GNUNET_YES == cork)
597     sme->deadline = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_MAX_CORK_DELAY);
598   GNUNET_CONTAINER_DLL_insert_tail (session->sme_head,
599                                     session->sme_tail,
600                                     sme);
601   try_transmission (session);
602 }
603
604
605 /**
606  * Helper function for GSC_SESSIONS_handle_client_iterate_peers.
607  *
608  * @param cls the 'struct GNUNET_SERVER_TransmitContext' to queue replies
609  * @param key identity of the connected peer
610  * @param value the 'struct Neighbour' for the peer
611  * @return GNUNET_OK (continue to iterate)
612  */
613 #include "core.h"
614 static int
615 queue_connect_message (void *cls, const GNUNET_HashCode * key, void *value)
616 {
617   struct GNUNET_SERVER_TransmitContext *tc = cls;
618   struct Session *session = value;
619   struct ConnectNotifyMessage cnm;
620   struct GNUNET_TRANSPORT_ATS_Information *a;
621  
622   /* FIXME: code duplication with clients... */
623   cnm.header.size = htons (sizeof (struct ConnectNotifyMessage));
624   cnm.header.type = htons (GNUNET_MESSAGE_TYPE_CORE_NOTIFY_CONNECT);
625   cnm.ats_count = htonl (0);
626   cnm.peer = session->peer;
627   a = &cnm.ats;
628   // FIXME: full ats...
629   a[0].type = htonl (GNUNET_TRANSPORT_ATS_ARRAY_TERMINATOR);
630   a[0].value = htonl (0);
631   GNUNET_SERVER_transmit_context_append_message (tc, &cnm.header);
632   return GNUNET_OK;
633 }
634
635
636 /**
637  * Handle CORE_ITERATE_PEERS request. For this request type, the client
638  * does not have to have transmitted an INIT request.  All current peers
639  * are returned, regardless of which message types they accept. 
640  *
641  * @param cls unused
642  * @param client client sending the iteration request
643  * @param message iteration request message
644  */
645 void
646 GSC_SESSIONS_handle_client_iterate_peers (void *cls, struct GNUNET_SERVER_Client *client,
647                                           const struct GNUNET_MessageHeader *message)
648 {
649   struct GNUNET_MessageHeader done_msg;
650   struct GNUNET_SERVER_TransmitContext *tc;
651
652   tc = GNUNET_SERVER_transmit_context_create (client);
653   GNUNET_CONTAINER_multihashmap_iterate (sessions, 
654                                          &queue_connect_message,
655                                          tc);
656   done_msg.size = htons (sizeof (struct GNUNET_MessageHeader));
657   done_msg.type = htons (GNUNET_MESSAGE_TYPE_CORE_ITERATE_PEERS_END);
658   GNUNET_SERVER_transmit_context_append_message (tc, &done_msg);
659   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
660 }
661
662
663 /**
664  * Handle CORE_PEER_CONNECTED request.   Notify client about connection
665  * to the given neighbour.  For this request type, the client does not
666  * have to have transmitted an INIT request.  All current peers are
667  * returned, regardless of which message types they accept.
668  *
669  * @param cls unused
670  * @param client client sending the iteration request
671  * @param message iteration request message
672  */
673 void
674 GSC_SESSIONS_handle_client_have_peer (void *cls, struct GNUNET_SERVER_Client *client,
675                                       const struct GNUNET_MessageHeader *message)
676 {
677   struct GNUNET_MessageHeader done_msg;
678   struct GNUNET_SERVER_TransmitContext *tc;
679   const struct GNUNET_PeerIdentity *peer;
680
681   peer = (const struct GNUNET_PeerIdentity *) &message[1]; // YUCK!
682   tc = GNUNET_SERVER_transmit_context_create (client);
683   GNUNET_CONTAINER_multihashmap_get_multiple (sessions, &peer->hashPubKey,
684                                               &queue_connect_message, tc);
685   done_msg.size = htons (sizeof (struct GNUNET_MessageHeader));
686   done_msg.type = htons (GNUNET_MESSAGE_TYPE_CORE_ITERATE_PEERS_END);
687   GNUNET_SERVER_transmit_context_append_message (tc, &done_msg);
688   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
689 }
690
691
692 /**
693  * Initialize sessions subsystem.
694  */
695 void
696 GSC_SESSIONS_init ()
697 {
698   sessions = GNUNET_CONTAINER_multihashmap_create (128);
699 }
700
701
702 /**
703  * Helper function for GSC_SESSIONS_handle_client_iterate_peers.
704  *
705  * @param cls NULL
706  * @param key identity of the connected peer
707  * @param value the 'struct Session' for the peer
708  * @return GNUNET_OK (continue to iterate)
709  */
710 static int
711 free_session_helper (void *cls, const GNUNET_HashCode * key, void *value)
712 {
713   struct Session *session = value;
714
715   GSC_SESSIONS_end (&session->peer);
716   return GNUNET_OK;
717 }
718
719
720 /**
721  * Shutdown sessions subsystem.
722  */
723 void
724 GSC_SESSIONS_done ()
725 {
726   GNUNET_CONTAINER_multihashmap_iterate (sessions,
727                                          &free_session_helper,
728                                          NULL);
729   GNUNET_CONTAINER_multihashmap_destroy (sessions);
730   sessions = NULL;
731   GNUNET_STATISTICS_set (GSC_stats, 
732                          gettext_noop ("# established sessions"),
733                          0, GNUNET_NO);
734 }
735
736 /* end of gnunet-service-core_sessions.c */
737