-testbed service reply queue and host success reply
[oweals/gnunet.git] / src / testbed / gnunet-service-testbed.c
1 /*
2   This file is part of GNUnet.
3   (C) 2012 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 testbed/gnunet-service-testbed.c
23  * @brief implementation of the TESTBED service
24  * @author Sree Harsha Totakura
25  */
26
27 #include "platform.h"
28 #include "gnunet_service_lib.h"
29 #include "gnunet_server_lib.h"
30
31 #include "testbed.h"
32 #include "gnunet_testbed_service.h"
33 #include "testbed_api_hosts.h"
34
35 /**
36  * Generic logging
37  */
38 #define LOG(kind,...)                           \
39   GNUNET_log (kind, __VA_ARGS__)
40
41 /**
42  * Debug logging
43  */
44 #define LOG_DEBUG(...)                          \
45   LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
46
47 struct Context
48 {
49   /**
50    * The client handle associated with this context
51    */
52   struct GNUNET_SERVER_Client *client;
53   
54   /**
55    * Event mask of event to be responded in this context
56    */
57   uint64_t event_mask;
58
59   /**
60    * Our host id according to this context
61    */
62   uint32_t host_id;
63 };
64
65
66 /**
67  * The message queue for sending messages to clients
68  */
69 struct MessageQueue
70 {
71   /**
72    * The message to be sent
73    */
74   struct GNUNET_MessageHeader *msg;
75
76   /**
77    * The client to send the message to
78    */
79   struct GNUNET_SERVER_Client *client;
80   
81   /**
82    * next pointer for DLL
83    */
84   struct MessageQueue *next;
85   
86   /**
87    * prev pointer for DLL
88    */
89   struct MessageQueue *prev;
90 };
91
92
93
94 /**
95  * Wrapped stdin.
96  */
97 static struct GNUNET_DISK_FileHandle *fh;
98
99 /**
100  * The master context; generated with the first INIT message
101  */
102 static struct Context *master_context;
103
104 /**
105  * The shutdown task handle
106  */
107 static GNUNET_SCHEDULER_TaskIdentifier shutdown_task_id;
108
109 /**
110  * Array of host list
111  */
112 static struct GNUNET_TESTBED_Host **host_list;
113
114 /**
115  * The size of the host list
116  */
117 static uint32_t host_list_size;
118
119 /**
120  * The message queue head
121  */
122 static struct MessageQueue *mq_head;
123
124 /**
125  * The message queue tail
126  */
127 static struct MessageQueue *mq_tail;
128
129 /**
130  * Current Transmit Handle; NULL if no notify transmit exists currently
131  */
132 struct GNUNET_SERVER_TransmitHandle *transmit_handle;
133
134
135 /**
136  * Function called to notify a client about the connection begin ready to queue
137  * more data.  "buf" will be NULL and "size" zero if the connection was closed
138  * for writing in the meantime.
139  *
140  * @param cls NULL
141  * @param size number of bytes available in buf
142  * @param buf where the callee should write the message
143  * @return number of bytes written to buf
144  */
145 static size_t
146 transmit_ready_notify (void *cls, size_t size, void *buf)
147 {
148   struct MessageQueue *mq_entry;
149
150   transmit_handle = NULL;
151   mq_entry = mq_head;
152   GNUNET_assert (NULL != mq_entry);
153   if (0 == size)
154     return 0;
155   GNUNET_assert (ntohs (mq_entry->msg->size) <= size);
156   size = ntohs (mq_entry->msg->size);
157   memcpy (buf, mq_entry->msg, size);
158   GNUNET_free (mq_entry->msg);
159   GNUNET_CONTAINER_DLL_remove (mq_head, mq_tail, mq_entry);
160   GNUNET_free (mq_entry);
161   mq_entry = mq_head;
162   if (NULL != mq_entry)
163     transmit_handle = 
164       GNUNET_SERVER_notify_transmit_ready (mq_entry->client,
165                                            ntohs (mq_entry->msg->size),
166                                            GNUNET_TIME_UNIT_FOREVER_REL,
167                                            &transmit_ready_notify, NULL);
168   return size;
169 }
170
171
172 /**
173  * Queues a message in send queue for sending to the service
174  *
175  * @param controller the handle to the controller
176  * @param msg the message to queue
177  */
178 static void
179 queue_message (struct GNUNET_SERVER_Client *client,
180                struct GNUNET_MessageHeader *msg)
181 {
182   struct MessageQueue *mq_entry;
183   uint16_t type;
184   uint16_t size;
185
186   type = ntohs (msg->type);
187   size = ntohs (msg->size);
188   GNUNET_assert ((GNUNET_MESSAGE_TYPE_TESTBED_INIT <= type) &&
189                  (GNUNET_MESSAGE_TYPE_TESTBED_MAX > type));                 
190   mq_entry = GNUNET_malloc (sizeof (struct MessageQueue));
191   mq_entry->msg = msg;
192   mq_entry->client = client;
193   LOG_DEBUG ( "Queueing message of type %u, size %u for sending\n", type,
194               ntohs (msg->size));
195   GNUNET_CONTAINER_DLL_insert_tail (mq_head, mq_tail, mq_entry);
196   if (NULL == transmit_handle)
197     transmit_handle = 
198       GNUNET_SERVER_notify_transmit_ready (client, size,
199                                            GNUNET_TIME_UNIT_FOREVER_REL,
200                                            &transmit_ready_notify, NULL);
201 }
202
203
204 /**
205  * Function to add a host to the current list of known hosts
206  *
207  * @param host the host to add 
208  * @return GNUNET_OK on success; GNUNET_SYSERR on failure due to host-id
209  *           already in use
210  */
211 static int
212 host_list_add (struct GNUNET_TESTBED_Host *host)
213 {
214   uint32_t host_id;
215   
216   host_id = GNUNET_TESTBED_host_get_id_ (host);
217   if (host_list_size <= host_id)
218   {
219     host_list = GNUNET_realloc (host_list, 
220                                 sizeof (struct GNUNET_TESTBED_Host *)
221                                 * (host_id + 10));
222     host_list_size += (host_id + 10);
223   }
224   if (NULL != host_list[host_id])
225   {
226     LOG_DEBUG ("A host with id: %u already exists\n", host_id);
227     return GNUNET_SYSERR;
228   }
229   host_list[host_id] = host;
230   return GNUNET_OK;
231 }
232
233
234 /**
235  * Message handler for GNUNET_MESSAGE_TYPE_TESTBED_INIT messages
236  *
237  * @param cls NULL
238  * @param client identification of the client
239  * @param message the actual message
240  */
241 static void 
242 handle_init (void *cls,
243              struct GNUNET_SERVER_Client *client,
244              const struct GNUNET_MessageHeader *message)
245 {
246   const struct GNUNET_TESTBED_InitMessage *msg;
247   struct GNUNET_TESTBED_Host *host;
248
249   if (NULL != master_context)
250   {
251     GNUNET_break (0);
252     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
253     return;
254   }
255   msg = (const struct GNUNET_TESTBED_InitMessage *) message;  
256   master_context = GNUNET_malloc (sizeof (struct Context));
257   master_context->client = client;
258   master_context->host_id = ntohl (msg->host_id);
259   host = GNUNET_TESTBED_host_create_with_id (master_context->host_id,
260                                              NULL, NULL, 0);
261   host_list_add (host);
262   master_context->event_mask = GNUNET_ntohll (msg->event_mask);
263   GNUNET_SERVER_client_keep (client);
264   LOG_DEBUG ("Created master context with host ID: %u\n",
265              master_context->host_id);
266   GNUNET_SERVER_receive_done (client, GNUNET_OK);
267 }
268
269
270 /**
271  * Message handler for GNUNET_MESSAGE_TYPE_TESTBED_ADDHOST messages
272  *
273  * @param cls NULL
274  * @param client identification of the client
275  * @param message the actual message
276  */
277 static void 
278 handle_addhost (void *cls,
279                 struct GNUNET_SERVER_Client *client,
280                 const struct GNUNET_MessageHeader *message)
281 {
282   struct GNUNET_TESTBED_Host *host;
283   const struct GNUNET_TESTBED_AddHostMessage *msg;
284   struct GNUNET_TESTBED_HostConfirmedMessage *reply;
285   char *username;
286   char *hostname;
287   char *emsg;
288   uint32_t host_id;
289   uint16_t username_length;
290   uint16_t hostname_length;
291   uint16_t reply_size;
292   
293   msg = (const struct GNUNET_TESTBED_AddHostMessage *) message;
294   username_length = ntohs (msg->user_name_length);
295   username_length = (0 == username_length) ? 0 : username_length + 1;
296   username = (char *) &(msg[1]);
297   hostname = username + username_length;
298   if (ntohs (message->size) <=
299       (sizeof (struct GNUNET_TESTBED_AddHostMessage) + username_length))
300   {
301     GNUNET_break (0);
302     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
303     return;
304   }
305   hostname_length = ntohs (message->size)
306     - (sizeof (struct GNUNET_TESTBED_AddHostMessage) + username_length);
307   if (strlen (hostname) != hostname_length)
308   {
309     GNUNET_break (0);
310     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
311     return;
312   }
313   host_id = ntohl (msg->host_id);
314   LOG_DEBUG ("Received ADDHOST message\n");
315   LOG_DEBUG ("-------host id: %u\n", host_id);
316   if (NULL != hostname) LOG_DEBUG ("-------hostname: %s\n", hostname);
317   if (NULL != username) LOG_DEBUG ("-------username: %s\n", username);
318   LOG_DEBUG ("-------ssh port: %u\n", ntohs (msg->ssh_port));
319   host = GNUNET_TESTBED_host_create_with_id (host_id, hostname, username,
320                                              ntohs (msg->ssh_port));
321   GNUNET_SERVER_receive_done (client, GNUNET_OK);
322   if (GNUNET_OK == host_list_add (host))
323     return;
324   /* We are unable to add a host */
325   emsg = "A host exists with given host-id";
326   GNUNET_TESTBED_host_destroy (host);
327   reply_size = sizeof (struct GNUNET_TESTBED_HostConfirmedMessage)
328     + strlen (emsg) + 1;
329   reply = GNUNET_malloc (reply_size);
330   reply->header.type = htons (GNUNET_MESSAGE_TYPE_TESTBED_ADDHOSTSUCCESS);
331   reply->header.size = htons (reply_size);
332   reply->host_id = htonl (host_id);
333   memcpy (&reply[1], emsg, strlen (emsg) + 1);
334   queue_message (client, (struct GNUNET_MessageHeader *) reply);
335 }
336
337
338 /**
339  * Task to clean up and shutdown nicely
340  *
341  * @param cls NULL
342  * @param tc the TaskContext from scheduler
343  */
344 static void
345 shutdown_task (void *cls,
346                const struct GNUNET_SCHEDULER_TaskContext *tc)
347 {
348   uint32_t host_id;
349
350   shutdown_task_id = GNUNET_SCHEDULER_NO_TASK;
351   GNUNET_SCHEDULER_shutdown ();
352   LOG (GNUNET_ERROR_TYPE_DEBUG, "Shutting down testbed service\n");
353   if (NULL != fh)
354   {
355     GNUNET_DISK_file_close (fh);
356     fh = NULL;
357   }
358   for (host_id = 0; host_id < host_list_size; host_id++)
359     if (NULL != host_list[host_id])
360       GNUNET_TESTBED_host_destroy (host_list[host_id]);
361   GNUNET_free_non_null (master_context);
362   master_context = NULL;
363 }
364
365
366 /**
367  * Callback for client disconnect
368  *
369  * @param cls NULL
370  * @param client the client which has disconnected
371  */
372 static void
373 client_disconnect_cb (void *cls, struct GNUNET_SERVER_Client *client)
374 {
375   if (NULL == master_context)
376     return;
377   if (client == master_context->client)
378   {
379     LOG (GNUNET_ERROR_TYPE_DEBUG, "Master client disconnected\n");
380     GNUNET_SERVER_client_drop (client);
381     /* should not be needed as we're terminated by failure to read
382        from stdin, but if stdin fails for some reason, this shouldn't 
383        hurt for now --- might need to revise this later if we ever
384        decide that master connections might be temporarily down 
385        for some reason */
386     GNUNET_SCHEDULER_shutdown ();
387   }
388 }
389
390
391 /**
392  * Testbed setup
393  *
394  * @param cls closure
395  * @param server the initialized server
396  * @param cfg configuration to use
397  */
398 static void 
399 testbed_run (void *cls,
400              struct GNUNET_SERVER_Handle *server,
401              const struct GNUNET_CONFIGURATION_Handle *cfg)
402 {
403   static const struct GNUNET_SERVER_MessageHandler message_handlers[] =
404     {
405       {&handle_init, NULL, GNUNET_MESSAGE_TYPE_TESTBED_INIT,
406        sizeof (struct GNUNET_TESTBED_InitMessage)},
407       {&handle_addhost, NULL, GNUNET_MESSAGE_TYPE_TESTBED_ADDHOST, 0},
408       {NULL}
409     };
410
411   GNUNET_SERVER_add_handlers (server,
412                               message_handlers);
413   GNUNET_SERVER_disconnect_notify (server,
414                                    &client_disconnect_cb,
415                                    NULL);  
416   fh = GNUNET_DISK_get_handle_from_native (stdin);
417   if (NULL == fh)
418     shutdown_task_id = 
419       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,                                  
420                                     &shutdown_task,
421                                     NULL);
422   else
423     shutdown_task_id = 
424       GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
425                                       fh,
426                                       &shutdown_task,
427                                       NULL);
428 }
429
430
431 /**
432  * The starting point of execution
433  */
434 int main (int argc, char *const *argv)
435 {
436   return
437     (GNUNET_OK ==
438      GNUNET_SERVICE_run (argc,
439                          argv,
440                          "testbed",
441                          GNUNET_SERVICE_OPTION_NONE,
442                          &testbed_run,
443                          NULL)) ? 0 : 1;
444 }