-allow 'years' in time units, indentation and doxygen fixes
[oweals/gnunet.git] / src / util / client_manager.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 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 util/client_manager.c
23  * @brief Client manager; higher level client API with transmission queue
24  * and message handler registration.
25  * @author Gabor X Toth
26  */
27
28 #include <inttypes.h>
29
30 #include "platform.h"
31 #include "gnunet_util_lib.h"
32
33 #define LOG(kind,...) GNUNET_log_from (kind, "util-client-mgr", __VA_ARGS__)
34
35
36 struct OperationListItem
37 {
38   struct OperationListItem *prev;
39   struct OperationListItem *next;
40
41   /**
42    * Operation ID.
43    */
44   uint64_t op_id;
45
46   /**
47    * Continuation to invoke with the result of an operation.
48    */
49   GNUNET_ResultCallback result_cb;
50
51   /**
52    * Closure for @a result_cb.
53    */
54   void *cls;
55 };
56
57
58 /**
59  * List of arrays of message handlers.
60  */
61 struct HandlersListItem
62 {
63   struct HandlersListItem *prev;
64   struct HandlersListItem *next;
65
66   /**
67    * NULL-terminated array of handlers.
68    */
69   const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers;
70 };
71
72
73 struct MessageQueueItem
74 {
75   struct MessageQueueItem *prev;
76   struct MessageQueueItem *next;
77   struct GNUNET_MessageHeader *msg;
78 };
79
80
81 struct GNUNET_CLIENT_MANAGER_Connection
82 {
83   /**
84    * Configuration to use.
85    */
86   const struct GNUNET_CONFIGURATION_Handle *cfg;
87
88   /**
89    * Client connection to service.
90    */
91   struct GNUNET_CLIENT_Connection *client;
92
93   /**
94    * Currently pending transmission request, or NULL for none.
95    */
96   struct GNUNET_CLIENT_TransmitHandle *client_tmit;
97
98   /**
99    * Service name to connect to.
100    */
101   const char *service_name;
102
103   /**
104    * Head of messages to transmit to the service.
105    */
106   struct MessageQueueItem *tmit_head;
107
108   /**
109    * Tail of messages to transmit to the service.
110    */
111   struct MessageQueueItem *tmit_tail;
112
113   /**
114    * Message handlers.
115    */
116   const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers;
117
118   /**
119    * First operation in the linked list.
120    */
121   struct OperationListItem *op_head;
122
123   /**
124    * Last operation in the linked list.
125    */
126   struct OperationListItem *op_tail;
127
128   /**
129    * Last operation ID used.
130    */
131   uint64_t last_op_id;
132
133   /**
134    * Disconnect callback.
135    */
136   void (*disconnect_cb)(void *);
137
138   /**
139    * Disconnect closure.
140    */
141   void *disconnect_cls;
142
143   /**
144    * User context value.
145    * @see GNUNET_CLIENT_MANAGER_set_user_context()
146    * @see GNUNET_CLIENT_MANAGER_get_user_context()
147    */
148   void *user_ctx;
149
150   /**
151    * Last size given when user context was initialized.
152    * Used for sanity check.
153    */
154   size_t user_ctx_size;
155
156   /**
157    * Task doing exponential back-off trying to reconnect.
158    */
159   struct GNUNET_SCHEDULER_Task * reconnect_task;
160
161   /**
162    * Time for next connect retry.
163    */
164   struct GNUNET_TIME_Relative reconnect_delay;
165
166   /**
167    * Are we currently polling for incoming messages?
168    */
169   uint8_t in_receive;
170
171   /**
172    * #GNUNET_YES if GNUNET_CLIENT_MANAGER_disconnect() was called
173    * and we're transmitting the last messages from the queue.
174    */
175   uint8_t is_disconnecting;
176 };
177
178
179 /**
180  * Handle received messages from the service.
181  */
182 static void
183 recv_message (void *cls, const struct GNUNET_MessageHeader *msg)
184 {
185   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
186   uint16_t type = 0, size = 0;
187
188   if (NULL != msg)
189   {
190     type = ntohs (msg->type);
191     size = ntohs (msg->size);
192     /* FIXME: decrease reconnect_delay gradually after a successful reconnection */
193   }
194
195   size_t i = 0;
196   while (NULL != mgr->handlers[i].callback)
197   {
198     const struct GNUNET_CLIENT_MANAGER_MessageHandler *mh = &mgr->handlers[i];
199     if ((mh->type == type) || (mh->type == GNUNET_MESSAGE_TYPE_ALL))
200     {
201       if (0 != mh->expected_size
202           && ((GNUNET_NO == mh->is_variable_size && size != mh->expected_size)
203               || (GNUNET_YES == mh->is_variable_size && size < mh->expected_size)))
204       {
205         LOG (GNUNET_ERROR_TYPE_ERROR,
206              "Expected %u bytes for message of type %u, got %u.\n",
207              mh->expected_size, type, size);
208         GNUNET_break_op (0);
209         GNUNET_CLIENT_disconnect (mgr->client);
210         mgr->client = NULL;
211         recv_message (mgr, NULL);
212         break;
213       }
214       mh->callback (mh->callback_cls, mgr, msg);
215     }
216     i++;
217   }
218   if (NULL != mgr->client)
219   {
220     GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
221                            GNUNET_TIME_UNIT_FOREVER_REL);
222   }
223 }
224
225
226 /**
227  * Schedule transmission of the next message from our queue.
228  *
229  * @param mgr  Client manager connection.
230  */
231 static void
232 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr);
233
234
235 static void
236 schedule_disconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
237 {
238   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
239   GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
240                                     mgr->disconnect_cb, mgr->disconnect_cls);
241 }
242
243
244 /**
245  * Transmit next message to service.
246  *
247  * @param cls
248  *        struct GNUNET_CLIENT_MANAGER_Connection
249  * @param buf_size
250  *        Number of bytes available in @a buf.
251  * @param buf
252  *        Where to copy the message.
253  *
254  * @return Number of bytes copied to @a buf.
255  */
256 static size_t
257 send_next_message (void *cls, size_t buf_size, void *buf)
258 {
259   LOG (GNUNET_ERROR_TYPE_DEBUG, "send_next_message()\n");
260   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
261
262   if (NULL == buf)
263   {
264     /* disconnected */
265     recv_message (mgr, NULL);
266     return 0;
267   }
268
269   struct MessageQueueItem *mqi = mgr->tmit_head;
270   if (NULL == mqi)
271     return 0;
272
273   uint16_t size = ntohs (mqi->msg->size);
274   mgr->client_tmit = NULL;
275   GNUNET_assert (size <= buf_size);
276   memcpy (buf, mqi->msg, size);
277
278   GNUNET_CONTAINER_DLL_remove (mgr->tmit_head, mgr->tmit_tail, mqi);
279   GNUNET_free (mqi->msg);
280   GNUNET_free (mqi);
281
282   if (NULL != mgr->tmit_head)
283   {
284     transmit_next (mgr);
285   }
286   else if (GNUNET_YES == mgr->is_disconnecting)
287   {
288     GNUNET_SCHEDULER_add_now (&schedule_disconnect, mgr);
289     return size;
290   }
291
292   if (GNUNET_NO == mgr->in_receive)
293   {
294     mgr->in_receive = GNUNET_YES;
295     GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
296                            GNUNET_TIME_UNIT_FOREVER_REL);
297   }
298   return size;
299 }
300
301
302 /**
303  * Schedule transmission of the next message from our queue.
304  *
305  * @param mgr  Client manager connection.
306  */
307 static void
308 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
309 {
310   LOG (GNUNET_ERROR_TYPE_DEBUG, "transmit_next()\n");
311   if (NULL != mgr->client_tmit || NULL == mgr->client)
312     return;
313
314   if (NULL == mgr->tmit_head)
315   {
316     if (GNUNET_YES == mgr->is_disconnecting)
317       GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
318                                         mgr->disconnect_cb, mgr->disconnect_cls);
319     return;
320   }
321
322   mgr->client_tmit
323     = GNUNET_CLIENT_notify_transmit_ready (mgr->client,
324                                            ntohs (mgr->tmit_head->msg->size),
325                                            GNUNET_TIME_UNIT_FOREVER_REL,
326                                            GNUNET_NO,
327                                            &send_next_message,
328                                            mgr);
329 }
330
331
332 /**
333  * Try again to connect to the service.
334  *
335  * @param cls
336  *        Channel handle.
337  * @param tc
338  *        Scheduler context.
339  */
340 static void
341 schedule_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
342 {
343   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
344   mgr->reconnect_task = NULL;
345
346   LOG (GNUNET_ERROR_TYPE_DEBUG,
347        "Connecting to %s service.\n", mgr->service_name);
348   GNUNET_assert (NULL == mgr->client);
349   mgr->client = GNUNET_CLIENT_connect (mgr->service_name, mgr->cfg);
350   GNUNET_assert (NULL != mgr->client);
351
352   transmit_next (mgr);
353 }
354
355
356 /**
357  * Connect to service.
358  *
359  * @param cfg
360  *        Configuration to use.
361  * @param service_name
362  *        Service name to connect to.
363  * @param handlers
364  *        Message handlers.
365  *
366  * @return Client manager connection handle.
367  */
368 struct GNUNET_CLIENT_MANAGER_Connection *
369 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
370                                const char *service_name,
371                                const struct
372                                GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
373 {
374   struct GNUNET_CLIENT_MANAGER_Connection *
375     mgr = GNUNET_malloc (sizeof (*mgr));
376   mgr->cfg = cfg;
377   mgr->service_name = service_name;
378   mgr->handlers = handlers;
379   mgr->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
380   mgr->reconnect_task = GNUNET_SCHEDULER_add_now (&schedule_reconnect, mgr);
381   return mgr;
382 }
383
384
385 /**
386  * Disconnect from the service.
387  *
388  * @param mgr
389  *        Client manager connection.
390  * @param transmit_queue
391  *        Transmit pending messages in queue before disconnecting.
392  * @param disconnect_cb
393  *        Function called after disconnected from the service.
394  * @param cls
395  *        Closure for @a disconnect_cb.
396  */
397 void
398 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
399                                   int transmit_queue,
400                                   GNUNET_ContinuationCallback disconnect_cb,
401                                   void *cls)
402 {
403   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting (%d)\n", transmit_queue);
404   mgr->disconnect_cb = disconnect_cb;
405   mgr->disconnect_cls = cls;
406   if (NULL != mgr->tmit_head)
407   {
408     if (GNUNET_YES == transmit_queue)
409     {
410       mgr->is_disconnecting = GNUNET_YES;
411       transmit_next (mgr);
412       return;
413     }
414     else
415     {
416       LOG (GNUNET_ERROR_TYPE_DEBUG,
417            "Disconnecting while there are still messages "
418            "in the transmission queue.\n");
419       GNUNET_CLIENT_MANAGER_drop_queue (mgr);
420     }
421   }
422   if (mgr->reconnect_task != NULL)
423   {
424     GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
425     mgr->reconnect_task = NULL;
426   }
427   if (NULL != mgr->client_tmit)
428   {
429     GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
430     mgr->client_tmit = NULL;
431   }
432   if (NULL != mgr->client)
433   {
434     GNUNET_CLIENT_disconnect (mgr->client);
435     mgr->client = NULL;
436   }
437   if (NULL != mgr->disconnect_cb)
438     mgr->disconnect_cb (mgr->disconnect_cls);
439   GNUNET_free (mgr);
440   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnected.\n");
441 }
442
443
444 /**
445  * Reschedule connect to the service using exponential back-off.
446  *
447  * @param mgr
448  *        Client manager connection.
449  */
450 void
451 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
452 {
453   if (NULL != mgr->reconnect_task)
454     return;
455
456   if (NULL != mgr->client_tmit)
457   {
458     GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
459     mgr->client_tmit = NULL;
460   }
461   if (NULL != mgr->client)
462   {
463     GNUNET_CLIENT_disconnect (mgr->client);
464     mgr->client = NULL;
465   }
466   mgr->in_receive = GNUNET_NO;
467   LOG (GNUNET_ERROR_TYPE_DEBUG,
468        "Scheduling task to reconnect to service in %s.\n",
469        GNUNET_STRINGS_relative_time_to_string (mgr->reconnect_delay, GNUNET_YES));
470   mgr->reconnect_task =
471     GNUNET_SCHEDULER_add_delayed (mgr->reconnect_delay, &schedule_reconnect, mgr);
472   mgr->reconnect_delay = GNUNET_TIME_STD_BACKOFF (mgr->reconnect_delay);
473 }
474
475
476 /**
477  * Add a message to the end of the transmission queue.
478  *
479  * @param mgr
480  *        Client manager connection.
481  * @param msg
482  *        Message to transmit, should be allocated with GNUNET_malloc() or
483  *        GNUNET_new(), as it is freed with GNUNET_free() after transmission.
484  */
485 void
486 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
487                                 struct GNUNET_MessageHeader *msg)
488 {
489   struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
490   mqi->msg = msg;
491   GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head, mgr->tmit_tail, mqi);
492   transmit_next (mgr);
493 }
494
495
496 /**
497  * Add a message to the beginning of the transmission queue.
498  *
499  * @param mgr
500  *        Client manager connection.
501  * @param msg
502  *        Message to transmit, should be allocated with GNUNET_malloc() or
503  *        GNUNET_new(), as it is freed with GNUNET_free() after transmission.
504  */
505 void
506 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
507                                     struct GNUNET_MessageHeader *msg)
508 {
509   struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
510   mqi->msg = msg;
511   GNUNET_CONTAINER_DLL_insert (mgr->tmit_head, mgr->tmit_tail, mqi);
512   transmit_next (mgr);
513 }
514
515
516 /**
517  * Drop all queued messages.
518  *
519  * @param mgr
520  *        Client manager connection.
521  */
522 void
523 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
524 {
525   struct MessageQueueItem *cur, *next = mgr->tmit_head;
526   while (NULL != next)
527   {
528     cur = next;
529     next = cur->next;
530     GNUNET_free (cur->msg);
531     GNUNET_free (cur);
532   }
533 }
534
535
536 /**
537  * Obtain client connection handle.
538  *
539  * @param mgr
540  *        Client manager connection.
541  *
542  * @return Client connection handle.
543  */
544 struct GNUNET_CLIENT_Connection *
545 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
546 {
547   return mgr->client;
548 }
549
550
551 /**
552  * Return user context associated with the given client.
553  * Note: you should probably use the macro (call without the underscore).
554  *
555  * @param mgr
556  *        Client manager connection.
557  * @param size
558  *        Number of bytes in user context struct (for verification only).
559  *
560  * @return User context.
561  */
562 void *
563 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
564                                          size_t size)
565 {
566   if ((0 == mgr->user_ctx_size) &&
567       (NULL == mgr->user_ctx))
568     return NULL; /* never set */
569   GNUNET_assert (size == mgr->user_ctx_size);
570   return mgr->user_ctx;
571 }
572
573
574 /**
575  * Set user context to be associated with the given client.
576  * Note: you should probably use the macro (call without the underscore).
577  *
578  * @param mgr
579  *        Client manager connection.
580  * @param ctx
581  *        User context.
582  * @param size
583  *        Number of bytes in user context struct (for verification only).
584  */
585 void
586 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
587                                          void *ctx,
588                                          size_t size)
589 {
590   if (NULL == ctx)
591   {
592     mgr->user_ctx_size = 0;
593     mgr->user_ctx = ctx;
594     return;
595   }
596   mgr->user_ctx_size = size;
597   mgr->user_ctx = ctx;
598 }
599
600
601 /**
602  * Get a unique operation ID to distinguish between asynchronous requests.
603  *
604  * @param mgr
605  *        Client manager connection.
606  *
607  * @return Operation ID to use.
608  */
609 uint64_t
610 GNUNET_CLIENT_MANAGER_op_get_next_id (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
611 {
612   return ++mgr->last_op_id;
613 }
614
615
616 /**
617  * Find operation by ID.
618  *
619  * @param mgr
620  *        Client manager connection.
621  * @param op_id
622  *        Operation ID to look up.
623  *
624  * @return Operation, or NULL if not found.
625  */
626 static struct OperationListItem *
627 op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr, uint64_t op_id)
628 {
629   struct OperationListItem *op = mgr->op_head;
630   while (NULL != op)
631   {
632     if (op->op_id == op_id)
633       return op;
634     op = op->next;
635   }
636   return NULL;
637 }
638
639
640 /**
641  * Find operation by ID.
642  *
643  * @param mgr
644  *        Client manager connection.
645  * @param op_id
646  *        Operation ID to look up.
647  * @param[out] result_cb
648  *        If an operation was found, its result callback is returned here.
649  * @param[out] cls
650  *        If an operation was found, its closure is returned here.
651  *
652  * @return #GNUNET_YES if an operation was found,
653  *         #GNUNET_NO  if not found.
654  */
655 int
656 GNUNET_CLIENT_MANAGER_op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
657                                uint64_t op_id,
658                                GNUNET_ResultCallback *result_cb,
659                                void **cls)
660 {
661   struct OperationListItem *op = op_find (mgr, op_id);
662   if (NULL != op)
663   {
664     *result_cb = op->result_cb;
665     *cls = op->cls;
666     return GNUNET_YES;
667   }
668   return GNUNET_NO;
669 }
670
671
672 /**
673  * Add a new operation.
674  *
675  * @param mgr
676  *        Client manager connection.
677  * @param result_cb
678  *        Function to call with the result of the operation.
679  * @param cls
680  *        Closure for @a result_cb.
681  *
682  * @return ID of the new operation.
683  */
684 uint64_t
685 GNUNET_CLIENT_MANAGER_op_add (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
686                               GNUNET_ResultCallback result_cb,
687                               void *cls)
688 {
689   if (NULL == result_cb)
690     return 0;
691
692   struct OperationListItem *op = GNUNET_malloc (sizeof (*op));
693   op->op_id = GNUNET_CLIENT_MANAGER_op_get_next_id (mgr);
694   op->result_cb = result_cb;
695   op->cls = cls;
696   GNUNET_CONTAINER_DLL_insert_tail (mgr->op_head, mgr->op_tail, op);
697
698   LOG (GNUNET_ERROR_TYPE_DEBUG,
699        "%p Added operation #%" PRIu64 "\n", mgr, op->op_id);
700   return op->op_id;
701 }
702
703
704 /**
705  * Remove an operation, and call its result callback (unless it was cancelled).
706  *
707  *
708  * @param mgr
709  *        Client manager connection.
710  * @param op_id
711  *        Operation ID.
712  * @param result_code
713  *        Result of the operation.
714  * @param data
715  *        Data result of the operation.
716  * @param data_size
717  *        Size of @a data.
718  * @param cancel
719  *        Is the operation cancelled?
720  *        #GNUNET_NO  Not cancelled, result callback is called.
721  *        #GNUNET_YES Cancelled, result callback is not called.
722  *
723  * @return #GNUNET_YES if the operation was found and removed,
724  *         #GNUNET_NO  if the operation was not found.
725  */
726 static int
727 op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
728            uint64_t op_id, int64_t result_code,
729            const void *data, uint16_t data_size, uint8_t cancel)
730 {
731   if (0 == op_id)
732     return GNUNET_NO;
733
734   struct OperationListItem *op = op_find (mgr, op_id);
735   if (NULL == op)
736   {
737     LOG (GNUNET_ERROR_TYPE_WARNING,
738          "Could not find operation #%" PRIu64 "\n", op_id);
739     return GNUNET_NO;
740   }
741
742   GNUNET_CONTAINER_DLL_remove (mgr->op_head, mgr->op_tail, op);
743
744   if (GNUNET_YES != cancel && NULL != op->result_cb)
745     op->result_cb (op->cls, result_code, data, data_size);
746
747   GNUNET_free (op);
748   return GNUNET_YES;
749 }
750
751
752 /**
753  * Call the result callback of an operation and remove it.
754  *
755  * @param mgr
756  *        Client manager connection.
757  * @param op_id
758  *        Operation ID.
759  * @param result_code
760  *        Result of the operation.
761  * @param data
762  *        Data result of the operation.
763  * @param data_size
764  *        Size of @a data.
765  *
766  * @return #GNUNET_YES if the operation was found and removed,
767  *         #GNUNET_NO  if the operation was not found.
768  */
769 int
770 GNUNET_CLIENT_MANAGER_op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
771                                  uint64_t op_id, int64_t result_code,
772                                  const void *data, uint16_t data_size)
773 {
774   LOG (GNUNET_ERROR_TYPE_DEBUG,
775        "%p Received result for operation #%" PRIu64 ": %" PRId64 " (size: %u)\n",
776        mgr, op_id, result_code, data_size);
777   return op_result (mgr, op_id, result_code, data, data_size, GNUNET_NO);
778 }
779
780
781 /**
782  * Cancel an operation.
783  *
784  * @param mgr
785  *        Client manager connection.
786  * @param op_id
787  *        Operation ID.
788  *
789  * @return #GNUNET_YES if the operation was found and removed,
790  *         #GNUNET_NO  if the operation was not found.
791  */
792 int
793 GNUNET_CLIENT_MANAGER_op_cancel (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
794                                  uint64_t op_id)
795 {
796   LOG (GNUNET_ERROR_TYPE_DEBUG,
797        "%p Cancelling operation #%" PRIu64  "\n", mgr, op_id);
798   return op_result (mgr, op_id, 0, NULL, 0, GNUNET_YES);
799 }