2 This file is part of GNUnet
3 Copyright (C) 2013, 2014 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your 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 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
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 "conversation.h"
34 * Possible states of a caller.
39 * The phone is ringing (user knows about incoming call).
44 * The phone is in an active conversation.
49 * We suspended the conversation.
54 * Caller suspended the conversation.
59 * Both sides suspended the conversation.
67 * A caller is the handle we have for an incoming call.
69 struct GNUNET_CONVERSATION_Caller
72 * We keep all callers in a DLL.
74 struct GNUNET_CONVERSATION_Caller *next;
77 * We keep all callers in a DLL.
79 struct GNUNET_CONVERSATION_Caller *prev;
84 struct GNUNET_CONVERSATION_Phone *phone;
87 * Function to call for phone events.
89 GNUNET_CONVERSATION_CallerEventHandler event_handler;
92 * Closure for @e event_handler
94 void *event_handler_cls;
97 * Speaker, or NULL if none is attached.
99 struct GNUNET_SPEAKER_Handle *speaker;
102 * Microphone, or NULL if none is attached.
104 struct GNUNET_MICROPHONE_Handle *mic;
107 * Identity of the person calling us.
109 struct GNUNET_CRYPTO_EcdsaPublicKey caller_id;
112 * Internal handle to identify the caller with the service.
117 * State machine for the phone.
119 enum CallerState state;
124 * Possible states of a phone.
129 * We still need to register the phone.
134 * We are waiting for calls.
141 * A phone is a device that can ring to signal an incoming call and
142 * that you can pick up to answer the call and hang up to terminate
143 * the call. You can also hang up a ringing phone immediately
144 * (without picking it up) to stop it from ringing. Phones have
145 * caller ID. You can ask the phone for its record and make that
146 * record available (via GNS) to enable others to call you.
147 * Multiple phones maybe connected to the same line (the line is
148 * something rather internal to a phone and not obvious from it).
149 * You can only have one conversation per phone at any time.
151 struct GNUNET_CONVERSATION_Phone
156 const struct GNUNET_CONFIGURATION_Handle *cfg;
159 * We keep all callers in a DLL.
161 struct GNUNET_CONVERSATION_Caller *caller_head;
164 * We keep all callers in a DLL.
166 struct GNUNET_CONVERSATION_Caller *caller_tail;
169 * Function to call for phone events.
171 GNUNET_CONVERSATION_PhoneEventHandler event_handler;
174 * Closure for @e event_handler
176 void *event_handler_cls;
179 * Connection to NAMESTORE (for reverse lookup).
181 struct GNUNET_NAMESTORE_Handle *ns;
184 * Handle for transmitting to the CONVERSATION service.
186 struct GNUNET_MQ_Handle *mq;
189 * This phone's record.
191 struct GNUNET_CONVERSATION_PhoneRecord my_record;
196 struct GNUNET_CRYPTO_EcdsaPrivateKey my_zone;
199 * State machine for the phone.
201 enum PhoneState state;
206 * The phone got disconnected, reconnect to the service.
208 * @param phone phone to reconnect
211 reconnect_phone (struct GNUNET_CONVERSATION_Phone *phone);
215 * Process recorded audio data.
217 * @param cls closure with the `struct GNUNET_CONVERSATION_Caller`
218 * @param data_size number of bytes in @a data
219 * @param data audio data to play
222 transmit_phone_audio (void *cls,
226 struct GNUNET_CONVERSATION_Caller *caller = cls;
227 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
228 struct GNUNET_MQ_Envelope *e;
229 struct ClientAudioMessage *am;
231 e = GNUNET_MQ_msg_extra (am,
233 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO);
234 am->cid = caller->cid;
235 GNUNET_memcpy (&am[1],
238 GNUNET_MQ_send (phone->mq,
244 * We received a `struct ClientPhoneRingMessage`
246 * @param cls the `struct GNUNET_CONVERSATION_Phone`
247 * @param ring the message
250 handle_phone_ring (void *cls,
251 const struct ClientPhoneRingMessage *ring)
253 struct GNUNET_CONVERSATION_Phone *phone = cls;
254 struct GNUNET_CONVERSATION_Caller *caller;
256 switch (phone->state)
263 caller = GNUNET_new (struct GNUNET_CONVERSATION_Caller);
264 caller->phone = phone;
265 GNUNET_CONTAINER_DLL_insert (phone->caller_head,
268 caller->caller_id = ring->caller_id;
269 caller->cid = ring->cid;
270 caller->state = CS_RINGING;
271 phone->event_handler (phone->event_handler_cls,
272 GNUNET_CONVERSATION_EC_PHONE_RING,
281 * Find the record of the caller matching the @a cid
283 * @param phone phone to search
284 * @param cid caller ID to search for (in NBO)
285 * @return NULL if @a cid was not found
287 static struct GNUNET_CONVERSATION_Caller *
288 find_caller (struct GNUNET_CONVERSATION_Phone *phone,
291 struct GNUNET_CONVERSATION_Caller *caller;
293 for (caller = phone->caller_head; NULL != caller; caller = caller->next)
294 if (cid == caller->cid)
301 * We received a `struct ClientPhoneHangupMessage`.
303 * @param cls the `struct GNUNET_CONVERSATION_Phone *`
304 * @param msg the message
307 handle_phone_hangup (void *cls,
308 const struct ClientPhoneHangupMessage *hang)
310 struct GNUNET_CONVERSATION_Phone *phone = cls;
311 struct GNUNET_CONVERSATION_Caller *caller;
313 caller = find_caller (phone,
317 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
318 "Received HANG_UP message for unknown caller ID %u\n",
319 (unsigned int) hang->cid);
323 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
324 "Received HANG_UP message, terminating call with `%s'\n",
325 GNUNET_GNSRECORD_pkey_to_zkey (&caller->caller_id));
326 switch (caller->state)
329 phone->event_handler (phone->event_handler_cls,
330 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
336 caller->speaker->disable_speaker (caller->speaker->cls);
337 caller->mic->disable_microphone (caller->mic->cls);
338 phone->event_handler (phone->event_handler_cls,
339 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
344 case CS_CALLEE_SUSPENDED:
345 case CS_CALLER_SUSPENDED:
346 case CS_BOTH_SUSPENDED:
347 phone->event_handler (phone->event_handler_cls,
348 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
353 GNUNET_CONTAINER_DLL_remove (phone->caller_head,
356 GNUNET_free (caller);
361 * We received a `struct ClientPhoneSuspendMessage`.
363 * @param cls the `struct GNUNET_CONVERSATION_Phone`
364 * @param suspend the message
367 handle_phone_suspend (void *cls,
368 const struct ClientPhoneSuspendMessage *suspend)
370 struct GNUNET_CONVERSATION_Phone *phone = cls;
371 struct GNUNET_CONVERSATION_Caller *caller;
373 caller = find_caller (phone,
377 switch (caller->state)
384 caller->state = CS_CALLER_SUSPENDED;
385 caller->speaker->disable_speaker (caller->speaker->cls);
386 caller->mic->disable_microphone (caller->mic->cls);
387 caller->event_handler (caller->event_handler_cls,
388 GNUNET_CONVERSATION_EC_CALLER_SUSPEND);
391 case CS_CALLEE_SUSPENDED:
392 caller->state = CS_BOTH_SUSPENDED;
393 caller->event_handler (caller->event_handler_cls,
394 GNUNET_CONVERSATION_EC_CALLER_SUSPEND);
397 case CS_CALLER_SUSPENDED:
398 case CS_BOTH_SUSPENDED:
406 * We received a `struct ClientPhoneResumeMessage`.
408 * @param cls the `struct GNUNET_CONVERSATION_Phone`
409 * @param resume the message
412 handle_phone_resume (void *cls,
413 const struct ClientPhoneResumeMessage *resume)
415 struct GNUNET_CONVERSATION_Phone *phone = cls;
416 struct GNUNET_CONVERSATION_Caller *caller;
418 caller = find_caller (phone,
422 switch (caller->state)
429 case CS_CALLEE_SUSPENDED:
433 case CS_CALLER_SUSPENDED:
434 caller->state = CS_ACTIVE;
435 caller->speaker->enable_speaker (caller->speaker->cls);
436 caller->mic->enable_microphone (caller->mic->cls,
437 &transmit_phone_audio,
439 caller->event_handler (caller->event_handler_cls,
440 GNUNET_CONVERSATION_EC_CALLER_RESUME);
443 case CS_BOTH_SUSPENDED:
444 caller->state = CS_CALLEE_SUSPENDED;
445 caller->event_handler (caller->event_handler_cls,
446 GNUNET_CONVERSATION_EC_CALLER_RESUME);
453 * We received a `struct ClientAudioMessage`, check it is well-formed.
455 * @param cls the `struct GNUNET_CONVERSATION_Phone`
456 * @param am the message
457 * @return #GNUNET_OK if @a am is well-formed
460 check_phone_audio (void *cls,
461 const struct ClientAudioMessage *am)
466 /* any variable-size payload is OK */
472 * We received a `struct ClientAudioMessage`
474 * @param cls the `struct GNUNET_CONVERSATION_Phone`
475 * @param am the message
478 handle_phone_audio (void *cls,
479 const struct ClientAudioMessage *am)
481 struct GNUNET_CONVERSATION_Phone *phone = cls;
482 struct GNUNET_CONVERSATION_Caller *caller;
484 caller = find_caller (phone,
488 switch (caller->state)
495 caller->speaker->play (caller->speaker->cls,
496 ntohs (am->header.size) - sizeof(struct
501 case CS_CALLEE_SUSPENDED:
502 case CS_CALLER_SUSPENDED:
503 case CS_BOTH_SUSPENDED:
510 * We encountered an error talking with the conversation service.
512 * @param cls the `struct GNUNET_CONVERSATION_Phone`
513 * @param error details about the error
516 phone_error_handler (void *cls,
517 enum GNUNET_MQ_Error error)
519 struct GNUNET_CONVERSATION_Phone *phone = cls;
522 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
524 "Connection to conversation service lost, trying to reconnect\n"));
525 reconnect_phone (phone);
530 * Clean up all callers of the given phone.
532 * @param phone phone to clean up callers for
535 clean_up_callers (struct GNUNET_CONVERSATION_Phone *phone)
537 struct GNUNET_CONVERSATION_Caller *caller;
539 while (NULL != (caller = phone->caller_head))
541 /* make sure mic/speaker are disabled *before* callback */
542 if (CS_ACTIVE == caller->state)
544 caller->speaker->disable_speaker (caller->speaker->cls);
545 caller->mic->disable_microphone (caller->mic->cls);
546 caller->state = CS_CALLER_SUSPENDED;
548 phone->event_handler (phone->event_handler_cls,
549 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
552 GNUNET_CONVERSATION_caller_hang_up (caller);
558 * The phone got disconnected, reconnect to the service.
560 * @param phone phone to reconnect
563 reconnect_phone (struct GNUNET_CONVERSATION_Phone *phone)
565 struct GNUNET_MQ_MessageHandler handlers[] = {
566 GNUNET_MQ_hd_fixed_size (phone_ring,
567 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RING,
568 struct ClientPhoneRingMessage,
570 GNUNET_MQ_hd_fixed_size (phone_hangup,
571 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
572 struct ClientPhoneHangupMessage,
574 GNUNET_MQ_hd_fixed_size (phone_suspend,
575 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
576 struct ClientPhoneSuspendMessage,
578 GNUNET_MQ_hd_fixed_size (phone_resume,
579 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
580 struct ClientPhoneResumeMessage,
582 GNUNET_MQ_hd_var_size (phone_audio,
583 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
584 struct ClientAudioMessage,
586 GNUNET_MQ_handler_end ()
588 struct GNUNET_MQ_Envelope *e;
589 struct ClientPhoneRegisterMessage *reg;
591 clean_up_callers (phone);
592 if (NULL != phone->mq)
594 GNUNET_MQ_destroy (phone->mq);
597 phone->state = PS_REGISTER;
598 phone->mq = GNUNET_CLIENT_connect (phone->cfg,
601 &phone_error_handler,
603 if (NULL == phone->mq)
605 e = GNUNET_MQ_msg (reg,
606 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_REGISTER);
607 reg->line_port = phone->my_record.line_port;
608 GNUNET_MQ_send (phone->mq,
610 phone->state = PS_READY;
615 * Create a new phone.
617 * @param cfg configuration for the phone; specifies the phone service and
618 * which line the phone is to be connected to
619 * @param ego ego to use for name resolution (when determining caller ID)
620 * @param event_handler how to notify the owner of the phone about events
621 * @param event_handler_cls closure for @a event_handler
622 * @return NULL on error (no valid line configured)
624 struct GNUNET_CONVERSATION_Phone *
625 GNUNET_CONVERSATION_phone_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
626 const struct GNUNET_IDENTITY_Ego *ego,
627 GNUNET_CONVERSATION_PhoneEventHandler
629 void *event_handler_cls)
631 struct GNUNET_CONVERSATION_Phone *phone;
633 struct GNUNET_HashCode line_port;
636 GNUNET_CONFIGURATION_get_value_string (cfg,
641 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
646 GNUNET_CRYPTO_hash (line,
649 phone = GNUNET_new (struct GNUNET_CONVERSATION_Phone);
651 GNUNET_CRYPTO_get_peer_identity (cfg,
652 &phone->my_record.peer))
659 phone->my_zone = *GNUNET_IDENTITY_ego_get_private_key (ego);
660 phone->event_handler = event_handler;
661 phone->event_handler_cls = event_handler_cls;
662 phone->ns = GNUNET_NAMESTORE_connect (cfg);
663 phone->my_record.version = htonl (1);
664 phone->my_record.reserved = htonl (0);
665 phone->my_record.line_port = line_port;
666 reconnect_phone (phone);
667 if ((NULL == phone->mq) ||
671 GNUNET_CONVERSATION_phone_destroy (phone);
679 * Fill in a namestore record with the contact information
680 * for this phone. Note that the filled in "data" value
681 * is only valid until the phone is destroyed.
683 * @param phone phone to create a record for
684 * @param rd namestore record to fill in
687 GNUNET_CONVERSATION_phone_get_record (struct GNUNET_CONVERSATION_Phone *phone,
688 struct GNUNET_GNSRECORD_Data *rd)
690 rd->data = &phone->my_record;
691 rd->expiration_time = 0;
692 rd->data_size = sizeof(struct GNUNET_CONVERSATION_PhoneRecord);
693 rd->record_type = GNUNET_GNSRECORD_TYPE_PHONE;
694 rd->flags = GNUNET_GNSRECORD_RF_NONE;
699 * Picks up a (ringing) phone. This will connect the speaker
700 * to the microphone of the other party, and vice versa.
702 * @param caller handle that identifies which caller should be answered
703 * @param event_handler how to notify about events by the caller
704 * @param event_handler_cls closure for @a event_handler
705 * @param speaker speaker to use
706 * @param mic microphone to use
709 GNUNET_CONVERSATION_caller_pick_up (struct GNUNET_CONVERSATION_Caller *caller,
710 GNUNET_CONVERSATION_CallerEventHandler
712 void *event_handler_cls,
713 struct GNUNET_SPEAKER_Handle *speaker,
714 struct GNUNET_MICROPHONE_Handle *mic)
716 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
717 struct GNUNET_MQ_Envelope *e;
718 struct ClientPhonePickupMessage *pick;
720 GNUNET_assert (CS_RINGING == caller->state);
721 caller->speaker = speaker;
723 e = GNUNET_MQ_msg (pick,
724 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICK_UP);
725 pick->cid = caller->cid;
726 GNUNET_MQ_send (phone->mq,
728 caller->state = CS_ACTIVE;
729 caller->event_handler = event_handler;
730 caller->event_handler_cls = event_handler_cls;
731 caller->speaker->enable_speaker (caller->speaker->cls);
732 caller->mic->enable_microphone (caller->mic->cls,
733 &transmit_phone_audio,
739 * Hang up up a (possibly ringing) phone. This will notify the other
740 * party that we are no longer interested in talking with them.
742 * @param caller conversation to hang up on
745 GNUNET_CONVERSATION_caller_hang_up (struct GNUNET_CONVERSATION_Caller *caller)
747 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
748 struct GNUNET_MQ_Envelope *e;
749 struct ClientPhoneHangupMessage *hang;
751 switch (caller->state)
754 caller->speaker->disable_speaker (caller->speaker->cls);
755 caller->mic->disable_microphone (caller->mic->cls);
761 GNUNET_CONTAINER_DLL_remove (phone->caller_head,
764 e = GNUNET_MQ_msg (hang,
765 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP);
766 hang->cid = caller->cid;
767 GNUNET_MQ_send (phone->mq,
769 GNUNET_free (caller);
776 * @param phone phone to destroy
779 GNUNET_CONVERSATION_phone_destroy (struct GNUNET_CONVERSATION_Phone *phone)
781 clean_up_callers (phone);
782 if (NULL != phone->ns)
784 GNUNET_NAMESTORE_disconnect (phone->ns);
787 if (NULL != phone->mq)
789 GNUNET_MQ_destroy (phone->mq);
797 * Pause conversation of an active call. This will disconnect the speaker
798 * and the microphone. The call can later be resumed with
799 * #GNUNET_CONVERSATION_caller_resume.
801 * @param caller call to suspend
804 GNUNET_CONVERSATION_caller_suspend (struct GNUNET_CONVERSATION_Caller *caller)
806 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
807 struct GNUNET_MQ_Envelope *e;
808 struct ClientPhoneSuspendMessage *suspend;
810 GNUNET_assert ((CS_ACTIVE == caller->state) ||
811 (CS_CALLER_SUSPENDED == caller->state));
812 if (CS_ACTIVE == caller->state)
814 caller->speaker->disable_speaker (caller->speaker->cls);
815 caller->mic->disable_microphone (caller->mic->cls);
817 caller->speaker = NULL;
819 e = GNUNET_MQ_msg (suspend,
820 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
821 suspend->cid = caller->cid;
822 GNUNET_MQ_send (phone->mq,
824 if (CS_ACTIVE == caller->state)
825 caller->state = CS_CALLEE_SUSPENDED;
827 caller->state = CS_BOTH_SUSPENDED;
832 * Resume suspended conversation of a phone.
834 * @param caller call to resume
835 * @param speaker speaker to use
836 * @param mic microphone to use
839 GNUNET_CONVERSATION_caller_resume (struct GNUNET_CONVERSATION_Caller *caller,
840 struct GNUNET_SPEAKER_Handle *speaker,
841 struct GNUNET_MICROPHONE_Handle *mic)
843 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
844 struct GNUNET_MQ_Envelope *e;
845 struct ClientPhoneResumeMessage *resume;
847 GNUNET_assert ((CS_CALLEE_SUSPENDED == caller->state) ||
848 (CS_BOTH_SUSPENDED == caller->state));
849 caller->speaker = speaker;
851 e = GNUNET_MQ_msg (resume,
852 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
853 resume->cid = caller->cid;
854 GNUNET_MQ_send (phone->mq,
856 if (CS_CALLEE_SUSPENDED == caller->state)
858 caller->state = CS_ACTIVE;
859 caller->speaker->enable_speaker (caller->speaker->cls);
860 caller->mic->enable_microphone (caller->mic->cls,
861 &transmit_phone_audio,
866 caller->state = CS_CALLER_SUSPENDED;
870 /* end of conversation_api.c */