Tell core that we want to have this packet delivered
[oweals/gnunet.git] / src / util / server_nc.c
1 /*
2      This file is part of GNUnet.
3      (C) 2010 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file util/server_nc.c
23  * @brief convenience functions for transmission of
24  *        a notification stream 
25  * @author Christian Grothoff
26  */
27
28 #include "platform.h"
29 #include "gnunet_common.h"
30 #include "gnunet_connection_lib.h"
31 #include "gnunet_scheduler_lib.h"
32 #include "gnunet_server_lib.h"
33 #include "gnunet_time_lib.h"
34
35
36 #define DEBUG_SERVER_NC GNUNET_NO
37
38 /**
39  * Entry in list of messages pending to be transmitted.
40  */
41 struct PendingMessageList
42 {
43
44   /**
45    * This is a linked list.
46    */ 
47   struct PendingMessageList *next;
48
49   /**
50    * Message to transmit (allocated at the end of this
51    * struct, do not free)
52    */
53   const struct GNUNET_MessageHeader *msg;
54
55   /**
56    * Can this message be dropped?
57    */ 
58   int can_drop;
59
60 };
61
62
63 /**
64  * Lists of clients we manage for notifications.
65  */
66 struct ClientList
67 {
68
69   /**
70    * This is a linked list.
71    */ 
72   struct ClientList *next;
73
74   /**
75    * Overall context this client belongs to. 
76    */
77   struct GNUNET_SERVER_NotificationContext *nc;
78
79   /**
80    * Handle to the client.
81    */
82   struct GNUNET_SERVER_Client *client;
83
84   /**
85    * Handle for pending transmission request to the client (or NULL).
86    */
87   struct GNUNET_CONNECTION_TransmitHandle *th;
88
89   /**
90    * Head of linked list of requests queued for transmission.
91    */ 
92   struct PendingMessageList *pending_head;
93
94   /**
95    * Tail of linked list of requests queued for transmission.
96    */ 
97   struct PendingMessageList *pending_tail;
98
99   /**
100    * Number of messages currently in the list.
101    */
102   unsigned int num_pending;
103
104 };
105
106
107 /**
108  * The notification context is the key datastructure for a conveniance
109  * API used for transmission of notifications to the client until the
110  * client disconnects (or the notification context is destroyed, in
111  * which case we disconnect these clients).  Essentially, all
112  * (notification) messages are queued up until the client is able to
113  * read them.
114  */
115 struct GNUNET_SERVER_NotificationContext
116 {
117
118   /**
119    * Server we do notifications for.
120    */
121   struct GNUNET_SERVER_Handle *server;
122
123   /**
124    * List of clients receiving notifications.
125    */
126   struct ClientList *clients;
127
128   /**
129    * Maximum number of optional messages to queue per client.
130    */
131   unsigned int queue_length;
132
133 };
134
135
136 /**
137  * Client has disconnected, clean up.
138  *
139  * @param cls our 'struct GNUNET_SERVER_NotificationContext *'
140  * @param client handle of client that disconnected
141  */
142 static void
143 handle_client_disconnect (void *cls,
144                           struct GNUNET_SERVER_Client *client)
145 {
146   struct GNUNET_SERVER_NotificationContext *nc = cls;
147   struct ClientList *pos;
148   struct ClientList *prev;
149   struct PendingMessageList *pml;
150
151   if (client == NULL)
152     {
153       nc->server = NULL;
154       return;
155     }
156   prev = NULL;
157   pos = nc->clients;
158   while (NULL != pos)
159     {
160       if (pos->client == client)
161         break;
162       prev = pos;
163       pos = pos->next;
164     }
165   if (pos == NULL)
166     return;
167 #if DEBUG_SERVER_NC
168   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
169               "Client disconnected, cleaning up %u messages in NC queue\n",
170               pos->num_pending);
171 #endif
172   if (prev == NULL)
173     nc->clients = pos->next;
174   else
175     prev->next = pos->next;
176   while (NULL != (pml = pos->pending_head))
177     {
178       pos->pending_head = pml->next;
179       GNUNET_free (pml);
180     }
181   GNUNET_SERVER_client_drop (client);
182   if (pos->th != NULL)
183     {
184       GNUNET_CONNECTION_notify_transmit_ready_cancel (pos->th);
185       pos->th = NULL;
186     }
187   GNUNET_free (pos);
188 }
189
190
191 /**
192  * Create a new notification context.
193  *
194  * @param server server for which this function creates the context
195  * @param queue_length maximum number of messages to keep in
196  *        the notification queue; optional messages are dropped
197  *        it the queue gets longer than this number of messages
198  * @return handle to the notification context
199  */
200 struct GNUNET_SERVER_NotificationContext *
201 GNUNET_SERVER_notification_context_create (struct GNUNET_SERVER_Handle *server,
202                                            unsigned int queue_length)
203 {
204   struct GNUNET_SERVER_NotificationContext *ret;
205
206   ret = GNUNET_malloc (sizeof (struct GNUNET_SERVER_NotificationContext));
207   ret->server = server;
208   ret->queue_length = queue_length;
209   GNUNET_SERVER_disconnect_notify (server,
210                                    &handle_client_disconnect,
211                                    ret);
212   return ret;
213 }
214
215
216 /**
217  * Destroy the context, force disconnect for all clients.
218  *
219  * @param nc context to destroy.
220  */
221 void
222 GNUNET_SERVER_notification_context_destroy (struct GNUNET_SERVER_NotificationContext *nc)
223 {
224   struct ClientList *pos;
225   struct PendingMessageList *pml;
226
227   while (NULL != (pos = nc->clients))
228     {
229       nc->clients = pos->next;
230       GNUNET_SERVER_client_drop (pos->client); 
231       GNUNET_SERVER_receive_done (pos->client, GNUNET_NO);
232       while (NULL != (pml = pos->pending_head))
233         {
234           pos->pending_head = pml->next;
235           GNUNET_free (pml);
236         }
237       GNUNET_free (pos);
238     }
239   if (nc->server != NULL)
240     GNUNET_SERVER_disconnect_notify_cancel (nc->server,
241                                             &handle_client_disconnect,
242                                             nc);
243   GNUNET_free (nc);
244 }
245
246
247 /**
248  * Add a client to the notification context.
249  *
250  * @param nc context to modify
251  * @param client client to add
252  */
253 void
254 GNUNET_SERVER_notification_context_add (struct GNUNET_SERVER_NotificationContext *nc,
255                                         struct GNUNET_SERVER_Client *client)
256 {
257   struct ClientList *cl;
258
259   cl = GNUNET_malloc (sizeof (struct ClientList));
260   cl->next = nc->clients;
261   cl->nc = nc;
262   cl->client = client;
263   GNUNET_SERVER_client_keep (client);
264   nc->clients = cl;
265 }
266
267
268 /**
269  * Function called to notify a client about the socket begin ready to
270  * queue more data.  "buf" will be NULL and "size" zero if the socket
271  * was closed for writing in the meantime.
272  *
273  * @param cls the 'struct ClientList *'
274  * @param size number of bytes available in buf
275  * @param buf where the callee should write the message
276  * @return number of bytes written to buf
277  */
278 static size_t
279 transmit_message (void *cls,
280                   size_t size,
281                   void *buf)
282 {
283   struct ClientList *cl = cls;
284   char *cbuf = buf;
285   struct PendingMessageList *pml;
286   uint16_t msize;
287   size_t ret;
288
289   cl->th = NULL;
290   if (buf == NULL)
291     {
292       /* 'cl' should be freed via disconnect notification shortly */
293 #if DEBUG_SERVER_NC
294       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
295                   "Failed to transmit message from NC queue to client\n");
296 #endif
297       return 0;
298     }
299   ret = 0;
300   while (cl->pending_head != NULL)
301     {
302       pml = cl->pending_head;
303       msize = ntohs (pml->msg->size);
304       if (size < msize)
305         break;
306       cl->pending_head = pml->next;
307       if (pml->next == NULL)
308         cl->pending_tail = NULL;
309 #if DEBUG_SERVER_NC
310       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
311                   "Copying message of type %u and size %u from pending queue to transmission buffer\n",
312                   ntohs (pml->msg->type),
313                   msize);
314 #endif
315       memcpy (&cbuf[ret], pml->msg, msize);
316       ret += msize;
317       size -= msize;
318       GNUNET_free (pml);
319       cl->num_pending--;
320     }
321   if (cl->pending_head != NULL)    
322     {
323 #if DEBUG_SERVER_NC
324       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
325                   "Have %u messages left in NC queue, will try transmission again\n",
326                   cl->num_pending);
327 #endif
328       cl->th = GNUNET_SERVER_notify_transmit_ready (cl->client,
329                                                     ntohs (cl->pending_head->msg->size),
330                                                     GNUNET_TIME_UNIT_FOREVER_REL,
331                                                     &transmit_message,
332                                                     cl);
333     }
334   return ret;
335 }
336
337
338 /**
339  * Send a message to a particular client.
340  *
341  * @param nc context to modify
342  * @param client client to transmit to
343  * @param msg message to send
344  * @param can_drop can this message be dropped due to queue length limitations
345  */
346 static void
347 do_unicast (struct GNUNET_SERVER_NotificationContext *nc,
348             struct ClientList *client,
349             const struct GNUNET_MessageHeader *msg,
350             int can_drop)
351 {
352   struct PendingMessageList *pml;
353   uint16_t size;
354
355   if ( (client->num_pending > nc->queue_length) &&
356        (GNUNET_YES == can_drop) )
357     {
358       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
359                   "Dropping message of type %u and size %u due to full queue (%u entries)\n",
360                   ntohs (msg->type),
361                   ntohs (msg->size),
362                   (unsigned int) nc->queue_length);
363       return; /* drop! */
364     }
365   if (client->num_pending > nc->queue_length)
366     {
367       /* FIXME: consider checking for other messages in the
368          queue that are 'droppable' */
369     }
370   client->num_pending++;
371   size = ntohs (msg->size);
372   pml = GNUNET_malloc (sizeof (struct PendingMessageList) + size);
373   pml->msg = (const struct GNUNET_MessageHeader*) &pml[1];
374   pml->can_drop = can_drop; 
375 #if DEBUG_SERVER_NC
376   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
377               "Adding message of type %u and size %u to pending queue (which has %u entries)\n",
378               ntohs (msg->type),
379               ntohs (msg->size),
380               (unsigned int) nc->queue_length);
381 #endif
382   memcpy (&pml[1], msg, size);
383   /* append */
384   if (client->pending_tail != NULL)
385     client->pending_tail->next = pml;
386   else
387     client->pending_head = pml;
388   client->pending_tail = pml;
389   if (client->th == NULL)
390     client->th = GNUNET_SERVER_notify_transmit_ready (client->client,
391                                                       ntohs (client->pending_head->msg->size),
392                                                       GNUNET_TIME_UNIT_FOREVER_REL,
393                                                       &transmit_message,
394                                                       client);
395
396
397
398 /**
399  * Send a message to a particular client; must have
400  * already been added to the notification context.
401  *
402  * @param nc context to modify
403  * @param client client to transmit to
404  * @param msg message to send
405  * @param can_drop can this message be dropped due to queue length limitations
406  */
407 void
408 GNUNET_SERVER_notification_context_unicast (struct GNUNET_SERVER_NotificationContext *nc,
409                                             struct GNUNET_SERVER_Client *client,
410                                             const struct GNUNET_MessageHeader *msg,
411                                             int can_drop)
412 {
413   struct ClientList *pos;
414   
415   pos = nc->clients;
416   while (NULL != pos)
417     {
418       if (pos->client == client)
419         break;
420       pos = pos->next;
421     }
422   GNUNET_assert (pos != NULL);
423   do_unicast (nc, pos, msg, can_drop); 
424 }
425
426
427 /**
428  * Send a message to all clients of this context.
429  *
430  * @param nc context to modify
431  * @param msg message to send
432  * @param can_drop can this message be dropped due to queue length limitations
433  */
434 void
435 GNUNET_SERVER_notification_context_broadcast (struct GNUNET_SERVER_NotificationContext *nc,
436                                               const struct GNUNET_MessageHeader *msg,
437                                               int can_drop)
438 {
439   struct ClientList *pos;
440   
441   pos = nc->clients;
442   while (NULL != pos)
443     {
444       do_unicast (nc, pos, msg, can_drop);
445       pos = pos->next;
446     }
447 }
448
449
450 /* end of server_nc.c */