use c99
[oweals/gnunet.git] / src / conversation / conversation_api_call.c
1 /*
2   This file is part of GNUnet
3   Copyright (C) 2013 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 reconnect_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   memcpy (&am[1], data, data_size);
181   GNUNET_MQ_send (call->mq, e);
182 }
183
184
185 /**
186  * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND.
187  *
188  * @param cls the `struct GNUNET_CONVERSATION_Call`
189  * @param msg the message
190  */
191 static void
192 handle_call_suspend (void *cls,
193                      const struct ClientPhoneSuspendMessage *msg)
194 {
195   struct GNUNET_CONVERSATION_Call *call = cls;
196
197   switch (call->state)
198   {
199   case CS_LOOKUP:
200     GNUNET_break (0);
201     reconnect_call (call);
202     break;
203   case CS_RINGING:
204     GNUNET_break_op (0);
205     reconnect_call (call);
206     break;
207   case CS_SUSPENDED_CALLER:
208     call->state = CS_SUSPENDED_BOTH;
209     call->event_handler (call->event_handler_cls,
210                          GNUNET_CONVERSATION_EC_CALL_SUSPENDED);
211     break;
212   case CS_SUSPENDED_CALLEE:
213   case CS_SUSPENDED_BOTH:
214     GNUNET_break_op (0);
215     break;
216   case CS_ACTIVE:
217     call->state = CS_SUSPENDED_CALLEE;
218     call->speaker->disable_speaker (call->speaker->cls);
219     call->mic->disable_microphone (call->mic->cls);
220     call->event_handler (call->event_handler_cls,
221                          GNUNET_CONVERSATION_EC_CALL_SUSPENDED);
222     break;
223   case CS_SHUTDOWN:
224     GNUNET_CONVERSATION_call_stop (call);
225     break;
226   }
227 }
228
229
230 /**
231  * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME.
232  *
233  * @param cls the `struct GNUNET_CONVERSATION_Call`
234  * @param msg the message
235  */
236 static void
237 handle_call_resume (void *cls,
238                      const struct ClientPhoneResumeMessage *msg)
239 {
240   struct GNUNET_CONVERSATION_Call *call = cls;
241
242   switch (call->state)
243   {
244   case CS_LOOKUP:
245     GNUNET_break (0);
246     reconnect_call (call);
247     break;
248   case CS_RINGING:
249     GNUNET_break_op (0);
250     reconnect_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   switch (call->state)
292   {
293   case CS_LOOKUP:
294     GNUNET_break (0);
295     reconnect_call (call);
296     break;
297   case CS_RINGING:
298     call->state = CS_ACTIVE;
299     call->speaker->enable_speaker (call->speaker->cls);
300     call->mic->enable_microphone (call->mic->cls,
301                                   &transmit_call_audio,
302                                   call);
303     call->event_handler (call->event_handler_cls,
304                          GNUNET_CONVERSATION_EC_CALL_PICKED_UP);
305     break;
306   case CS_SUSPENDED_CALLER:
307   case CS_SUSPENDED_CALLEE:
308   case CS_SUSPENDED_BOTH:
309   case CS_ACTIVE:
310     GNUNET_break (0);
311     reconnect_call (call);
312     break;
313   case CS_SHUTDOWN:
314     GNUNET_CONVERSATION_call_stop (call);
315     break;
316   }
317 }
318
319
320 /**
321  * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_HANG_UP.
322  *
323  * @param cls the `struct GNUNET_CONVERSATION_Call`
324  * @param msg the message
325  */
326 static void
327 handle_call_hangup (void *cls,
328                     const struct ClientPhoneHangupMessage *msg)
329 {
330   struct GNUNET_CONVERSATION_Call *call = cls;
331   GNUNET_CONVERSATION_CallEventHandler eh;
332   void *eh_cls;
333
334   switch (call->state)
335   {
336   case CS_LOOKUP:
337     GNUNET_break (0);
338     reconnect_call (call);
339     break;
340   case CS_RINGING:
341   case CS_SUSPENDED_CALLER:
342   case CS_SUSPENDED_CALLEE:
343   case CS_SUSPENDED_BOTH:
344   case CS_ACTIVE:
345     eh = call->event_handler;
346     eh_cls = call->event_handler_cls;
347     GNUNET_CONVERSATION_call_stop (call);
348     eh (eh_cls, GNUNET_CONVERSATION_EC_CALL_HUNG_UP);
349     return;
350   case CS_SHUTDOWN:
351     GNUNET_CONVERSATION_call_stop (call);
352     break;
353   }
354 }
355
356
357 /**
358  * We received a `struct ClientAudioMessage`, check it is well-formed.
359  *
360  * @param cls the `struct GNUNET_CONVERSATION_Call`
361  * @param msg the message
362  * @return #GNUNET_OK (always well-formed)
363  */
364 static int
365 check_call_audio (void *cls,
366                   const struct ClientAudioMessage *am)
367 {
368   /* any payload is OK */
369   return GNUNET_OK;
370 }
371
372
373 /**
374  * We received a `struct ClientAudioMessage`
375  *
376  * @param cls the `struct GNUNET_CONVERSATION_Call`
377  * @param msg the message
378  */
379 static void
380 handle_call_audio (void *cls,
381                    const struct ClientAudioMessage *am)
382 {
383   struct GNUNET_CONVERSATION_Call *call = cls;
384
385   switch (call->state)
386   {
387   case CS_LOOKUP:
388     GNUNET_break (0);
389     reconnect_call (call);
390     break;
391   case CS_RINGING:
392     GNUNET_break (0);
393     reconnect_call (call);
394     break;
395   case CS_SUSPENDED_CALLER:
396     /* can happen: we suspended, other peer did not yet
397        learn about this. */
398     break;
399   case CS_SUSPENDED_CALLEE:
400   case CS_SUSPENDED_BOTH:
401     /* can (rarely) also happen: other peer suspended, but cadet might
402        have had delayed data on the unreliable channel */
403     break;
404   case CS_ACTIVE:
405     call->speaker->play (call->speaker->cls,
406                          ntohs (am->header.size) - sizeof (struct ClientAudioMessage),
407                          &am[1]);
408     break;
409   case CS_SHUTDOWN:
410     GNUNET_CONVERSATION_call_stop (call);
411     break;
412   }
413 }
414
415
416 /**
417  * Iterator called on obtained result for a GNS lookup.
418  *
419  * @param cls closure with the `struct GNUNET_CONVERSATION_Call`
420  * @param rd_count number of records in @a rd
421  * @param rd the records in reply
422  */
423 static void
424 handle_gns_response (void *cls,
425                      uint32_t rd_count,
426                      const struct GNUNET_GNSRECORD_Data *rd)
427 {
428   struct GNUNET_CONVERSATION_Call *call = cls;
429   uint32_t i;
430   struct GNUNET_MQ_Envelope *e;
431   struct ClientCallMessage *ccm;
432
433   GNUNET_break (NULL != call->gns_lookup);
434   GNUNET_break (CS_LOOKUP == call->state);
435   call->gns_lookup = NULL;
436   for (i=0;i<rd_count;i++)
437   {
438     if (GNUNET_GNSRECORD_TYPE_PHONE == rd[i].record_type)
439     {
440       if (rd[i].data_size != sizeof (struct GNUNET_CONVERSATION_PhoneRecord))
441       {
442         GNUNET_break_op (0);
443         continue;
444       }
445       memcpy (&call->phone_record,
446               rd[i].data,
447               rd[i].data_size);
448       e = GNUNET_MQ_msg (ccm, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_CALL);
449       ccm->line = call->phone_record.line;
450       ccm->target = call->phone_record.peer;
451       ccm->caller_id = *GNUNET_IDENTITY_ego_get_private_key (call->caller_id);
452       GNUNET_MQ_send (call->mq, e);
453       call->state = CS_RINGING;
454       call->event_handler (call->event_handler_cls,
455                            GNUNET_CONVERSATION_EC_CALL_RINGING);
456       return;
457     }
458   }
459   /* not found */
460   call->event_handler (call->event_handler_cls,
461                        GNUNET_CONVERSATION_EC_CALL_GNS_FAIL);
462   GNUNET_CONVERSATION_call_stop (call);
463 }
464
465
466 /**
467  * We encountered an error talking with the conversation service.
468  *
469  * @param cls the `struct GNUNET_CONVERSATION_Call`
470  * @param error details about the error
471  */
472 static void
473 call_error_handler (void *cls,
474                     enum GNUNET_MQ_Error error)
475 {
476   struct GNUNET_CONVERSATION_Call *call = cls;
477
478   if (CS_SHUTDOWN == call->state)
479   {
480     GNUNET_CONVERSATION_call_stop (call);
481     return;
482   }
483   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
484               _("Connection to conversation service lost, trying to reconnect\n"));
485   reconnect_call (call);
486 }
487
488
489 /**
490  * The call got disconnected, reconnect to the service.
491  *
492  * @param call call to reconnect
493  */
494 static void
495 reconnect_call (struct GNUNET_CONVERSATION_Call *call)
496 {
497   if (CS_ACTIVE == call->state)
498   {
499     call->speaker->disable_speaker (call->speaker->cls);
500     call->mic->disable_microphone (call->mic->cls);
501   }
502   if (NULL != call->mq)
503   {
504     GNUNET_MQ_destroy (call->mq);
505     call->mq = NULL;
506   }
507   call->state = CS_SHUTDOWN;
508   call->event_handler (call->event_handler_cls,
509                        GNUNET_CONVERSATION_EC_CALL_ERROR);
510   GNUNET_CONVERSATION_call_stop (call);
511 }
512
513
514 /**
515  * Call the phone of another user.
516  *
517  * @param cfg configuration to use, specifies our phone service
518  * @param caller_id identity of the caller
519  * @param zone_id GNS zone to use to resolve @a callee
520  * @param callee GNS name of the callee (used to locate the callee's record)
521  * @param speaker speaker to use (will be used automatically immediately once the
522  *        #GNUNET_CONVERSATION_EC_CALL_PICKED_UP event is generated); we will NOT generate
523  *        a ring tone on the speaker
524  * @param mic microphone to use (will be used automatically immediately once the
525  *        #GNUNET_CONVERSATION_EC_CALL_PICKED_UP event is generated)
526  * @param event_handler how to notify the owner of the phone about events
527  * @param event_handler_cls closure for @a event_handler
528  * @return handle for the call, NULL on hard errors
529  */
530 struct GNUNET_CONVERSATION_Call *
531 GNUNET_CONVERSATION_call_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
532                                 struct GNUNET_IDENTITY_Ego *caller_id,
533                                 struct GNUNET_IDENTITY_Ego *zone_id,
534                                 const char *callee,
535                                 struct GNUNET_SPEAKER_Handle *speaker,
536                                 struct GNUNET_MICROPHONE_Handle *mic,
537                                 GNUNET_CONVERSATION_CallEventHandler event_handler,
538                                 void *event_handler_cls)
539 {
540   GNUNET_MQ_hd_fixed_size (call_suspend,
541                            GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
542                            struct ClientPhoneSuspendMessage);
543   GNUNET_MQ_hd_fixed_size (call_resume,
544                            GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
545                            struct ClientPhoneResumeMessage);
546   GNUNET_MQ_hd_fixed_size (call_picked_up,
547                            GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP,
548                            struct ClientPhonePickedupMessage);
549   GNUNET_MQ_hd_fixed_size (call_hangup,
550                            GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
551                            struct ClientPhoneHangupMessage);
552   GNUNET_MQ_hd_var_size (call_audio,
553                          GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
554                          struct ClientAudioMessage);
555   struct GNUNET_CONVERSATION_Call *call
556     = GNUNET_new (struct GNUNET_CONVERSATION_Call);
557   struct GNUNET_MQ_MessageHandler handlers[] = {
558     make_call_suspend_handler (call),
559     make_call_resume_handler (call),
560     make_call_picked_up_handler (call),
561     make_call_hangup_handler (call),
562     make_call_audio_handler (call),
563     GNUNET_MQ_handler_end ()
564   };
565   struct GNUNET_CRYPTO_EcdsaPublicKey my_zone;
566
567   call->mq = GNUNET_CLIENT_connecT (cfg,
568                                     "conversation",
569                                     handlers,
570                                     &call_error_handler,
571                                     call);
572   if (NULL == call->mq)
573   {
574     GNUNET_break (0);
575     GNUNET_free (call);
576     return NULL;
577   }
578   call->cfg = cfg;
579   call->caller_id = caller_id;
580   call->zone_id = zone_id;
581   call->callee = GNUNET_strdup (callee);
582   call->speaker = speaker;
583   call->mic = mic;
584   call->event_handler = event_handler;
585   call->event_handler_cls = event_handler_cls;
586   call->gns = GNUNET_GNS_connect (cfg);
587   if (NULL == call->gns)
588   {
589     GNUNET_CONVERSATION_call_stop (call);
590     return NULL;
591   }
592   call->state = CS_LOOKUP;
593   GNUNET_IDENTITY_ego_get_public_key (call->zone_id,
594                                       &my_zone);
595   call->gns_lookup = GNUNET_GNS_lookup (call->gns,
596                                         call->callee,
597                                         &my_zone,
598                                         GNUNET_GNSRECORD_TYPE_PHONE,
599                                         GNUNET_NO,
600                                         NULL /* FIXME: add shortening support */,
601                                         &handle_gns_response, call);
602   GNUNET_assert (NULL != call->gns_lookup);
603   return call;
604 }
605
606
607 /**
608  * Terminate a call.  The call may be ringing or ready at this time.
609  *
610  * @param call call to terminate
611  */
612 void
613 GNUNET_CONVERSATION_call_stop (struct GNUNET_CONVERSATION_Call *call)
614 {
615   if ( (NULL != call->speaker) &&
616        (CS_ACTIVE == call->state) )
617     call->speaker->disable_speaker (call->speaker->cls);
618   if ( (NULL != call->mic) &&
619        (CS_ACTIVE == call->state) )
620     call->mic->disable_microphone (call->mic->cls);
621   if (CS_SHUTDOWN != call->state)
622   {
623     call->state = CS_SHUTDOWN;
624   }
625   if (NULL != call->mq)
626   {
627     GNUNET_MQ_destroy (call->mq);
628     call->mq = NULL;
629   }
630   if (NULL != call->gns_lookup)
631   {
632     GNUNET_GNS_lookup_cancel (call->gns_lookup);
633     call->gns_lookup = NULL;
634   }
635   if (NULL != call->gns)
636   {
637     GNUNET_GNS_disconnect (call->gns);
638     call->gns = NULL;
639   }
640   GNUNET_free (call->callee);
641   GNUNET_free (call);
642 }
643
644
645 /**
646  * Pause a call.  Temporarily suspends the use of speaker and
647  * microphone.
648  *
649  * @param call call to pause
650  */
651 void
652 GNUNET_CONVERSATION_call_suspend (struct GNUNET_CONVERSATION_Call *call)
653 {
654   struct GNUNET_MQ_Envelope *e;
655   struct ClientPhoneSuspendMessage *suspend;
656
657   GNUNET_assert ( (CS_SUSPENDED_CALLEE == call->state) ||
658                   (CS_ACTIVE == call->state) );
659   if (CS_ACTIVE == call->state)
660   {
661     call->speaker->disable_speaker (call->speaker->cls);
662     call->mic->disable_microphone (call->mic->cls);
663   }
664   call->speaker = NULL;
665   call->mic = NULL;
666   e = GNUNET_MQ_msg (suspend, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
667   GNUNET_MQ_send (call->mq, e);
668   if (CS_SUSPENDED_CALLER == call->state)
669     call->state = CS_SUSPENDED_BOTH;
670   else
671     call->state = CS_SUSPENDED_CALLER;
672 }
673
674
675 /**
676  * Resumes a call after #GNUNET_CONVERSATION_call_suspend.
677  *
678  * @param call call to resume
679  * @param speaker speaker to use
680  *        a ring tone on the speaker
681  * @param mic microphone to use
682  */
683 void
684 GNUNET_CONVERSATION_call_resume (struct GNUNET_CONVERSATION_Call *call,
685                                  struct GNUNET_SPEAKER_Handle *speaker,
686                                  struct GNUNET_MICROPHONE_Handle *mic)
687 {
688   struct GNUNET_MQ_Envelope *e;
689   struct ClientPhoneResumeMessage *resume;
690
691   GNUNET_assert ( (CS_SUSPENDED_CALLER == call->state) ||
692                   (CS_SUSPENDED_BOTH == call->state) );
693   e = GNUNET_MQ_msg (resume, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
694   GNUNET_MQ_send (call->mq, e);
695   call->speaker = speaker;
696   call->mic = mic;
697   if (CS_SUSPENDED_CALLER == call->state)
698   {
699     call->state = CS_ACTIVE;
700     call->speaker->enable_speaker (call->speaker->cls);
701     call->mic->enable_microphone (call->mic->cls,
702                                   &transmit_call_audio,
703                                   call);
704   }
705   else
706   {
707     call->state = CS_SUSPENDED_CALLEE;
708   }
709 }
710
711
712 /* end of conversation_api_call.c */