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.
66 * A caller is the handle we have for an incoming call.
68 struct GNUNET_CONVERSATION_Caller
71 * We keep all callers in a DLL.
73 struct GNUNET_CONVERSATION_Caller *next;
76 * We keep all callers in a DLL.
78 struct GNUNET_CONVERSATION_Caller *prev;
83 struct GNUNET_CONVERSATION_Phone *phone;
86 * Function to call for phone events.
88 GNUNET_CONVERSATION_CallerEventHandler event_handler;
91 * Closure for @e event_handler
93 void *event_handler_cls;
96 * Speaker, or NULL if none is attached.
98 struct GNUNET_SPEAKER_Handle *speaker;
101 * Microphone, or NULL if none is attached.
103 struct GNUNET_MICROPHONE_Handle *mic;
106 * Identity of the person calling us.
108 struct GNUNET_CRYPTO_EcdsaPublicKey caller_id;
111 * Internal handle to identify the caller with the service.
116 * State machine for the phone.
118 enum CallerState state;
123 * Possible states of a phone.
128 * We still need to register the phone.
133 * We are waiting for calls.
140 * A phone is a device that can ring to signal an incoming call and
141 * that you can pick up to answer the call and hang up to terminate
142 * the call. You can also hang up a ringing phone immediately
143 * (without picking it up) to stop it from ringing. Phones have
144 * caller ID. You can ask the phone for its record and make that
145 * record available (via GNS) to enable others to call you.
146 * Multiple phones maybe connected to the same line (the line is
147 * something rather internal to a phone and not obvious from it).
148 * You can only have one conversation per phone at any time.
150 struct GNUNET_CONVERSATION_Phone
155 const struct GNUNET_CONFIGURATION_Handle *cfg;
158 * We keep all callers in a DLL.
160 struct GNUNET_CONVERSATION_Caller *caller_head;
163 * We keep all callers in a DLL.
165 struct GNUNET_CONVERSATION_Caller *caller_tail;
168 * Function to call for phone events.
170 GNUNET_CONVERSATION_PhoneEventHandler event_handler;
173 * Closure for @e event_handler
175 void *event_handler_cls;
178 * Connection to NAMESTORE (for reverse lookup).
180 struct GNUNET_NAMESTORE_Handle *ns;
183 * Handle for transmitting to the CONVERSATION service.
185 struct GNUNET_MQ_Handle *mq;
188 * This phone's record.
190 struct GNUNET_CONVERSATION_PhoneRecord my_record;
195 struct GNUNET_CRYPTO_EcdsaPrivateKey my_zone;
198 * State machine for the phone.
200 enum PhoneState state;
205 * The phone got disconnected, reconnect to the service.
207 * @param phone phone to reconnect
210 reconnect_phone (struct GNUNET_CONVERSATION_Phone *phone);
214 * Process recorded audio data.
216 * @param cls closure with the `struct GNUNET_CONVERSATION_Caller`
217 * @param data_size number of bytes in @a data
218 * @param data audio data to play
221 transmit_phone_audio (void *cls,
225 struct GNUNET_CONVERSATION_Caller *caller = cls;
226 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
227 struct GNUNET_MQ_Envelope *e;
228 struct ClientAudioMessage *am;
230 e = GNUNET_MQ_msg_extra (am,
232 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO);
233 am->cid = caller->cid;
234 GNUNET_memcpy (&am[1],
237 GNUNET_MQ_send (phone->mq,
243 * We received a `struct ClientPhoneRingMessage`
245 * @param cls the `struct GNUNET_CONVERSATION_Phone`
246 * @param ring the message
249 handle_phone_ring (void *cls,
250 const struct ClientPhoneRingMessage *ring)
252 struct GNUNET_CONVERSATION_Phone *phone = cls;
253 struct GNUNET_CONVERSATION_Caller *caller;
255 switch (phone->state)
262 caller = GNUNET_new (struct GNUNET_CONVERSATION_Caller);
263 caller->phone = phone;
264 GNUNET_CONTAINER_DLL_insert (phone->caller_head,
267 caller->caller_id = ring->caller_id;
268 caller->cid = ring->cid;
269 caller->state = CS_RINGING;
270 phone->event_handler (phone->event_handler_cls,
271 GNUNET_CONVERSATION_EC_PHONE_RING,
280 * Find the record of the caller matching the @a cid
282 * @param phone phone to search
283 * @param cid caller ID to search for (in NBO)
284 * @return NULL if @a cid was not found
286 static struct GNUNET_CONVERSATION_Caller *
287 find_caller (struct GNUNET_CONVERSATION_Phone *phone,
290 struct GNUNET_CONVERSATION_Caller *caller;
292 for (caller = phone->caller_head; NULL != caller; caller = caller->next)
293 if (cid == caller->cid)
300 * We received a `struct ClientPhoneHangupMessage`.
302 * @param cls the `struct GNUNET_CONVERSATION_Phone *`
303 * @param msg the message
306 handle_phone_hangup (void *cls,
307 const struct ClientPhoneHangupMessage *hang)
309 struct GNUNET_CONVERSATION_Phone *phone = cls;
310 struct GNUNET_CONVERSATION_Caller *caller;
312 caller = find_caller (phone,
316 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
317 "Received HANG_UP message for unknown caller ID %u\n",
318 (unsigned int) hang->cid);
322 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
323 "Received HANG_UP message, terminating call with `%s'\n",
324 GNUNET_GNSRECORD_pkey_to_zkey (&caller->caller_id));
325 switch (caller->state)
328 phone->event_handler (phone->event_handler_cls,
329 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
335 caller->speaker->disable_speaker (caller->speaker->cls);
336 caller->mic->disable_microphone (caller->mic->cls);
337 phone->event_handler (phone->event_handler_cls,
338 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
343 case CS_CALLEE_SUSPENDED:
344 case CS_CALLER_SUSPENDED:
345 case CS_BOTH_SUSPENDED:
346 phone->event_handler (phone->event_handler_cls,
347 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
352 GNUNET_CONTAINER_DLL_remove (phone->caller_head,
355 GNUNET_free (caller);
360 * We received a `struct ClientPhoneSuspendMessage`.
362 * @param cls the `struct GNUNET_CONVERSATION_Phone`
363 * @param suspend the message
366 handle_phone_suspend (void *cls,
367 const struct ClientPhoneSuspendMessage *suspend)
369 struct GNUNET_CONVERSATION_Phone *phone = cls;
370 struct GNUNET_CONVERSATION_Caller *caller;
372 caller = find_caller (phone,
376 switch (caller->state)
383 caller->state = CS_CALLER_SUSPENDED;
384 caller->speaker->disable_speaker (caller->speaker->cls);
385 caller->mic->disable_microphone (caller->mic->cls);
386 caller->event_handler (caller->event_handler_cls,
387 GNUNET_CONVERSATION_EC_CALLER_SUSPEND);
390 case CS_CALLEE_SUSPENDED:
391 caller->state = CS_BOTH_SUSPENDED;
392 caller->event_handler (caller->event_handler_cls,
393 GNUNET_CONVERSATION_EC_CALLER_SUSPEND);
396 case CS_CALLER_SUSPENDED:
397 case CS_BOTH_SUSPENDED:
405 * We received a `struct ClientPhoneResumeMessage`.
407 * @param cls the `struct GNUNET_CONVERSATION_Phone`
408 * @param resume the message
411 handle_phone_resume (void *cls,
412 const struct ClientPhoneResumeMessage *resume)
414 struct GNUNET_CONVERSATION_Phone *phone = cls;
415 struct GNUNET_CONVERSATION_Caller *caller;
417 caller = find_caller (phone,
421 switch (caller->state)
428 case CS_CALLEE_SUSPENDED:
432 case CS_CALLER_SUSPENDED:
433 caller->state = CS_ACTIVE;
434 caller->speaker->enable_speaker (caller->speaker->cls);
435 caller->mic->enable_microphone (caller->mic->cls,
436 &transmit_phone_audio,
438 caller->event_handler (caller->event_handler_cls,
439 GNUNET_CONVERSATION_EC_CALLER_RESUME);
442 case CS_BOTH_SUSPENDED:
443 caller->state = CS_CALLEE_SUSPENDED;
444 caller->event_handler (caller->event_handler_cls,
445 GNUNET_CONVERSATION_EC_CALLER_RESUME);
452 * We received a `struct ClientAudioMessage`, check it is well-formed.
454 * @param cls the `struct GNUNET_CONVERSATION_Phone`
455 * @param am the message
456 * @return #GNUNET_OK if @a am is well-formed
459 check_phone_audio (void *cls,
460 const struct ClientAudioMessage *am)
465 /* any variable-size payload is OK */
471 * We received a `struct ClientAudioMessage`
473 * @param cls the `struct GNUNET_CONVERSATION_Phone`
474 * @param am the message
477 handle_phone_audio (void *cls,
478 const struct ClientAudioMessage *am)
480 struct GNUNET_CONVERSATION_Phone *phone = cls;
481 struct GNUNET_CONVERSATION_Caller *caller;
483 caller = find_caller (phone,
487 switch (caller->state)
494 caller->speaker->play (caller->speaker->cls,
495 ntohs (am->header.size) - sizeof(struct
500 case CS_CALLEE_SUSPENDED:
501 case CS_CALLER_SUSPENDED:
502 case CS_BOTH_SUSPENDED:
509 * We encountered an error talking with the conversation service.
511 * @param cls the `struct GNUNET_CONVERSATION_Phone`
512 * @param error details about the error
515 phone_error_handler (void *cls,
516 enum GNUNET_MQ_Error error)
518 struct GNUNET_CONVERSATION_Phone *phone = cls;
521 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
523 "Connection to conversation service lost, trying to reconnect\n"));
524 reconnect_phone (phone);
529 * Clean up all callers of the given phone.
531 * @param phone phone to clean up callers for
534 clean_up_callers (struct GNUNET_CONVERSATION_Phone *phone)
536 struct GNUNET_CONVERSATION_Caller *caller;
538 while (NULL != (caller = phone->caller_head))
540 /* make sure mic/speaker are disabled *before* callback */
541 if (CS_ACTIVE == caller->state)
543 caller->speaker->disable_speaker (caller->speaker->cls);
544 caller->mic->disable_microphone (caller->mic->cls);
545 caller->state = CS_CALLER_SUSPENDED;
547 phone->event_handler (phone->event_handler_cls,
548 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
551 GNUNET_CONVERSATION_caller_hang_up (caller);
557 * The phone got disconnected, reconnect to the service.
559 * @param phone phone to reconnect
562 reconnect_phone (struct GNUNET_CONVERSATION_Phone *phone)
564 struct GNUNET_MQ_MessageHandler handlers[] = {
565 GNUNET_MQ_hd_fixed_size (phone_ring,
566 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RING,
567 struct ClientPhoneRingMessage,
569 GNUNET_MQ_hd_fixed_size (phone_hangup,
570 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
571 struct ClientPhoneHangupMessage,
573 GNUNET_MQ_hd_fixed_size (phone_suspend,
574 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
575 struct ClientPhoneSuspendMessage,
577 GNUNET_MQ_hd_fixed_size (phone_resume,
578 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
579 struct ClientPhoneResumeMessage,
581 GNUNET_MQ_hd_var_size (phone_audio,
582 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
583 struct ClientAudioMessage,
585 GNUNET_MQ_handler_end ()
587 struct GNUNET_MQ_Envelope *e;
588 struct ClientPhoneRegisterMessage *reg;
590 clean_up_callers (phone);
591 if (NULL != phone->mq)
593 GNUNET_MQ_destroy (phone->mq);
596 phone->state = PS_REGISTER;
597 phone->mq = GNUNET_CLIENT_connect (phone->cfg,
600 &phone_error_handler,
602 if (NULL == phone->mq)
604 e = GNUNET_MQ_msg (reg,
605 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_REGISTER);
606 reg->line_port = phone->my_record.line_port;
607 GNUNET_MQ_send (phone->mq,
609 phone->state = PS_READY;
614 * Create a new phone.
616 * @param cfg configuration for the phone; specifies the phone service and
617 * which line the phone is to be connected to
618 * @param ego ego to use for name resolution (when determining caller ID)
619 * @param event_handler how to notify the owner of the phone about events
620 * @param event_handler_cls closure for @a event_handler
621 * @return NULL on error (no valid line configured)
623 struct GNUNET_CONVERSATION_Phone *
624 GNUNET_CONVERSATION_phone_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
625 const struct GNUNET_IDENTITY_Ego *ego,
626 GNUNET_CONVERSATION_PhoneEventHandler
628 void *event_handler_cls)
630 struct GNUNET_CONVERSATION_Phone *phone;
632 struct GNUNET_HashCode line_port;
635 GNUNET_CONFIGURATION_get_value_string (cfg,
640 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
645 GNUNET_CRYPTO_hash (line,
648 phone = GNUNET_new (struct GNUNET_CONVERSATION_Phone);
650 GNUNET_CRYPTO_get_peer_identity (cfg,
651 &phone->my_record.peer))
658 phone->my_zone = *GNUNET_IDENTITY_ego_get_private_key (ego);
659 phone->event_handler = event_handler;
660 phone->event_handler_cls = event_handler_cls;
661 phone->ns = GNUNET_NAMESTORE_connect (cfg);
662 phone->my_record.version = htonl (1);
663 phone->my_record.reserved = htonl (0);
664 phone->my_record.line_port = line_port;
665 reconnect_phone (phone);
666 if ((NULL == phone->mq) ||
670 GNUNET_CONVERSATION_phone_destroy (phone);
678 * Fill in a namestore record with the contact information
679 * for this phone. Note that the filled in "data" value
680 * is only valid until the phone is destroyed.
682 * @param phone phone to create a record for
683 * @param rd namestore record to fill in
686 GNUNET_CONVERSATION_phone_get_record (struct GNUNET_CONVERSATION_Phone *phone,
687 struct GNUNET_GNSRECORD_Data *rd)
689 rd->data = &phone->my_record;
690 rd->expiration_time = 0;
691 rd->data_size = sizeof(struct GNUNET_CONVERSATION_PhoneRecord);
692 rd->record_type = GNUNET_GNSRECORD_TYPE_PHONE;
693 rd->flags = GNUNET_GNSRECORD_RF_NONE;
698 * Picks up a (ringing) phone. This will connect the speaker
699 * to the microphone of the other party, and vice versa.
701 * @param caller handle that identifies which caller should be answered
702 * @param event_handler how to notify about events by the caller
703 * @param event_handler_cls closure for @a event_handler
704 * @param speaker speaker to use
705 * @param mic microphone to use
708 GNUNET_CONVERSATION_caller_pick_up (struct GNUNET_CONVERSATION_Caller *caller,
709 GNUNET_CONVERSATION_CallerEventHandler
711 void *event_handler_cls,
712 struct GNUNET_SPEAKER_Handle *speaker,
713 struct GNUNET_MICROPHONE_Handle *mic)
715 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
716 struct GNUNET_MQ_Envelope *e;
717 struct ClientPhonePickupMessage *pick;
719 GNUNET_assert (CS_RINGING == caller->state);
720 caller->speaker = speaker;
722 e = GNUNET_MQ_msg (pick,
723 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICK_UP);
724 pick->cid = caller->cid;
725 GNUNET_MQ_send (phone->mq,
727 caller->state = CS_ACTIVE;
728 caller->event_handler = event_handler;
729 caller->event_handler_cls = event_handler_cls;
730 caller->speaker->enable_speaker (caller->speaker->cls);
731 caller->mic->enable_microphone (caller->mic->cls,
732 &transmit_phone_audio,
738 * Hang up up a (possibly ringing) phone. This will notify the other
739 * party that we are no longer interested in talking with them.
741 * @param caller conversation to hang up on
744 GNUNET_CONVERSATION_caller_hang_up (struct GNUNET_CONVERSATION_Caller *caller)
746 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
747 struct GNUNET_MQ_Envelope *e;
748 struct ClientPhoneHangupMessage *hang;
750 switch (caller->state)
753 caller->speaker->disable_speaker (caller->speaker->cls);
754 caller->mic->disable_microphone (caller->mic->cls);
760 GNUNET_CONTAINER_DLL_remove (phone->caller_head,
763 e = GNUNET_MQ_msg (hang,
764 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP);
765 hang->cid = caller->cid;
766 GNUNET_MQ_send (phone->mq,
768 GNUNET_free (caller);
775 * @param phone phone to destroy
778 GNUNET_CONVERSATION_phone_destroy (struct GNUNET_CONVERSATION_Phone *phone)
780 clean_up_callers (phone);
781 if (NULL != phone->ns)
783 GNUNET_NAMESTORE_disconnect (phone->ns);
786 if (NULL != phone->mq)
788 GNUNET_MQ_destroy (phone->mq);
796 * Pause conversation of an active call. This will disconnect the speaker
797 * and the microphone. The call can later be resumed with
798 * #GNUNET_CONVERSATION_caller_resume.
800 * @param caller call to suspend
803 GNUNET_CONVERSATION_caller_suspend (struct GNUNET_CONVERSATION_Caller *caller)
805 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
806 struct GNUNET_MQ_Envelope *e;
807 struct ClientPhoneSuspendMessage *suspend;
809 GNUNET_assert ((CS_ACTIVE == caller->state) ||
810 (CS_CALLER_SUSPENDED == caller->state));
811 if (CS_ACTIVE == caller->state)
813 caller->speaker->disable_speaker (caller->speaker->cls);
814 caller->mic->disable_microphone (caller->mic->cls);
816 caller->speaker = NULL;
818 e = GNUNET_MQ_msg (suspend,
819 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
820 suspend->cid = caller->cid;
821 GNUNET_MQ_send (phone->mq,
823 if (CS_ACTIVE == caller->state)
824 caller->state = CS_CALLEE_SUSPENDED;
826 caller->state = CS_BOTH_SUSPENDED;
831 * Resume suspended conversation of a phone.
833 * @param caller call to resume
834 * @param speaker speaker to use
835 * @param mic microphone to use
838 GNUNET_CONVERSATION_caller_resume (struct GNUNET_CONVERSATION_Caller *caller,
839 struct GNUNET_SPEAKER_Handle *speaker,
840 struct GNUNET_MICROPHONE_Handle *mic)
842 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
843 struct GNUNET_MQ_Envelope *e;
844 struct ClientPhoneResumeMessage *resume;
846 GNUNET_assert ((CS_CALLEE_SUSPENDED == caller->state) ||
847 (CS_BOTH_SUSPENDED == caller->state));
848 caller->speaker = speaker;
850 e = GNUNET_MQ_msg (resume,
851 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
852 resume->cid = caller->cid;
853 GNUNET_MQ_send (phone->mq,
855 if (CS_CALLEE_SUSPENDED == caller->state)
857 caller->state = CS_ACTIVE;
858 caller->speaker->enable_speaker (caller->speaker->cls);
859 caller->mic->enable_microphone (caller->mic->cls,
860 &transmit_phone_audio,
865 caller->state = CS_CALLER_SUSPENDED;
870 /* end of conversation_api.c */