-rps: merge duplicate functions
[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    * 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   if (CS_ACTIVE == call->state)
489   {
490     call->speaker->disable_speaker (call->speaker->cls);
491     call->mic->disable_microphone (call->mic->cls);
492   }
493   if (NULL != call->mq)
494   {
495     GNUNET_MQ_destroy (call->mq);
496     call->mq = NULL;
497   }
498   if (NULL != call->client)
499   {
500     GNUNET_CLIENT_disconnect (call->client);
501     call->client = NULL;
502   }
503   call->state = CS_SHUTDOWN;
504   call->event_handler (call->event_handler_cls,
505                        GNUNET_CONVERSATION_EC_CALL_ERROR);
506   GNUNET_CONVERSATION_call_stop (call);
507 }
508
509
510 /**
511  * Call the phone of another user.
512  *
513  * @param cfg configuration to use, specifies our phone service
514  * @param caller_id identity of the caller
515  * @param zone_id GNS zone to use to resolve @a callee
516  * @param callee GNS name of the callee (used to locate the callee's record)
517  * @param speaker speaker to use (will be used automatically immediately once the
518  *        #GNUNET_CONVERSATION_EC_CALL_PICKED_UP event is generated); we will NOT generate
519  *        a ring tone on the speaker
520  * @param mic microphone to use (will be used automatically immediately once the
521  *        #GNUNET_CONVERSATION_EC_CALL_PICKED_UP event is generated)
522  * @param event_handler how to notify the owner of the phone about events
523  * @param event_handler_cls closure for @a event_handler
524  * @return handle for the call, NULL on hard errors
525  */
526 struct GNUNET_CONVERSATION_Call *
527 GNUNET_CONVERSATION_call_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
528                                 struct GNUNET_IDENTITY_Ego *caller_id,
529                                 struct GNUNET_IDENTITY_Ego *zone_id,
530                                 const char *callee,
531                                 struct GNUNET_SPEAKER_Handle *speaker,
532                                 struct GNUNET_MICROPHONE_Handle *mic,
533                                 GNUNET_CONVERSATION_CallEventHandler event_handler,
534                                 void *event_handler_cls)
535 {
536   static struct GNUNET_MQ_MessageHandler handlers[] =
537   {
538     { &handle_call_suspend,
539       GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
540       sizeof (struct ClientPhoneSuspendMessage) },
541     { &handle_call_resume,
542       GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
543       sizeof (struct ClientPhoneResumeMessage) },
544     { &handle_call_picked_up,
545       GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP,
546       sizeof (struct ClientPhonePickedupMessage) },
547     { &handle_call_hangup,
548       GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
549       sizeof (struct ClientPhoneHangupMessage) },
550     { &handle_call_audio_message,
551       GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
552       0 },
553     { NULL, 0, 0 }
554   };
555   struct GNUNET_CRYPTO_EcdsaPublicKey my_zone;
556   struct GNUNET_CONVERSATION_Call *call;
557
558   call = GNUNET_new (struct GNUNET_CONVERSATION_Call);
559   call->client = GNUNET_CLIENT_connect ("conversation", cfg);
560   if (NULL == call->client)
561   {
562     GNUNET_break (0);
563     GNUNET_free (call);
564     return NULL;
565   }
566   call->cfg = cfg;
567   call->caller_id = caller_id;
568   call->zone_id = zone_id;
569   call->callee = GNUNET_strdup (callee);
570   call->speaker = speaker;
571   call->mic = mic;
572   call->event_handler = event_handler;
573   call->event_handler_cls = event_handler_cls;
574   call->gns = GNUNET_GNS_connect (cfg);
575   if (NULL == call->gns)
576   {
577     GNUNET_CONVERSATION_call_stop (call);
578     return NULL;
579   }
580   call->mq = GNUNET_MQ_queue_for_connection_client (call->client,
581                                                     handlers,
582                                                     &call_error_handler,
583                                                     call);
584   call->state = CS_LOOKUP;
585   GNUNET_IDENTITY_ego_get_public_key (call->zone_id,
586                                       &my_zone);
587   call->gns_lookup = GNUNET_GNS_lookup (call->gns,
588                                         call->callee,
589                                         &my_zone,
590                                         GNUNET_GNSRECORD_TYPE_PHONE,
591                                         GNUNET_NO,
592                                         NULL /* FIXME: add shortening support */,
593                                         &handle_gns_response, call);
594   GNUNET_assert (NULL != call->gns_lookup);
595   return call;
596 }
597
598
599 /**
600  * Terminate a call.  The call may be ringing or ready at this time.
601  *
602  * @param call call to terminate
603  */
604 void
605 GNUNET_CONVERSATION_call_stop (struct GNUNET_CONVERSATION_Call *call)
606 {
607   if ( (NULL != call->speaker) &&
608        (CS_ACTIVE == call->state) )
609     call->speaker->disable_speaker (call->speaker->cls);
610   if ( (NULL != call->mic) &&
611        (CS_ACTIVE == call->state) )
612     call->mic->disable_microphone (call->mic->cls);
613   if (CS_SHUTDOWN != call->state)
614   {
615     call->state = CS_SHUTDOWN;
616   }
617   if (NULL != call->mq)
618   {
619     GNUNET_MQ_destroy (call->mq);
620     call->mq = NULL;
621   }
622   if (NULL != call->client)
623   {
624     GNUNET_CLIENT_disconnect (call->client);
625     call->client = NULL;
626   }
627   if (NULL != call->gns_lookup)
628   {
629     GNUNET_GNS_lookup_cancel (call->gns_lookup);
630     call->gns_lookup = NULL;
631   }
632   if (NULL != call->gns)
633   {
634     GNUNET_GNS_disconnect (call->gns);
635     call->gns = NULL;
636   }
637   GNUNET_free (call->callee);
638   GNUNET_free (call);
639 }
640
641
642 /**
643  * Pause a call.  Temporarily suspends the use of speaker and
644  * microphone.
645  *
646  * @param call call to pause
647  */
648 void
649 GNUNET_CONVERSATION_call_suspend (struct GNUNET_CONVERSATION_Call *call)
650 {
651   struct GNUNET_MQ_Envelope *e;
652   struct ClientPhoneSuspendMessage *suspend;
653
654   GNUNET_assert ( (CS_SUSPENDED_CALLEE == call->state) ||
655                   (CS_ACTIVE == call->state) );
656   if (CS_ACTIVE == call->state)
657   {
658     call->speaker->disable_speaker (call->speaker->cls);
659     call->mic->disable_microphone (call->mic->cls);
660   }
661   call->speaker = NULL;
662   call->mic = NULL;
663   e = GNUNET_MQ_msg (suspend, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
664   GNUNET_MQ_send (call->mq, e);
665   if (CS_SUSPENDED_CALLER == call->state)
666     call->state = CS_SUSPENDED_BOTH;
667   else
668     call->state = CS_SUSPENDED_CALLER;
669 }
670
671
672 /**
673  * Resumes a call after #GNUNET_CONVERSATION_call_suspend.
674  *
675  * @param call call to resume
676  * @param speaker speaker to use
677  *        a ring tone on the speaker
678  * @param mic microphone to use
679  */
680 void
681 GNUNET_CONVERSATION_call_resume (struct GNUNET_CONVERSATION_Call *call,
682                                  struct GNUNET_SPEAKER_Handle *speaker,
683                                  struct GNUNET_MICROPHONE_Handle *mic)
684 {
685   struct GNUNET_MQ_Envelope *e;
686   struct ClientPhoneResumeMessage *resume;
687
688   GNUNET_assert ( (CS_SUSPENDED_CALLER == call->state) ||
689                   (CS_SUSPENDED_BOTH == call->state) );
690   e = GNUNET_MQ_msg (resume, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
691   GNUNET_MQ_send (call->mq, e);
692   call->speaker = speaker;
693   call->mic = mic;
694   if (CS_SUSPENDED_CALLER == call->state)
695   {
696     call->state = CS_ACTIVE;
697     call->speaker->enable_speaker (call->speaker->cls);
698     call->mic->enable_microphone (call->mic->cls,
699                                   &transmit_call_audio,
700                                   call);
701   }
702   else
703   {
704     call->state = CS_SUSPENDED_CALLEE;
705   }
706 }
707
708
709 /* end of conversation_api_call.c */