-remove debug message
[oweals/gnunet.git] / src / conversation / conversation_api_call.c
1 /*
2    This file is part of GNUnet
3    Copyright (C) 2013, 2016 GNUnet e.V.
4
5    GNUnet is free software: you can redistribute it and/or modify it
6    under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License,
8    or (at your 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    Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20
21 /**
22  * @file conversation/conversation_api_call.c
23  * @brief call API to the conversation service
24  * @author Simon Dieterle
25  * @author Andreas Fuchs
26  * @author Christian Grothoff
27  */
28 #include "platform.h"
29 #include "gnunet_conversation_service.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "gnunet_gns_service.h"
32 #include "conversation.h"
33
34
35 /**
36  * Possible states of the phone.
37  */
38 enum CallState
39 {
40   /**
41    * We still need to lookup the callee.
42    */
43   CS_LOOKUP = 0,
44
45   /**
46    * The call is ringing.
47    */
48   CS_RINGING,
49
50   /**
51    * The call is in an active conversation.
52    */
53   CS_ACTIVE,
54
55   /**
56    * The call is in termination.
57    */
58   CS_SHUTDOWN,
59
60   /**
61    * The call was suspended by the caller.
62    */
63   CS_SUSPENDED_CALLER,
64
65   /**
66    * The call was suspended by the callee.
67    */
68   CS_SUSPENDED_CALLEE,
69
70   /**
71    * The call was suspended by both caller and callee.
72    */
73   CS_SUSPENDED_BOTH
74 };
75
76
77 /**
78  * Handle for an outgoing call.
79  */
80 struct GNUNET_CONVERSATION_Call
81 {
82   /**
83    * Our configuration.
84    */
85   const struct GNUNET_CONFIGURATION_Handle *cfg;
86
87   /**
88    * Our caller identity.
89    */
90   struct GNUNET_IDENTITY_Ego *caller_id;
91
92   /**
93    * Target callee as a GNS address/name.
94    */
95   char *callee;
96
97   /**
98    * Our speaker.
99    */
100   struct GNUNET_SPEAKER_Handle *speaker;
101
102   /**
103    * Our microphone.
104    */
105   struct GNUNET_MICROPHONE_Handle *mic;
106
107   /**
108    * Function to call with events.
109    */
110   GNUNET_CONVERSATION_CallEventHandler event_handler;
111
112   /**
113    * Closure for @e event_handler
114    */
115   void *event_handler_cls;
116
117   /**
118    * Handle for transmitting to the CONVERSATION service.
119    */
120   struct GNUNET_MQ_Handle *mq;
121
122   /**
123    * Connection to GNS (can be NULL).
124    */
125   struct GNUNET_GNS_Handle *gns;
126
127   /**
128    * Active GNS lookup (or NULL).
129    */
130   struct GNUNET_GNS_LookupWithTldRequest *gns_lookup;
131
132   /**
133    * Target phone record, only valid after the lookup is done.
134    */
135   struct GNUNET_CONVERSATION_PhoneRecord phone_record;
136
137   /**
138    * State machine for the call.
139    */
140   enum CallState state;
141 };
142
143
144 /**
145  * The call got disconnected, reconnect to the service.
146  *
147  * @param call call to reconnect
148  */
149 static void
150 fail_call (struct GNUNET_CONVERSATION_Call *call);
151
152
153 /**
154  * Process recorded audio data.
155  *
156  * @param cls closure with the `struct GNUNET_CONVERSATION_Call`
157  * @param data_size number of bytes in @a data
158  * @param data audio data to play
159  */
160 static void
161 transmit_call_audio (void *cls,
162                      size_t data_size,
163                      const void *data)
164 {
165   struct GNUNET_CONVERSATION_Call *call = cls;
166   struct GNUNET_MQ_Envelope *e;
167   struct ClientAudioMessage *am;
168
169   GNUNET_assert (CS_ACTIVE == call->state);
170   e = GNUNET_MQ_msg_extra (am,
171                            data_size,
172                            GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO);
173   GNUNET_memcpy (&am[1],
174                  data,
175                  data_size);
176   GNUNET_MQ_send (call->mq,
177                   e);
178 }
179
180
181 /**
182  * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND.
183  *
184  * @param cls the `struct GNUNET_CONVERSATION_Call`
185  * @param msg the message
186  */
187 static void
188 handle_call_suspend (void *cls,
189                      const struct ClientPhoneSuspendMessage *msg)
190 {
191   struct GNUNET_CONVERSATION_Call *call = cls;
192
193   (void) msg;
194   switch (call->state)
195   {
196   case CS_LOOKUP:
197     GNUNET_break (0);
198     fail_call (call);
199     break;
200
201   case CS_RINGING:
202     GNUNET_break_op (0);
203     fail_call (call);
204     break;
205
206   case CS_SUSPENDED_CALLER:
207     call->state = CS_SUSPENDED_BOTH;
208     call->event_handler (call->event_handler_cls,
209                          GNUNET_CONVERSATION_EC_CALL_SUSPENDED);
210     break;
211
212   case CS_SUSPENDED_CALLEE:
213   case CS_SUSPENDED_BOTH:
214     GNUNET_break_op (0);
215     break;
216
217   case CS_ACTIVE:
218     call->state = CS_SUSPENDED_CALLEE;
219     call->speaker->disable_speaker (call->speaker->cls);
220     call->mic->disable_microphone (call->mic->cls);
221     call->event_handler (call->event_handler_cls,
222                          GNUNET_CONVERSATION_EC_CALL_SUSPENDED);
223     break;
224
225   case CS_SHUTDOWN:
226     GNUNET_CONVERSATION_call_stop (call);
227     break;
228   }
229 }
230
231
232 /**
233  * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME.
234  *
235  * @param cls the `struct GNUNET_CONVERSATION_Call`
236  * @param msg the message
237  */
238 static void
239 handle_call_resume (void *cls,
240                     const struct ClientPhoneResumeMessage *msg)
241 {
242   struct GNUNET_CONVERSATION_Call *call = cls;
243
244   (void) msg;
245   switch (call->state)
246   {
247   case CS_LOOKUP:
248     GNUNET_break (0);
249     fail_call (call);
250     break;
251
252   case CS_RINGING:
253     GNUNET_break_op (0);
254     fail_call (call);
255     break;
256
257   case CS_SUSPENDED_CALLER:
258     GNUNET_break_op (0);
259     break;
260
261   case CS_SUSPENDED_CALLEE:
262     call->state = CS_ACTIVE;
263     call->speaker->enable_speaker (call->speaker->cls);
264     call->mic->enable_microphone (call->mic->cls,
265                                   &transmit_call_audio,
266                                   call);
267     call->event_handler (call->event_handler_cls,
268                          GNUNET_CONVERSATION_EC_CALL_RESUMED);
269     break;
270
271   case CS_SUSPENDED_BOTH:
272     call->state = CS_SUSPENDED_CALLER;
273     call->event_handler (call->event_handler_cls,
274                          GNUNET_CONVERSATION_EC_CALL_RESUMED);
275     break;
276
277   case CS_ACTIVE:
278     GNUNET_break_op (0);
279     break;
280
281   case CS_SHUTDOWN:
282     GNUNET_CONVERSATION_call_stop (call);
283     break;
284   }
285 }
286
287
288 /**
289  * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP.
290  *
291  * @param cls the `struct GNUNET_CONVERSATION_Call`
292  * @param msg the message
293  */
294 static void
295 handle_call_picked_up (void *cls,
296                        const struct ClientPhonePickedupMessage *msg)
297 {
298   struct GNUNET_CONVERSATION_Call *call = cls;
299
300   (void) msg;
301   switch (call->state)
302   {
303   case CS_LOOKUP:
304     GNUNET_break (0);
305     fail_call (call);
306     break;
307
308   case CS_RINGING:
309     call->state = CS_ACTIVE;
310     call->speaker->enable_speaker (call->speaker->cls);
311     call->mic->enable_microphone (call->mic->cls,
312                                   &transmit_call_audio,
313                                   call);
314     call->event_handler (call->event_handler_cls,
315                          GNUNET_CONVERSATION_EC_CALL_PICKED_UP);
316     break;
317
318   case CS_SUSPENDED_CALLER:
319   case CS_SUSPENDED_CALLEE:
320   case CS_SUSPENDED_BOTH:
321   case CS_ACTIVE:
322     GNUNET_break (0);
323     fail_call (call);
324     break;
325
326   case CS_SHUTDOWN:
327     GNUNET_CONVERSATION_call_stop (call);
328     break;
329   }
330 }
331
332
333 /**
334  * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_HANG_UP.
335  *
336  * @param cls the `struct GNUNET_CONVERSATION_Call`
337  * @param msg the message
338  */
339 static void
340 handle_call_hangup (void *cls,
341                     const struct ClientPhoneHangupMessage *msg)
342 {
343   struct GNUNET_CONVERSATION_Call *call = cls;
344   GNUNET_CONVERSATION_CallEventHandler eh;
345   void *eh_cls;
346
347   (void) msg;
348   switch (call->state)
349   {
350   case CS_LOOKUP:
351     GNUNET_break (0);
352     fail_call (call);
353     break;
354
355   case CS_RINGING:
356   case CS_SUSPENDED_CALLER:
357   case CS_SUSPENDED_CALLEE:
358   case CS_SUSPENDED_BOTH:
359   case CS_ACTIVE:
360     eh = call->event_handler;
361     eh_cls = call->event_handler_cls;
362     GNUNET_CONVERSATION_call_stop (call);
363     eh (eh_cls,
364         GNUNET_CONVERSATION_EC_CALL_HUNG_UP);
365     return;
366
367   case CS_SHUTDOWN:
368     GNUNET_CONVERSATION_call_stop (call);
369     break;
370   }
371 }
372
373
374 /**
375  * We received a `struct ClientAudioMessage`, check it is well-formed.
376  *
377  * @param cls the `struct GNUNET_CONVERSATION_Call`
378  * @param msg the message
379  * @return #GNUNET_OK (always well-formed)
380  */
381 static int
382 check_call_audio (void *cls,
383                   const struct ClientAudioMessage *am)
384 {
385   (void) cls;
386   (void) am;
387   /* any payload is OK */
388   return GNUNET_OK;
389 }
390
391
392 /**
393  * We received a `struct ClientAudioMessage`
394  *
395  * @param cls the `struct GNUNET_CONVERSATION_Call`
396  * @param msg the message
397  */
398 static void
399 handle_call_audio (void *cls,
400                    const struct ClientAudioMessage *am)
401 {
402   struct GNUNET_CONVERSATION_Call *call = cls;
403
404   switch (call->state)
405   {
406   case CS_LOOKUP:
407     GNUNET_break (0);
408     fail_call (call);
409     break;
410
411   case CS_RINGING:
412     GNUNET_break (0);
413     fail_call (call);
414     break;
415
416   case CS_SUSPENDED_CALLER:
417     /* can happen: we suspended, other peer did not yet
418        learn about this. */
419     break;
420
421   case CS_SUSPENDED_CALLEE:
422   case CS_SUSPENDED_BOTH:
423     /* can (rarely) also happen: other peer suspended, but cadet might
424        have had delayed data on the unreliable channel */
425     break;
426
427   case CS_ACTIVE:
428     call->speaker->play (call->speaker->cls,
429                          ntohs (am->header.size) - sizeof(struct
430                                                           ClientAudioMessage),
431                          &am[1]);
432     break;
433
434   case CS_SHUTDOWN:
435     GNUNET_CONVERSATION_call_stop (call);
436     break;
437   }
438 }
439
440
441 /**
442  * Iterator called on obtained result for a GNS lookup.
443  *
444  * @param cls closure with the `struct GNUNET_CONVERSATION_Call`
445  * @param was_gns #GNUNET_NO if name was not a GNS name
446  * @param rd_count number of records in @a rd
447  * @param rd the records in reply
448  */
449 static void
450 handle_gns_response (void *cls,
451                      int was_gns,
452                      uint32_t rd_count,
453                      const struct GNUNET_GNSRECORD_Data *rd)
454 {
455   struct GNUNET_CONVERSATION_Call *call = cls;
456   struct GNUNET_MQ_Envelope *e;
457   struct ClientCallMessage *ccm;
458
459   (void) was_gns;
460   GNUNET_break (NULL != call->gns_lookup);
461   GNUNET_break (CS_LOOKUP == call->state);
462   call->gns_lookup = NULL;
463   for (uint32_t i = 0; i < rd_count; i++)
464   {
465     if (GNUNET_GNSRECORD_TYPE_PHONE == rd[i].record_type)
466     {
467       if (rd[i].data_size != sizeof(struct GNUNET_CONVERSATION_PhoneRecord))
468       {
469         GNUNET_break_op (0);
470         continue;
471       }
472       GNUNET_memcpy (&call->phone_record,
473                      rd[i].data,
474                      rd[i].data_size);
475       e = GNUNET_MQ_msg (ccm,
476                          GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_CALL);
477       ccm->line_port = call->phone_record.line_port;
478       ccm->target = call->phone_record.peer;
479       ccm->caller_id = *GNUNET_IDENTITY_ego_get_private_key (call->caller_id);
480       GNUNET_MQ_send (call->mq,
481                       e);
482       call->state = CS_RINGING;
483       call->event_handler (call->event_handler_cls,
484                            GNUNET_CONVERSATION_EC_CALL_RINGING);
485       return;
486     }
487   }
488   /* not found */
489   call->event_handler (call->event_handler_cls,
490                        GNUNET_CONVERSATION_EC_CALL_GNS_FAIL);
491   GNUNET_CONVERSATION_call_stop (call);
492 }
493
494
495 /**
496  * We encountered an error talking with the conversation service.
497  *
498  * @param cls the `struct GNUNET_CONVERSATION_Call`
499  * @param error details about the error
500  */
501 static void
502 call_error_handler (void *cls,
503                     enum GNUNET_MQ_Error error)
504 {
505   struct GNUNET_CONVERSATION_Call *call = cls;
506
507   (void) error;
508   if (CS_SHUTDOWN == call->state)
509   {
510     GNUNET_CONVERSATION_call_stop (call);
511     return;
512   }
513   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
514               _ (
515                 "Connection to conversation service lost, trying to reconnect\n"));
516   fail_call (call);
517 }
518
519
520 /**
521  * The call got disconnected, destroy the handle.
522  *
523  * @param call call to reconnect
524  */
525 static void
526 fail_call (struct GNUNET_CONVERSATION_Call *call)
527 {
528   if (CS_ACTIVE == call->state)
529   {
530     call->speaker->disable_speaker (call->speaker->cls);
531     call->mic->disable_microphone (call->mic->cls);
532   }
533   if (NULL != call->mq)
534   {
535     GNUNET_MQ_destroy (call->mq);
536     call->mq = NULL;
537   }
538   call->state = CS_SHUTDOWN;
539   call->event_handler (call->event_handler_cls,
540                        GNUNET_CONVERSATION_EC_CALL_ERROR);
541   GNUNET_CONVERSATION_call_stop (call);
542 }
543
544
545 /**
546  * Call the phone of another user.
547  *
548  * @param cfg configuration to use, specifies our phone service
549  * @param caller_id identity of the caller
550  * @param callee GNS name of the callee (used to locate the callee's record)
551  * @param speaker speaker to use (will be used automatically immediately once the
552  *        #GNUNET_CONVERSATION_EC_CALL_PICKED_UP event is generated); we will NOT generate
553  *        a ring tone on the speaker
554  * @param mic microphone to use (will be used automatically immediately once the
555  *        #GNUNET_CONVERSATION_EC_CALL_PICKED_UP event is generated)
556  * @param event_handler how to notify the owner of the phone about events
557  * @param event_handler_cls closure for @a event_handler
558  * @return handle for the call, NULL on hard errors
559  */
560 struct GNUNET_CONVERSATION_Call *
561 GNUNET_CONVERSATION_call_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
562                                 struct GNUNET_IDENTITY_Ego *caller_id,
563                                 const char *callee,
564                                 struct GNUNET_SPEAKER_Handle *speaker,
565                                 struct GNUNET_MICROPHONE_Handle *mic,
566                                 GNUNET_CONVERSATION_CallEventHandler
567                                 event_handler,
568                                 void *event_handler_cls)
569 {
570   struct GNUNET_CONVERSATION_Call *call
571     = GNUNET_new (struct GNUNET_CONVERSATION_Call);
572   struct GNUNET_MQ_MessageHandler handlers[] = {
573     GNUNET_MQ_hd_fixed_size (call_suspend,
574                              GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
575                              struct ClientPhoneSuspendMessage,
576                              call),
577     GNUNET_MQ_hd_fixed_size (call_resume,
578                              GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
579                              struct ClientPhoneResumeMessage,
580                              call),
581     GNUNET_MQ_hd_fixed_size (call_picked_up,
582                              GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP,
583                              struct ClientPhonePickedupMessage,
584                              call),
585     GNUNET_MQ_hd_fixed_size (call_hangup,
586                              GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
587                              struct ClientPhoneHangupMessage,
588                              call),
589     GNUNET_MQ_hd_var_size (call_audio,
590                            GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
591                            struct ClientAudioMessage,
592                            call),
593     GNUNET_MQ_handler_end ()
594   };
595
596   call->mq = GNUNET_CLIENT_connect (cfg,
597                                     "conversation",
598                                     handlers,
599                                     &call_error_handler,
600                                     call);
601   if (NULL == call->mq)
602   {
603     GNUNET_break (0);
604     GNUNET_free (call);
605     return NULL;
606   }
607   call->cfg = cfg;
608   call->caller_id = caller_id;
609   call->callee = GNUNET_strdup (callee);
610   call->speaker = speaker;
611   call->mic = mic;
612   call->event_handler = event_handler;
613   call->event_handler_cls = event_handler_cls;
614   call->gns = GNUNET_GNS_connect (cfg);
615   if (NULL == call->gns)
616   {
617     GNUNET_CONVERSATION_call_stop (call);
618     return NULL;
619   }
620   call->state = CS_LOOKUP;
621   call->gns_lookup = GNUNET_GNS_lookup_with_tld (call->gns,
622                                                  call->callee,
623                                                  GNUNET_GNSRECORD_TYPE_PHONE,
624                                                  GNUNET_NO,
625                                                  &handle_gns_response,
626                                                  call);
627   if (NULL == call->gns_lookup)
628   {
629     GNUNET_CONVERSATION_call_stop (call);
630     return NULL;
631   }
632   return call;
633 }
634
635
636 /**
637  * Terminate a call.  The call may be ringing or ready at this time.
638  *
639  * @param call call to terminate
640  */
641 void
642 GNUNET_CONVERSATION_call_stop (struct GNUNET_CONVERSATION_Call *call)
643 {
644   if ((NULL != call->speaker) &&
645       (CS_ACTIVE == call->state))
646     call->speaker->disable_speaker (call->speaker->cls);
647   if ((NULL != call->mic) &&
648       (CS_ACTIVE == call->state))
649     call->mic->disable_microphone (call->mic->cls);
650   if (CS_SHUTDOWN != call->state)
651   {
652     call->state = CS_SHUTDOWN;
653   }
654   if (NULL != call->mq)
655   {
656     GNUNET_MQ_destroy (call->mq);
657     call->mq = NULL;
658   }
659   if (NULL != call->gns_lookup)
660   {
661     GNUNET_GNS_lookup_with_tld_cancel (call->gns_lookup);
662     call->gns_lookup = NULL;
663   }
664   if (NULL != call->gns)
665   {
666     GNUNET_GNS_disconnect (call->gns);
667     call->gns = NULL;
668   }
669   GNUNET_free (call->callee);
670   GNUNET_free (call);
671 }
672
673
674 /**
675  * Pause a call.  Temporarily suspends the use of speaker and
676  * microphone.
677  *
678  * @param call call to pause
679  */
680 void
681 GNUNET_CONVERSATION_call_suspend (struct GNUNET_CONVERSATION_Call *call)
682 {
683   struct GNUNET_MQ_Envelope *e;
684   struct ClientPhoneSuspendMessage *suspend;
685
686   GNUNET_assert ((CS_SUSPENDED_CALLEE == call->state) ||
687                  (CS_ACTIVE == call->state));
688   if (CS_ACTIVE == call->state)
689   {
690     call->speaker->disable_speaker (call->speaker->cls);
691     call->mic->disable_microphone (call->mic->cls);
692   }
693   call->speaker = NULL;
694   call->mic = NULL;
695   e = GNUNET_MQ_msg (suspend,
696                      GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
697   GNUNET_MQ_send (call->mq,
698                   e);
699   if (CS_SUSPENDED_CALLER == call->state)
700     call->state = CS_SUSPENDED_BOTH;
701   else
702     call->state = CS_SUSPENDED_CALLER;
703 }
704
705
706 /**
707  * Resumes a call after #GNUNET_CONVERSATION_call_suspend.
708  *
709  * @param call call to resume
710  * @param speaker speaker to use
711  *        a ring tone on the speaker
712  * @param mic microphone to use
713  */
714 void
715 GNUNET_CONVERSATION_call_resume (struct GNUNET_CONVERSATION_Call *call,
716                                  struct GNUNET_SPEAKER_Handle *speaker,
717                                  struct GNUNET_MICROPHONE_Handle *mic)
718 {
719   struct GNUNET_MQ_Envelope *e;
720   struct ClientPhoneResumeMessage *resume;
721
722   GNUNET_assert ((CS_SUSPENDED_CALLER == call->state) ||
723                  (CS_SUSPENDED_BOTH == call->state));
724   e = GNUNET_MQ_msg (resume, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
725   GNUNET_MQ_send (call->mq, e);
726   call->speaker = speaker;
727   call->mic = mic;
728   if (CS_SUSPENDED_CALLER == call->state)
729   {
730     call->state = CS_ACTIVE;
731     call->speaker->enable_speaker (call->speaker->cls);
732     call->mic->enable_microphone (call->mic->cls,
733                                   &transmit_call_audio,
734                                   call);
735   }
736   else
737   {
738     call->state = CS_SUSPENDED_CALLEE;
739   }
740 }
741
742
743 /* end of conversation_api_call.c */