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