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