2 This file is part of GNUnet
3 (C) 2013, 2014 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.c
23 * @brief phone and caller 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 a caller.
41 * The phone is ringing (user knows about incoming call).
46 * The phone is in an active conversation.
51 * We suspended the conversation.
56 * Caller suspended the conversation.
61 * Both sides suspended the conversation.
69 * A caller is the handle we have for an incoming call.
71 struct GNUNET_CONVERSATION_Caller
75 * We keep all callers in a DLL.
77 struct GNUNET_CONVERSATION_Caller *next;
80 * We keep all callers in a DLL.
82 struct GNUNET_CONVERSATION_Caller *prev;
87 struct GNUNET_CONVERSATION_Phone *phone;
90 * Function to call for phone events.
92 GNUNET_CONVERSATION_CallerEventHandler event_handler;
95 * Closure for @e event_handler
97 void *event_handler_cls;
100 * Speaker, or NULL if none is attached.
102 struct GNUNET_SPEAKER_Handle *speaker;
105 * Microphone, or NULL if none is attached.
107 struct GNUNET_MICROPHONE_Handle *mic;
110 * Identity of the person calling us.
112 struct GNUNET_CRYPTO_EcdsaPublicKey caller_id;
115 * Internal handle to identify the caller with the service.
120 * State machine for the phone.
122 enum CallerState state;
128 * Possible states of a phone.
133 * We still need to register the phone.
138 * We are waiting for calls.
146 * A phone is a device that can ring to signal an incoming call and
147 * that you can pick up to answer the call and hang up to terminate
148 * the call. You can also hang up a ringing phone immediately
149 * (without picking it up) to stop it from ringing. Phones have
150 * caller ID. You can ask the phone for its record and make that
151 * record available (via GNS) to enable others to call you.
152 * Multiple phones maybe connected to the same line (the line is
153 * something rather internal to a phone and not obvious from it).
154 * You can only have one conversation per phone at any time.
156 struct GNUNET_CONVERSATION_Phone
161 const struct GNUNET_CONFIGURATION_Handle *cfg;
164 * Handle to talk with CONVERSATION service.
166 struct GNUNET_CLIENT_Connection *client;
169 * We keep all callers in a DLL.
171 struct GNUNET_CONVERSATION_Caller *caller_head;
174 * We keep all callers in a DLL.
176 struct GNUNET_CONVERSATION_Caller *caller_tail;
179 * Function to call for phone events.
181 GNUNET_CONVERSATION_PhoneEventHandler event_handler;
184 * Closure for @e event_handler
186 void *event_handler_cls;
189 * Connection to NAMESTORE (for reverse lookup).
191 struct GNUNET_NAMESTORE_Handle *ns;
194 * Handle for transmitting to the CONVERSATION service.
196 struct GNUNET_MQ_Handle *mq;
199 * This phone's record.
201 struct GNUNET_CONVERSATION_PhoneRecord my_record;
206 struct GNUNET_CRYPTO_EcdsaPrivateKey my_zone;
209 * State machine for the phone.
211 enum PhoneState state;
217 * The phone got disconnected, reconnect to the service.
219 * @param phone phone to reconnect
222 reconnect_phone (struct GNUNET_CONVERSATION_Phone *phone);
226 * Process recorded audio data.
228 * @param cls closure with the `struct GNUNET_CONVERSATION_Caller`
229 * @param data_size number of bytes in @a data
230 * @param data audio data to play
233 transmit_phone_audio (void *cls,
237 struct GNUNET_CONVERSATION_Caller *caller = cls;
238 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
239 struct GNUNET_MQ_Envelope *e;
240 struct ClientAudioMessage *am;
242 e = GNUNET_MQ_msg_extra (am,
244 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO);
245 am->cid = caller->cid;
246 memcpy (&am[1], data, data_size);
247 GNUNET_MQ_send (phone->mq, e);
252 * We received a `struct ClientPhoneRingMessage`
254 * @param cls the `struct GNUNET_CONVERSATION_Phone`
255 * @param msg the message
258 handle_phone_ring (void *cls,
259 const struct GNUNET_MessageHeader *msg)
261 struct GNUNET_CONVERSATION_Phone *phone = cls;
262 const struct ClientPhoneRingMessage *ring;
263 struct GNUNET_CONVERSATION_Caller *caller;
265 ring = (const struct ClientPhoneRingMessage *) msg;
266 switch (phone->state)
272 caller = GNUNET_new (struct GNUNET_CONVERSATION_Caller);
273 caller->phone = phone;
274 GNUNET_CONTAINER_DLL_insert (phone->caller_head,
277 caller->caller_id = ring->caller_id;
278 caller->cid = ring->cid;
279 caller->state = CS_RINGING;
280 phone->event_handler (phone->event_handler_cls,
281 GNUNET_CONVERSATION_EC_PHONE_RING,
290 * We received a `struct ClientPhoneHangupMessage`.
292 * @param cls the `struct GNUNET_CONVERSATION_Phone *`
293 * @param msg the message
296 handle_phone_hangup (void *cls,
297 const struct GNUNET_MessageHeader *msg)
299 struct GNUNET_CONVERSATION_Phone *phone = cls;
300 const struct ClientPhoneHangupMessage *hang;
301 struct GNUNET_CONVERSATION_Caller *caller;
303 hang = (const struct ClientPhoneHangupMessage *) msg;
304 for (caller = phone->caller_head; NULL != caller; caller = caller->next)
305 if (hang->cid == caller->cid)
309 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
310 "Received HANG_UP message for unknown caller ID %u\n",
311 (unsigned int) hang->cid);
315 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
316 "Received HANG_UP message, terminating call with `%s'\n",
317 GNUNET_GNSRECORD_pkey_to_zkey (&caller->caller_id));
318 switch (caller->state)
321 phone->event_handler (phone->event_handler_cls,
322 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
327 caller->speaker->disable_speaker (caller->speaker->cls);
328 caller->mic->disable_microphone (caller->mic->cls);
329 phone->event_handler (phone->event_handler_cls,
330 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
334 case CS_CALLEE_SUSPENDED:
335 case CS_CALLER_SUSPENDED:
336 case CS_BOTH_SUSPENDED:
337 phone->event_handler (phone->event_handler_cls,
338 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
343 GNUNET_CONTAINER_DLL_remove (phone->caller_head,
346 GNUNET_free (caller);
351 * We received a `struct ClientPhoneSuspendMessage`.
353 * @param cls the `struct GNUNET_CONVERSATION_Phone`
354 * @param msg the message
357 handle_phone_suspend (void *cls,
358 const struct GNUNET_MessageHeader *msg)
360 struct GNUNET_CONVERSATION_Phone *phone = cls;
361 struct GNUNET_CONVERSATION_Caller *caller;
362 const struct ClientPhoneSuspendMessage *suspend;
364 suspend = (const struct ClientPhoneSuspendMessage *) msg;
365 for (caller = phone->caller_head; NULL != caller; caller = caller->next)
366 if (suspend->cid == caller->cid)
370 switch (caller->state)
376 caller->state = CS_CALLER_SUSPENDED;
377 caller->speaker->disable_speaker (caller->speaker->cls);
378 caller->mic->disable_microphone (caller->mic->cls);
379 caller->event_handler (caller->event_handler_cls,
380 GNUNET_CONVERSATION_EC_CALLER_SUSPEND);
382 case CS_CALLEE_SUSPENDED:
383 caller->state = CS_BOTH_SUSPENDED;
384 caller->event_handler (caller->event_handler_cls,
385 GNUNET_CONVERSATION_EC_CALLER_SUSPEND);
387 case CS_CALLER_SUSPENDED:
388 case CS_BOTH_SUSPENDED:
396 * We received a `struct ClientPhoneResumeMessage`.
398 * @param cls the `struct GNUNET_CONVERSATION_Phone`
399 * @param msg the message
402 handle_phone_resume (void *cls,
403 const struct GNUNET_MessageHeader *msg)
405 struct GNUNET_CONVERSATION_Phone *phone = cls;
406 struct GNUNET_CONVERSATION_Caller *caller;
407 const struct ClientPhoneResumeMessage *resume;
409 resume = (const struct ClientPhoneResumeMessage *) msg;
410 for (caller = phone->caller_head; NULL != caller; caller = caller->next)
411 if (resume->cid == caller->cid)
415 switch (caller->state)
421 case CS_CALLEE_SUSPENDED:
424 case CS_CALLER_SUSPENDED:
425 caller->state = CS_ACTIVE;
426 caller->speaker->enable_speaker (caller->speaker->cls);
427 caller->mic->enable_microphone (caller->mic->cls,
428 &transmit_phone_audio,
430 caller->event_handler (caller->event_handler_cls,
431 GNUNET_CONVERSATION_EC_CALLER_RESUME);
433 case CS_BOTH_SUSPENDED:
434 caller->state = CS_CALLEE_SUSPENDED;
435 caller->event_handler (caller->event_handler_cls,
436 GNUNET_CONVERSATION_EC_CALLER_RESUME);
443 * We received a `struct ClientAudioMessage`
445 * @param cls the `struct GNUNET_CONVERSATION_Phone`
446 * @param msg the message
449 handle_phone_audio_message (void *cls,
450 const struct GNUNET_MessageHeader *msg)
452 struct GNUNET_CONVERSATION_Phone *phone = cls;
453 const struct ClientAudioMessage *am;
454 struct GNUNET_CONVERSATION_Caller *caller;
456 am = (const struct ClientAudioMessage *) msg;
457 for (caller = phone->caller_head; NULL != caller; caller = caller->next)
458 if (am->cid == caller->cid)
462 switch (caller->state)
468 caller->speaker->play (caller->speaker->cls,
469 ntohs (msg->size) - sizeof (struct ClientAudioMessage),
472 case CS_CALLEE_SUSPENDED:
473 case CS_CALLER_SUSPENDED:
474 case CS_BOTH_SUSPENDED:
481 * We encountered an error talking with the conversation service.
483 * @param cls the `struct GNUNET_CONVERSATION_Phone`
484 * @param error details about the error
487 phone_error_handler (void *cls,
488 enum GNUNET_MQ_Error error)
490 struct GNUNET_CONVERSATION_Phone *phone = cls;
492 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
493 _("Connection to conversation service lost, trying to reconnect\n"));
494 reconnect_phone (phone);
499 * Clean up all callers of the given phone.
501 * @param phone phone to clean up callers for
504 clean_up_callers (struct GNUNET_CONVERSATION_Phone *phone)
506 struct GNUNET_CONVERSATION_Caller *caller;
508 while (NULL != (caller = phone->caller_head))
510 /* make sure mic/speaker are disabled *before* callback */
511 if (CS_ACTIVE == caller->state)
513 caller->speaker->disable_speaker (caller->speaker->cls);
514 caller->mic->disable_microphone (caller->mic->cls);
515 caller->state = CS_CALLER_SUSPENDED;
517 phone->event_handler (phone->event_handler_cls,
518 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
521 GNUNET_CONVERSATION_caller_hang_up (caller);
527 * The phone got disconnected, reconnect to the service.
529 * @param phone phone to reconnect
532 reconnect_phone (struct GNUNET_CONVERSATION_Phone *phone)
534 static struct GNUNET_MQ_MessageHandler handlers[] =
536 { &handle_phone_ring,
537 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RING,
538 sizeof (struct ClientPhoneRingMessage) },
539 { &handle_phone_hangup,
540 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
541 sizeof (struct ClientPhoneHangupMessage) },
542 { &handle_phone_suspend,
543 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
544 sizeof (struct ClientPhoneSuspendMessage) },
545 { &handle_phone_resume,
546 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
547 sizeof (struct ClientPhoneResumeMessage) },
548 { &handle_phone_audio_message,
549 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
553 struct GNUNET_MQ_Envelope *e;
554 struct ClientPhoneRegisterMessage *reg;
556 clean_up_callers (phone);
557 if (NULL != phone->mq)
559 GNUNET_MQ_destroy (phone->mq);
562 if (NULL != phone->client)
564 GNUNET_CLIENT_disconnect (phone->client);
565 phone->client = NULL;
567 phone->state = PS_REGISTER;
568 phone->client = GNUNET_CLIENT_connect ("conversation", phone->cfg);
569 if (NULL == phone->client)
571 phone->mq = GNUNET_MQ_queue_for_connection_client (phone->client,
573 &phone_error_handler,
575 e = GNUNET_MQ_msg (reg, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_REGISTER);
576 reg->line = phone->my_record.line;
577 GNUNET_MQ_send (phone->mq, e);
578 phone->state = PS_READY;
583 * Create a new phone.
585 * @param cfg configuration for the phone; specifies the phone service and
586 * which line the phone is to be connected to
587 * @param ego ego to use for name resolution (when determining caller ID)
588 * @param event_handler how to notify the owner of the phone about events
589 * @param event_handler_cls closure for @a event_handler
590 * @return NULL on error (no valid line configured)
592 struct GNUNET_CONVERSATION_Phone *
593 GNUNET_CONVERSATION_phone_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
594 const struct GNUNET_IDENTITY_Ego *ego,
595 GNUNET_CONVERSATION_PhoneEventHandler event_handler,
596 void *event_handler_cls)
598 struct GNUNET_CONVERSATION_Phone *phone;
599 unsigned long long line;
602 GNUNET_CONFIGURATION_get_value_number (cfg,
607 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
612 if (line >= (1 << 31))
614 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
617 _("number too large"));
620 phone = GNUNET_new (struct GNUNET_CONVERSATION_Phone);
622 GNUNET_CRYPTO_get_peer_identity (cfg,
623 &phone->my_record.peer))
630 phone->my_zone = *GNUNET_IDENTITY_ego_get_private_key (ego);
631 phone->event_handler = event_handler;
632 phone->event_handler_cls = event_handler_cls;
633 phone->ns = GNUNET_NAMESTORE_connect (cfg);
634 phone->my_record.line = htonl ((uint32_t) line);
635 phone->my_record.version = htonl (0);
636 reconnect_phone (phone);
637 if ( (NULL == phone->client) ||
638 (NULL == phone->ns) )
641 GNUNET_CONVERSATION_phone_destroy (phone);
649 * Fill in a namestore record with the contact information
650 * for this phone. Note that the filled in "data" value
651 * is only valid until the phone is destroyed.
653 * @param phone phone to create a record for
654 * @param rd namestore record to fill in
657 GNUNET_CONVERSATION_phone_get_record (struct GNUNET_CONVERSATION_Phone *phone,
658 struct GNUNET_GNSRECORD_Data *rd)
660 rd->data = &phone->my_record;
661 rd->expiration_time = 0;
662 rd->data_size = sizeof (struct GNUNET_CONVERSATION_PhoneRecord);
663 rd->record_type = GNUNET_GNSRECORD_TYPE_PHONE;
664 rd->flags = GNUNET_GNSRECORD_RF_NONE;
669 * Picks up a (ringing) phone. This will connect the speaker
670 * to the microphone of the other party, and vice versa.
672 * @param caller handle that identifies which caller should be answered
673 * @param event_handler how to notify about events by the caller
674 * @param event_handler_cls closure for @a event_handler
675 * @param speaker speaker to use
676 * @param mic microphone to use
679 GNUNET_CONVERSATION_caller_pick_up (struct GNUNET_CONVERSATION_Caller *caller,
680 GNUNET_CONVERSATION_CallerEventHandler event_handler,
681 void *event_handler_cls,
682 struct GNUNET_SPEAKER_Handle *speaker,
683 struct GNUNET_MICROPHONE_Handle *mic)
685 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
686 struct GNUNET_MQ_Envelope *e;
687 struct ClientPhonePickupMessage *pick;
689 GNUNET_assert (CS_RINGING == caller->state);
690 caller->speaker = speaker;
692 e = GNUNET_MQ_msg (pick, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICK_UP);
693 pick->cid = caller->cid;
694 GNUNET_MQ_send (phone->mq, e);
695 caller->state = CS_ACTIVE;
696 caller->event_handler = event_handler;
697 caller->event_handler_cls = event_handler_cls;
698 caller->speaker->enable_speaker (caller->speaker->cls);
699 caller->mic->enable_microphone (caller->mic->cls,
700 &transmit_phone_audio,
706 * Hang up up a (possibly ringing) phone. This will notify the other
707 * party that we are no longer interested in talking with them.
709 * @param caller conversation to hang up on
712 GNUNET_CONVERSATION_caller_hang_up (struct GNUNET_CONVERSATION_Caller *caller)
714 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
715 struct GNUNET_MQ_Envelope *e;
716 struct ClientPhoneHangupMessage *hang;
718 switch (caller->state)
721 caller->speaker->disable_speaker (caller->speaker->cls);
722 caller->mic->disable_microphone (caller->mic->cls);
727 GNUNET_CONTAINER_DLL_remove (phone->caller_head,
730 e = GNUNET_MQ_msg (hang,
731 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP);
732 hang->cid = caller->cid;
733 GNUNET_MQ_send (phone->mq, e);
734 GNUNET_free (caller);
741 * @param phone phone to destroy
744 GNUNET_CONVERSATION_phone_destroy (struct GNUNET_CONVERSATION_Phone *phone)
746 clean_up_callers (phone);
747 if (NULL != phone->ns)
749 GNUNET_NAMESTORE_disconnect (phone->ns);
752 if (NULL != phone->mq)
754 GNUNET_MQ_destroy (phone->mq);
757 if (NULL != phone->client)
759 GNUNET_CLIENT_disconnect (phone->client);
760 phone->client = NULL;
767 * Pause conversation of an active call. This will disconnect the speaker
768 * and the microphone. The call can later be resumed with
769 * #GNUNET_CONVERSATION_caller_resume.
771 * @param caller call to suspend
774 GNUNET_CONVERSATION_caller_suspend (struct GNUNET_CONVERSATION_Caller *caller)
776 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
777 struct GNUNET_MQ_Envelope *e;
778 struct ClientPhoneSuspendMessage *suspend;
780 GNUNET_assert ( (CS_ACTIVE == caller->state) ||
781 (CS_CALLER_SUSPENDED == caller->state) );
782 if (CS_ACTIVE == caller->state)
784 caller->speaker->disable_speaker (caller->speaker->cls);
785 caller->mic->disable_microphone (caller->mic->cls);
787 caller->speaker = NULL;
789 e = GNUNET_MQ_msg (suspend, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
790 suspend->cid = caller->cid;
791 GNUNET_MQ_send (phone->mq, e);
792 if (CS_ACTIVE == caller->state)
793 caller->state = CS_CALLEE_SUSPENDED;
795 caller->state = CS_BOTH_SUSPENDED;
800 * Resume suspended conversation of a phone.
802 * @param caller call to resume
803 * @param speaker speaker to use
804 * @param mic microphone to use
807 GNUNET_CONVERSATION_caller_resume (struct GNUNET_CONVERSATION_Caller *caller,
808 struct GNUNET_SPEAKER_Handle *speaker,
809 struct GNUNET_MICROPHONE_Handle *mic)
811 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
812 struct GNUNET_MQ_Envelope *e;
813 struct ClientPhoneResumeMessage *resume;
815 GNUNET_assert ( (CS_CALLEE_SUSPENDED == caller->state) ||
816 (CS_BOTH_SUSPENDED == caller->state) );
817 caller->speaker = speaker;
819 e = GNUNET_MQ_msg (resume, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
820 resume->cid = caller->cid;
821 GNUNET_MQ_send (phone->mq, e);
822 if (CS_CALLEE_SUSPENDED == caller->state)
824 caller->state = CS_ACTIVE;
825 caller->speaker->enable_speaker (caller->speaker->cls);
826 caller->mic->enable_microphone (caller->mic->cls,
827 &transmit_phone_audio,
832 caller->state = CS_CALLER_SUSPENDED;
836 /* end of conversation_api.c */