server crash test case
[oweals/gnunet.git] / src / lockmanager / lockmanager_api.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 lockmanager/lockmanager_api.c
23  * @brief API implementation of gnunet_lockmanager_service.h
24  * @author Sree Harsha Totakura
25  */
26
27 #include "platform.h"
28 #include "gnunet_common.h"
29 #include "gnunet_container_lib.h"
30 #include "gnunet_client_lib.h"
31 #include "gnunet_crypto_lib.h"
32 #include "gnunet_lockmanager_service.h"
33 #include "gnunet_protocols.h"
34
35 #include "lockmanager.h"
36
37 #define LOG(kind,...) \
38   GNUNET_log_from (kind, "lockmanager-api",__VA_ARGS__)
39
40 #define TIME_REL_MINS(min) \
41   GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, min)
42
43 #define TIMEOUT TIME_REL_MINS(3)
44
45 /**
46  * Handler for the lockmanager service
47  */
48 struct GNUNET_LOCKMANAGER_Handle
49 {
50   /**
51    * The client connection to the service
52    */
53   struct GNUNET_CLIENT_Connection *conn;
54
55   /**
56    * Hashmap handle
57    */
58   struct GNUNET_CONTAINER_MultiHashMap *hashmap;
59 };
60
61
62 /**
63  * Structure for Locking Request
64  */
65 struct GNUNET_LOCKMANAGER_LockingRequest
66 {
67   /**
68    * The handle associated with this request
69    */
70   struct GNUNET_LOCKMANAGER_Handle *handle;
71
72   /**
73    * The status callback
74    */
75   GNUNET_LOCKMANAGER_StatusCallback status_cb;
76
77   /**
78    * Closure for the status callback
79    */
80   void *status_cb_cls;
81
82   /**
83    * The pending transmit handle for the ACQUIRE message
84    */
85   struct GNUNET_CLIENT_TransmitHandle *transmit_handle;
86
87   /**
88    * The locking domain of this request
89    */
90   char *domain;
91   
92   /**
93    * The lock
94    */
95   uint32_t lock;
96
97   /**
98    * The status of the lock
99    */
100   enum GNUNET_LOCKMANAGER_Status status;
101 };
102
103
104 /**
105  * Structure for matching a lock
106  */
107 struct LockingRequestMatch
108 {
109   /**
110    * The matched LockingRequest entry; Should be NULL if no entry is found
111    */
112   struct GNUNET_LOCKMANAGER_LockingRequest *matched_entry;
113
114   /**
115    * The locking domain name of the lock
116    */
117   const char *domain;
118
119   /**
120    * The lock number
121    */
122   uint32_t lock;
123 };
124
125
126 /**
127  * Get the key for the given lock in the 'lock_map'.
128  *
129  * @param domain_name
130  * @param lock_number
131  * @param key set to the key
132  */
133 static void
134 get_key (const char *domain_name,
135          uint32_t lock_number,
136          struct GNUNET_HashCode *key)
137 {
138   uint32_t *last_32;
139
140   GNUNET_CRYPTO_hash (domain_name,
141                       strlen (domain_name),
142                       key);
143   last_32 = (uint32_t *) key;
144   *last_32 ^= lock_number;
145 }
146
147
148 /**
149  * Hashmap iterator for matching a LockingRequest
150  *
151  * @param cls the LockingRequestMatch structure
152  * @param key current key code
153  * @param value value in the hash map (struct GNUNET_LOCKMANAGER_LockingRequest)
154  * @return GNUNET_YES if we should continue to
155  *         iterate,
156  *         GNUNET_NO if not. 
157  */
158 static int
159 match_iterator (void *cls, const GNUNET_HashCode *key, void *value)
160 {
161   struct LockingRequestMatch *match = cls;
162   struct GNUNET_LOCKMANAGER_LockingRequest *lr = value;
163
164   if ( (match->lock == lr->lock) && (0 == strcmp (match->domain, lr->domain)) )
165   {
166     match->matched_entry = lr;    
167     return GNUNET_NO;
168   }
169   return GNUNET_YES;
170 }
171
172
173 /**
174  * Function to find a LockingRequest associated with the given domain and lock
175  * attributes in the map
176  *
177  * @param map the map where the LockingRequests are stored
178  * @param domain the locking domain name
179  * @param lock the lock number
180  * @return the found LockingRequest; NULL if a matching LockingRequest wasn't
181  *           found 
182  */
183 static struct GNUNET_LOCKMANAGER_LockingRequest *
184 hashmap_find_lockingrequest (const struct GNUNET_CONTAINER_MultiHashMap *map,
185                              const char *domain,
186                              uint32_t lock)
187 {
188   struct GNUNET_HashCode hash;
189   struct LockingRequestMatch lock_match;
190
191   lock_match.matched_entry = NULL;
192   lock_match.domain = domain;
193   lock_match.lock = lock;
194   get_key (domain, lock, &hash);
195   GNUNET_CONTAINER_multihashmap_get_multiple (map,
196                                               &hash,
197                                               &match_iterator,
198                                               &lock_match);
199   return lock_match.matched_entry;
200 }
201
202
203 /**
204  * Task for calling status change callback for a lock
205  *
206  * @param cls the LockingRequest associated with this lock
207  * @param tc the TaskScheduler context
208  */
209 static void
210 call_status_cb_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
211 {
212   const struct GNUNET_LOCKMANAGER_LockingRequest *r = cls;
213
214   if (NULL != r->status_cb)
215   {
216     LOG (GNUNET_ERROR_TYPE_DEBUG,
217          "Calling status change for SUCCESS on lock num: %d, domain: %s\n",
218          r->lock, r->domain);
219     r->status_cb (r->status_cb_cls,
220                   r->domain,
221                   r->lock,
222                   r->status);
223   }
224 }
225
226
227 /**
228  * Iterator to call relase and free all LockingRequest entries
229  *
230  * @param cls the lockmanager handle
231  * @param key current key code
232  * @param value the Locking request
233  * @return GNUNET_YES if we should continue to
234  *         iterate,
235  *         GNUNET_NO if not.
236  */
237 static int
238 release_iterator(void *cls,
239                  const GNUNET_HashCode * key,
240                  void *value)
241 {
242   struct GNUNET_LOCKMANAGER_Handle *h = cls;
243   struct GNUNET_LOCKMANAGER_LockingRequest *r = value;
244
245   if (NULL != r->status_cb)
246   {
247     LOG (GNUNET_ERROR_TYPE_DEBUG,
248          "Calling status change for RELEASE on lock num: %d, domain: %s\n",
249          r->lock, r->domain);
250     r->status_cb (r->status_cb_cls,
251                   r->domain,
252                   r->lock,
253                   GNUNET_LOCKMANAGER_RELEASE);
254   }
255   GNUNET_assert (GNUNET_YES == 
256                  GNUNET_CONTAINER_multihashmap_remove (h->hashmap,
257                                                        key,
258                                                        value));
259   GNUNET_free (r->domain);
260   GNUNET_free (r);
261   return GNUNET_YES;
262 }
263
264
265 /**
266  * Handler for server replies
267  *
268  * @param cls the LOCKMANAGER_Handle
269  * @param msg received message, NULL on timeout or fatal error
270  */
271 static void 
272 handle_replies (void *cls,
273                 const struct GNUNET_MessageHeader *msg)
274 {
275   struct GNUNET_LOCKMANAGER_Handle *handle = cls;
276   const struct GNUNET_LOCKMANAGER_Message *m;
277   struct GNUNET_LOCKMANAGER_LockingRequest *lr;
278   const char *domain;
279   struct GNUNET_HashCode hash;
280   uint32_t lock;
281   uint16_t msize;
282   
283   if (NULL == msg)
284   {
285     LOG (GNUNET_ERROR_TYPE_DEBUG,
286          "Lockmanager service not available or went down\n");
287     /* Should release all locks and free its locking requests */
288     GNUNET_CONTAINER_multihashmap_iterate (handle->hashmap,
289                                            &release_iterator,
290                                            handle);
291     return;
292   }
293   if (GNUNET_MESSAGE_TYPE_LOCKMANAGER_SUCCESS != ntohs(msg->type))
294   {
295     GNUNET_break (0);
296     return;
297   }
298   msize = ntohs (msg->size);
299   if (msize <= sizeof (struct GNUNET_LOCKMANAGER_Message))
300   {
301     GNUNET_break (0);
302     return;
303   }
304   m = (const struct GNUNET_LOCKMANAGER_Message *) msg;
305   domain = (const char *) &m[1];
306   msize -= sizeof (struct GNUNET_LOCKMANAGER_Message);
307   if ('\0' != domain[msize-1])
308   {
309     GNUNET_break (0);
310     return;
311   }
312
313   lock = ntohl (m->lock);
314   get_key (domain, lock, &hash);      
315   LOG (GNUNET_ERROR_TYPE_DEBUG,
316        "Received SUCCESS message for lock: %d, domain %s\n",
317        lock, domain);
318   if (NULL == (lr = hashmap_find_lockingrequest (handle->hashmap,
319                                                  domain,
320                                                  lock)))
321   {
322     GNUNET_break (0);
323     return;
324   }
325   if (GNUNET_LOCKMANAGER_SUCCESS == lr->status)
326   {
327     GNUNET_break (0);
328     return;
329   }
330   LOG (GNUNET_ERROR_TYPE_DEBUG,
331        "Changing status for lock: %d in domain: %s to SUCCESS\n",
332        lr->lock, lr->domain);
333   lr->status = GNUNET_LOCKMANAGER_SUCCESS;
334   GNUNET_SCHEDULER_add_continuation (&call_status_cb_task,
335                                      lr,
336                                      GNUNET_SCHEDULER_REASON_PREREQ_DONE);
337 }
338
339
340 /**
341  * Transmit notify for sending message to server
342  *
343  * @param cls the message to send
344  * @param size number of bytes available in buf
345  * @param buf where the callee should write the message
346  * @return number of bytes written to buf
347  */
348 static size_t 
349 transmit_notify (void *cls, size_t size, void *buf)
350 {
351   struct GNUNET_LOCKMANAGER_Message *msg = cls;
352   uint16_t msg_size;
353
354   if ((0 == size) || (NULL == buf))
355   {
356     /* FIXME: Timed out -- requeue? */
357     return 0;
358   }
359   msg_size = ntohs (msg->header.size);
360   GNUNET_assert (size >= msg_size);
361   memcpy (buf, msg, msg_size);
362   GNUNET_free (msg);
363   LOG (GNUNET_ERROR_TYPE_DEBUG,
364        "Message of size %u sent\n", msg_size);
365   return msg_size;
366 }
367
368
369 /**
370  * Iterator to free hash map entries.
371  *
372  * @param cls the lockmanger handle
373  * @param key current key code
374  * @param value the Locking request
375  * @return GNUNET_YES if we should continue to
376  *         iterate,
377  *         GNUNET_NO if not.
378  */
379 static int
380 free_iterator(void *cls,
381               const GNUNET_HashCode * key,
382               void *value)
383 {
384   struct GNUNET_LOCKMANAGER_Handle *h = cls;
385   struct GNUNET_LOCKMANAGER_LockingRequest *r = value;
386
387   LOG (GNUNET_ERROR_TYPE_DEBUG,
388        "Clearing locking request\n");
389   GNUNET_assert (GNUNET_YES == 
390                  GNUNET_CONTAINER_multihashmap_remove (h->hashmap,
391                                                        key,
392                                                        value));
393   GNUNET_free (r->domain);
394   GNUNET_free (r);
395   return GNUNET_YES;
396 }
397
398
399 /*******************/
400 /* API Definitions */
401 /*******************/
402
403
404 /**
405  * Connect to the lockmanager service
406  *
407  * @param cfg the configuration to use
408  *
409  * @return upon success the handle to the service; NULL upon error
410  */
411 struct GNUNET_LOCKMANAGER_Handle *
412 GNUNET_LOCKMANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg)
413 {
414   struct GNUNET_LOCKMANAGER_Handle *h;
415
416   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __func__);
417   h = GNUNET_malloc (sizeof (struct GNUNET_LOCKMANAGER_Handle));
418   h->conn = GNUNET_CLIENT_connect ("lockmanager", cfg);
419   if (NULL == h->conn)
420   {
421     GNUNET_free (h);
422     LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
423     return NULL;
424   }  
425   h->hashmap = GNUNET_CONTAINER_multihashmap_create (15);
426   GNUNET_assert (NULL != h->hashmap);
427   GNUNET_CLIENT_receive (h->conn,
428                          &handle_replies,
429                          h,
430                          GNUNET_TIME_UNIT_FOREVER_REL);
431   
432   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
433   return h;
434 }
435
436
437 /**
438  * Disconnect from the lockmanager service
439  *
440  * @param handle the handle to the lockmanager service
441  */
442 void
443 GNUNET_LOCKMANAGER_disconnect (struct GNUNET_LOCKMANAGER_Handle *handle)
444 {
445   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __func__);
446   GNUNET_CLIENT_disconnect (handle->conn);
447   if (0 != GNUNET_CONTAINER_multihashmap_size (handle->hashmap))
448   {
449     LOG (GNUNET_ERROR_TYPE_WARNING,
450          "Some locking requests are still present. Cancel them before "
451          "calling %s\n", __func__);
452     GNUNET_CONTAINER_multihashmap_iterate (handle->hashmap,
453                                            &free_iterator,
454                                            handle);
455   }
456   GNUNET_CONTAINER_multihashmap_destroy (handle->hashmap);
457   GNUNET_free (handle);
458   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
459 }
460
461
462 /**
463  * Tries to acquire the given lock(even if the lock has been lost) until the
464  * request is called. If the lock is available the status_cb will be
465  * called. If the lock is busy then the request is queued and status_cb
466  * will be called when the lock has been made available and acquired by us.
467  *
468  * @param handle the handle to the lockmanager service
469  *
470  * @param domain_name name of the locking domain. Clients who want to share
471  *          locks must use the same name for the locking domain. Also the
472  *          domain_name should be selected with the prefix
473  *          "GNUNET_<PROGRAM_NAME>_" to avoid domain name collisions.
474  *
475  *
476  * @param lock which lock to lock
477  *
478  * @param status_cb the callback for signalling when the lock is acquired and
479  *          when it is lost
480  *
481  * @param status_cb_cls the closure to the above callback
482  *
483  * @return the locking request handle for this request
484  */
485 struct GNUNET_LOCKMANAGER_LockingRequest *
486 GNUNET_LOCKMANAGER_acquire_lock (struct GNUNET_LOCKMANAGER_Handle *handle,
487                                  const char *domain_name,
488                                  uint32_t lock,
489                                  GNUNET_LOCKMANAGER_StatusCallback
490                                  status_cb,
491                                  void *status_cb_cls)
492 {
493   struct GNUNET_LOCKMANAGER_LockingRequest *r;
494   struct GNUNET_LOCKMANAGER_Message *msg;
495   struct GNUNET_HashCode hash;
496   uint16_t msg_size;
497   size_t domain_name_length;
498   
499   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __func__);
500   r = GNUNET_malloc (sizeof (struct GNUNET_LOCKMANAGER_LockingRequest));
501   domain_name_length = strlen (domain_name) + 1;
502   r->handle = handle;
503   r->lock = lock;
504   r->domain = GNUNET_malloc (domain_name_length);
505   r->status = GNUNET_LOCKMANAGER_RELEASE;
506   r->status_cb = status_cb;
507   r->status_cb_cls = status_cb_cls;
508   memcpy (r->domain, domain_name, domain_name_length);
509   msg_size = sizeof (struct GNUNET_LOCKMANAGER_Message) + domain_name_length;
510   msg = GNUNET_malloc (msg_size);
511   msg->header.type = htons (GNUNET_MESSAGE_TYPE_LOCKMANAGER_ACQUIRE);
512   msg->header.size = htons (msg_size);
513   msg->lock = htonl (lock);
514   memcpy (&msg[1], r->domain, domain_name_length);
515   LOG (GNUNET_ERROR_TYPE_DEBUG, "Queueing ACQUIRE message\n");
516   r->transmit_handle =
517     GNUNET_CLIENT_notify_transmit_ready (r->handle->conn,
518                                          msg_size,
519                                          TIMEOUT,
520                                          GNUNET_YES,
521                                          &transmit_notify,
522                                          msg);
523   get_key (r->domain, r->lock, &hash);
524   GNUNET_CONTAINER_multihashmap_put (r->handle->hashmap,
525                                      &hash,
526                                      r,
527                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
528   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
529   return r;
530 }
531
532
533
534 /**
535  * Function to cancel the locking request generated by
536  * GNUNET_LOCKMANAGER_acquire_lock. If the lock is acquired us then the lock is
537  * released. GNUNET_LOCKMANAGER_StatusCallback will not be called upon any
538  * status changes resulting due to this call.
539  *
540  * @param request the LockingRequest to cancel
541  */
542 void
543 GNUNET_LOCKMANAGER_cancel_request (struct GNUNET_LOCKMANAGER_LockingRequest
544                                    *request)
545 {
546   struct GNUNET_LOCKMANAGER_Message *msg;
547   struct GNUNET_HashCode hash;
548   uint16_t msg_size;
549   size_t domain_name_length;
550
551   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __func__);
552   /* FIXME: Stop ACQUIRE retransmissions */
553   if (GNUNET_LOCKMANAGER_SUCCESS == request->status)
554   {
555     domain_name_length = strlen (request->domain) + 1;
556     msg_size = sizeof (struct GNUNET_LOCKMANAGER_Message) 
557       + domain_name_length;
558     msg = GNUNET_malloc (msg_size);
559     msg->header.type = htons (GNUNET_MESSAGE_TYPE_LOCKMANAGER_RELEASE);
560     msg->header.size = htons (msg_size);
561     msg->lock = htonl (request->lock);
562     memcpy (&msg[1], request->domain, domain_name_length);
563     GNUNET_CLIENT_notify_transmit_ready (request->handle->conn,
564                                          msg_size,
565                                          TIMEOUT, /* What if this fails */
566                                          GNUNET_NO,
567                                          &transmit_notify,
568                                          msg);
569   }
570   get_key (request->domain, request->lock, &hash);
571   GNUNET_assert (GNUNET_YES ==
572                  GNUNET_CONTAINER_multihashmap_remove
573                  (request->handle->hashmap, &hash, request));
574   GNUNET_free (request->domain);
575   GNUNET_free (request);
576   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
577 }