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