2 This file is part of GNUnet
3 Copyright (C) 2013 Christian Grothoff (and other contributing authors)
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.
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.
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.
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
29 #include "gnunet_conversation_service.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "gnunet_gns_service.h"
32 #include "conversation.h"
36 * Possible states of the phone.
41 * We still need to lookup the callee.
46 * The call is ringing.
51 * The call is in an active conversation.
56 * The call is in termination.
61 * The call was suspended by the caller.
66 * The call was suspended by the callee.
71 * The call was suspended by both caller and callee.
78 * Handle for an outgoing call.
80 struct GNUNET_CONVERSATION_Call
86 const struct GNUNET_CONFIGURATION_Handle *cfg;
89 * Handle to talk with CONVERSATION service.
91 struct GNUNET_CLIENT_Connection *client;
94 * Our caller identity.
96 struct GNUNET_IDENTITY_Ego *caller_id;
99 * GNS zone to use to resolve @e callee.
101 struct GNUNET_IDENTITY_Ego *zone_id;
104 * Target callee as a GNS address/name.
111 struct GNUNET_SPEAKER_Handle *speaker;
116 struct GNUNET_MICROPHONE_Handle *mic;
119 * Function to call with events.
121 GNUNET_CONVERSATION_CallEventHandler event_handler;
124 * Closure for @e event_handler
126 void *event_handler_cls;
129 * Handle for transmitting to the CONVERSATION service.
131 struct GNUNET_MQ_Handle *mq;
134 * Connection to GNS (can be NULL).
136 struct GNUNET_GNS_Handle *gns;
139 * Active GNS lookup (or NULL).
141 struct GNUNET_GNS_LookupRequest *gns_lookup;
144 * Target phone record, only valid after the lookup is done.
146 struct GNUNET_CONVERSATION_PhoneRecord phone_record;
149 * State machine for the call.
151 enum CallState state;
157 * The call got disconnected, reconnect to the service.
159 * @param call call to reconnect
162 reconnect_call (struct GNUNET_CONVERSATION_Call *call);
166 * Process recorded audio data.
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
173 transmit_call_audio (void *cls,
177 struct GNUNET_CONVERSATION_Call *call = cls;
178 struct GNUNET_MQ_Envelope *e;
179 struct ClientAudioMessage *am;
181 GNUNET_assert (CS_ACTIVE == call->state);
182 e = GNUNET_MQ_msg_extra (am,
184 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO);
185 memcpy (&am[1], data, data_size);
186 GNUNET_MQ_send (call->mq, e);
191 * We received a `struct ClientPhoneSuspendMessage`
193 * @param cls the `struct GNUNET_CONVERSATION_Call`
194 * @param msg the message
197 handle_call_suspend (void *cls,
198 const struct GNUNET_MessageHeader *msg)
200 struct GNUNET_CONVERSATION_Call *call = cls;
206 reconnect_call (call);
210 reconnect_call (call);
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);
217 case CS_SUSPENDED_CALLEE:
218 case CS_SUSPENDED_BOTH:
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);
229 GNUNET_CONVERSATION_call_stop (call);
236 * We received a `struct ClientPhoneResumeMessage`
238 * @param cls the `struct GNUNET_CONVERSATION_Call`
239 * @param msg the message
242 handle_call_resume (void *cls,
243 const struct GNUNET_MessageHeader *msg)
245 struct GNUNET_CONVERSATION_Call *call = cls;
251 reconnect_call (call);
255 reconnect_call (call);
257 case CS_SUSPENDED_CALLER:
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,
266 call->event_handler (call->event_handler_cls,
267 GNUNET_CONVERSATION_EC_CALL_RESUMED);
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);
278 GNUNET_CONVERSATION_call_stop (call);
285 * We received a `struct ClientPhonePickedupMessage`
287 * @param cls the `struct GNUNET_CONVERSATION_Call`
288 * @param msg the message
291 handle_call_picked_up (void *cls,
292 const struct GNUNET_MessageHeader *msg)
294 struct GNUNET_CONVERSATION_Call *call = cls;
300 reconnect_call (call);
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,
308 call->event_handler (call->event_handler_cls,
309 GNUNET_CONVERSATION_EC_CALL_PICKED_UP);
311 case CS_SUSPENDED_CALLER:
312 case CS_SUSPENDED_CALLEE:
313 case CS_SUSPENDED_BOTH:
316 reconnect_call (call);
319 GNUNET_CONVERSATION_call_stop (call);
326 * We received a `struct ClientPhoneHangupMessage`
328 * @param cls the `struct GNUNET_CONVERSATION_Call`
329 * @param msg the message
332 handle_call_hangup (void *cls,
333 const struct GNUNET_MessageHeader *msg)
335 struct GNUNET_CONVERSATION_Call *call = cls;
336 GNUNET_CONVERSATION_CallEventHandler eh;
343 reconnect_call (call);
346 case CS_SUSPENDED_CALLER:
347 case CS_SUSPENDED_CALLEE:
348 case CS_SUSPENDED_BOTH:
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);
356 GNUNET_CONVERSATION_call_stop (call);
363 * We received a `struct ClientAudioMessage`
365 * @param cls the `struct GNUNET_CONVERSATION_Call`
366 * @param msg the message
369 handle_call_audio_message (void *cls,
370 const struct GNUNET_MessageHeader *msg)
372 struct GNUNET_CONVERSATION_Call *call = cls;
373 const struct ClientAudioMessage *am;
375 am = (const struct ClientAudioMessage *) msg;
380 reconnect_call (call);
384 reconnect_call (call);
386 case CS_SUSPENDED_CALLER:
387 /* can happen: we suspended, other peer did not yet
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 */
396 call->speaker->play (call->speaker->cls,
397 ntohs (msg->size) - sizeof (struct ClientAudioMessage),
401 GNUNET_CONVERSATION_call_stop (call);
408 * Iterator called on obtained result for a GNS lookup.
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
415 handle_gns_response (void *cls,
417 const struct GNUNET_GNSRECORD_Data *rd)
419 struct GNUNET_CONVERSATION_Call *call = cls;
421 struct GNUNET_MQ_Envelope *e;
422 struct ClientCallMessage *ccm;
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++)
429 if (GNUNET_GNSRECORD_TYPE_PHONE == rd[i].record_type)
431 if (rd[i].data_size != sizeof (struct GNUNET_CONVERSATION_PhoneRecord))
436 memcpy (&call->phone_record,
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);
451 call->event_handler (call->event_handler_cls,
452 GNUNET_CONVERSATION_EC_CALL_GNS_FAIL);
453 GNUNET_CONVERSATION_call_stop (call);
458 * We encountered an error talking with the conversation service.
460 * @param cls the `struct GNUNET_CONVERSATION_Call`
461 * @param error details about the error
464 call_error_handler (void *cls,
465 enum GNUNET_MQ_Error error)
467 struct GNUNET_CONVERSATION_Call *call = cls;
469 if (CS_SHUTDOWN == call->state)
471 GNUNET_CONVERSATION_call_stop (call);
474 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
475 _("Connection to conversation service lost, trying to reconnect\n"));
476 reconnect_call (call);
481 * The call got disconnected, reconnect to the service.
483 * @param call call to reconnect
486 reconnect_call (struct GNUNET_CONVERSATION_Call *call)
488 if (CS_ACTIVE == call->state)
490 call->speaker->disable_speaker (call->speaker->cls);
491 call->mic->disable_microphone (call->mic->cls);
493 if (NULL != call->mq)
495 GNUNET_MQ_destroy (call->mq);
498 if (NULL != call->client)
500 GNUNET_CLIENT_disconnect (call->client);
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);
511 * Call the phone of another user.
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
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,
531 struct GNUNET_SPEAKER_Handle *speaker,
532 struct GNUNET_MICROPHONE_Handle *mic,
533 GNUNET_CONVERSATION_CallEventHandler event_handler,
534 void *event_handler_cls)
536 static struct GNUNET_MQ_MessageHandler handlers[] =
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,
555 struct GNUNET_CRYPTO_EcdsaPublicKey my_zone;
556 struct GNUNET_CONVERSATION_Call *call;
558 call = GNUNET_new (struct GNUNET_CONVERSATION_Call);
559 call->client = GNUNET_CLIENT_connect ("conversation", cfg);
560 if (NULL == call->client)
567 call->caller_id = caller_id;
568 call->zone_id = zone_id;
569 call->callee = GNUNET_strdup (callee);
570 call->speaker = speaker;
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)
577 GNUNET_CONVERSATION_call_stop (call);
580 call->mq = GNUNET_MQ_queue_for_connection_client (call->client,
584 call->state = CS_LOOKUP;
585 GNUNET_IDENTITY_ego_get_public_key (call->zone_id,
587 call->gns_lookup = GNUNET_GNS_lookup (call->gns,
590 GNUNET_GNSRECORD_TYPE_PHONE,
592 NULL /* FIXME: add shortening support */,
593 &handle_gns_response, call);
594 GNUNET_assert (NULL != call->gns_lookup);
600 * Terminate a call. The call may be ringing or ready at this time.
602 * @param call call to terminate
605 GNUNET_CONVERSATION_call_stop (struct GNUNET_CONVERSATION_Call *call)
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)
615 call->state = CS_SHUTDOWN;
617 if (NULL != call->mq)
619 GNUNET_MQ_destroy (call->mq);
622 if (NULL != call->client)
624 GNUNET_CLIENT_disconnect (call->client);
627 if (NULL != call->gns_lookup)
629 GNUNET_GNS_lookup_cancel (call->gns_lookup);
630 call->gns_lookup = NULL;
632 if (NULL != call->gns)
634 GNUNET_GNS_disconnect (call->gns);
637 GNUNET_free (call->callee);
643 * Pause a call. Temporarily suspends the use of speaker and
646 * @param call call to pause
649 GNUNET_CONVERSATION_call_suspend (struct GNUNET_CONVERSATION_Call *call)
651 struct GNUNET_MQ_Envelope *e;
652 struct ClientPhoneSuspendMessage *suspend;
654 GNUNET_assert ( (CS_SUSPENDED_CALLEE == call->state) ||
655 (CS_ACTIVE == call->state) );
656 if (CS_ACTIVE == call->state)
658 call->speaker->disable_speaker (call->speaker->cls);
659 call->mic->disable_microphone (call->mic->cls);
661 call->speaker = 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;
668 call->state = CS_SUSPENDED_CALLER;
673 * Resumes a call after #GNUNET_CONVERSATION_call_suspend.
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
681 GNUNET_CONVERSATION_call_resume (struct GNUNET_CONVERSATION_Call *call,
682 struct GNUNET_SPEAKER_Handle *speaker,
683 struct GNUNET_MICROPHONE_Handle *mic)
685 struct GNUNET_MQ_Envelope *e;
686 struct ClientPhoneResumeMessage *resume;
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;
694 if (CS_SUSPENDED_CALLER == call->state)
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,
704 call->state = CS_SUSPENDED_CALLEE;
709 /* end of conversation_api_call.c */