aboutsummaryrefslogtreecommitdiff
path: root/src/conversation/conversation_api_call.c
diff options
context:
space:
mode:
authorMartin Schanzenbach <schanzen@gnunet.org>2023-10-19 11:36:32 +0200
committerMartin Schanzenbach <schanzen@gnunet.org>2023-10-19 11:36:32 +0200
commitb56e4e05ad919c7191260fcf1d78b1f8d739871a (patch)
tree88a8aa4c82d664666c10c5099654bbfc295ab139 /src/conversation/conversation_api_call.c
parent7c7d819e8e03dadb91935d5ae91aa921cc7b86c7 (diff)
downloadgnunet-b56e4e05ad919c7191260fcf1d78b1f8d739871a.tar.gz
gnunet-b56e4e05ad919c7191260fcf1d78b1f8d739871a.zip
BUILD: Move conversation to contrib/service
Diffstat (limited to 'src/conversation/conversation_api_call.c')
-rw-r--r--src/conversation/conversation_api_call.c749
1 files changed, 0 insertions, 749 deletions
diff --git a/src/conversation/conversation_api_call.c b/src/conversation/conversation_api_call.c
deleted file mode 100644
index 2650eb782..000000000
--- a/src/conversation/conversation_api_call.c
+++ /dev/null
@@ -1,749 +0,0 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2016 GNUnet e.V.
4
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.
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 Affero General Public License for more details.
14
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/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
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 */
38enum 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 */
80struct GNUNET_CONVERSATION_Call
81{
82 /**
83 * Our configuration.
84 */
85 const struct GNUNET_CONFIGURATION_Handle *cfg;
86
87 /**
88 * Our caller identity.
89 */
90 struct GNUNET_IDENTITY_Ego *caller_id;
91
92 /**
93 * Target callee as a GNS address/name.
94 */
95 char *callee;
96
97 /**
98 * Our speaker.
99 */
100 struct GNUNET_SPEAKER_Handle *speaker;
101
102 /**
103 * Our microphone.
104 */
105 struct GNUNET_MICROPHONE_Handle *mic;
106
107 /**
108 * Function to call with events.
109 */
110 GNUNET_CONVERSATION_CallEventHandler event_handler;
111
112 /**
113 * Closure for @e event_handler
114 */
115 void *event_handler_cls;
116
117 /**
118 * Handle for transmitting to the CONVERSATION service.
119 */
120 struct GNUNET_MQ_Handle *mq;
121
122 /**
123 * Connection to GNS (can be NULL).
124 */
125 struct GNUNET_GNS_Handle *gns;
126
127 /**
128 * Active GNS lookup (or NULL).
129 */
130 struct GNUNET_GNS_LookupWithTldRequest *gns_lookup;
131
132 /**
133 * Target phone record, only valid after the lookup is done.
134 */
135 struct GNUNET_CONVERSATION_PhoneRecord phone_record;
136
137 /**
138 * State machine for the call.
139 */
140 enum CallState state;
141};
142
143
144/**
145 * The call got disconnected, reconnect to the service.
146 *
147 * @param call call to reconnect
148 */
149static void
150fail_call (struct GNUNET_CONVERSATION_Call *call);
151
152
153/**
154 * Process recorded audio data.
155 *
156 * @param cls closure with the `struct GNUNET_CONVERSATION_Call`
157 * @param data_size number of bytes in @a data
158 * @param data audio data to play
159 */
160static void
161transmit_call_audio (void *cls,
162 size_t data_size,
163 const void *data)
164{
165 struct GNUNET_CONVERSATION_Call *call = cls;
166 struct GNUNET_MQ_Envelope *e;
167 struct ClientAudioMessage *am;
168
169 GNUNET_assert (CS_ACTIVE == call->state);
170 e = GNUNET_MQ_msg_extra (am,
171 data_size,
172 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO);
173 GNUNET_memcpy (&am[1],
174 data,
175 data_size);
176 GNUNET_MQ_send (call->mq,
177 e);
178}
179
180
181/**
182 * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND.
183 *
184 * @param cls the `struct GNUNET_CONVERSATION_Call`
185 * @param msg the message
186 */
187static void
188handle_call_suspend (void *cls,
189 const struct ClientPhoneSuspendMessage *msg)
190{
191 struct GNUNET_CONVERSATION_Call *call = cls;
192
193 (void) msg;
194 switch (call->state)
195 {
196 case CS_LOOKUP:
197 GNUNET_break (0);
198 fail_call (call);
199 break;
200
201 case CS_RINGING:
202 GNUNET_break_op (0);
203 fail_call (call);
204 break;
205
206 case CS_SUSPENDED_CALLER:
207 call->state = CS_SUSPENDED_BOTH;
208 call->event_handler (call->event_handler_cls,
209 GNUNET_CONVERSATION_EC_CALL_SUSPENDED);
210 break;
211
212 case CS_SUSPENDED_CALLEE:
213 case CS_SUSPENDED_BOTH:
214 GNUNET_break_op (0);
215 break;
216
217 case CS_ACTIVE:
218 call->state = CS_SUSPENDED_CALLEE;
219 call->speaker->disable_speaker (call->speaker->cls);
220 call->mic->disable_microphone (call->mic->cls);
221 call->event_handler (call->event_handler_cls,
222 GNUNET_CONVERSATION_EC_CALL_SUSPENDED);
223 break;
224
225 case CS_SHUTDOWN:
226 GNUNET_CONVERSATION_call_stop (call);
227 break;
228 }
229}
230
231
232/**
233 * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME.
234 *
235 * @param cls the `struct GNUNET_CONVERSATION_Call`
236 * @param msg the message
237 */
238static void
239handle_call_resume (void *cls,
240 const struct ClientPhoneResumeMessage *msg)
241{
242 struct GNUNET_CONVERSATION_Call *call = cls;
243
244 (void) msg;
245 switch (call->state)
246 {
247 case CS_LOOKUP:
248 GNUNET_break (0);
249 fail_call (call);
250 break;
251
252 case CS_RINGING:
253 GNUNET_break_op (0);
254 fail_call (call);
255 break;
256
257 case CS_SUSPENDED_CALLER:
258 GNUNET_break_op (0);
259 break;
260
261 case CS_SUSPENDED_CALLEE:
262 call->state = CS_ACTIVE;
263 call->speaker->enable_speaker (call->speaker->cls);
264 call->mic->enable_microphone (call->mic->cls,
265 &transmit_call_audio,
266 call);
267 call->event_handler (call->event_handler_cls,
268 GNUNET_CONVERSATION_EC_CALL_RESUMED);
269 break;
270
271 case CS_SUSPENDED_BOTH:
272 call->state = CS_SUSPENDED_CALLER;
273 call->event_handler (call->event_handler_cls,
274 GNUNET_CONVERSATION_EC_CALL_RESUMED);
275 break;
276
277 case CS_ACTIVE:
278 GNUNET_break_op (0);
279 break;
280
281 case CS_SHUTDOWN:
282 GNUNET_CONVERSATION_call_stop (call);
283 break;
284 }
285}
286
287
288/**
289 * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP.
290 *
291 * @param cls the `struct GNUNET_CONVERSATION_Call`
292 * @param msg the message
293 */
294static void
295handle_call_picked_up (void *cls,
296 const struct ClientPhonePickedupMessage *msg)
297{
298 struct GNUNET_CONVERSATION_Call *call = cls;
299
300 (void) msg;
301 switch (call->state)
302 {
303 case CS_LOOKUP:
304 GNUNET_break (0);
305 fail_call (call);
306 break;
307
308 case CS_RINGING:
309 call->state = CS_ACTIVE;
310 call->speaker->enable_speaker (call->speaker->cls);
311 call->mic->enable_microphone (call->mic->cls,
312 &transmit_call_audio,
313 call);
314 call->event_handler (call->event_handler_cls,
315 GNUNET_CONVERSATION_EC_CALL_PICKED_UP);
316 break;
317
318 case CS_SUSPENDED_CALLER:
319 case CS_SUSPENDED_CALLEE:
320 case CS_SUSPENDED_BOTH:
321 case CS_ACTIVE:
322 GNUNET_break (0);
323 fail_call (call);
324 break;
325
326 case CS_SHUTDOWN:
327 GNUNET_CONVERSATION_call_stop (call);
328 break;
329 }
330}
331
332
333/**
334 * We received a #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_HANG_UP.
335 *
336 * @param cls the `struct GNUNET_CONVERSATION_Call`
337 * @param msg the message
338 */
339static void
340handle_call_hangup (void *cls,
341 const struct ClientPhoneHangupMessage *msg)
342{
343 struct GNUNET_CONVERSATION_Call *call = cls;
344 GNUNET_CONVERSATION_CallEventHandler eh;
345 void *eh_cls;
346
347 (void) msg;
348 switch (call->state)
349 {
350 case CS_LOOKUP:
351 GNUNET_break (0);
352 fail_call (call);
353 break;
354
355 case CS_RINGING:
356 case CS_SUSPENDED_CALLER:
357 case CS_SUSPENDED_CALLEE:
358 case CS_SUSPENDED_BOTH:
359 case CS_ACTIVE:
360 eh = call->event_handler;
361 eh_cls = call->event_handler_cls;
362 GNUNET_CONVERSATION_call_stop (call);
363 eh (eh_cls,
364 GNUNET_CONVERSATION_EC_CALL_HUNG_UP);
365 return;
366
367 case CS_SHUTDOWN:
368 GNUNET_CONVERSATION_call_stop (call);
369 break;
370 }
371}
372
373
374/**
375 * We received a `struct ClientAudioMessage`, check it is well-formed.
376 *
377 * @param cls the `struct GNUNET_CONVERSATION_Call`
378 * @param am the message
379 * @return #GNUNET_OK (always well-formed)
380 */
381static int
382check_call_audio (void *cls,
383 const struct ClientAudioMessage *am)
384{
385 (void) cls;
386 (void) am;
387 /* any payload is OK */
388 return GNUNET_OK;
389}
390
391
392/**
393 * We received a `struct ClientAudioMessage`
394 *
395 * @param cls the `struct GNUNET_CONVERSATION_Call`
396 * @param am the message
397 */
398static void
399handle_call_audio (void *cls,
400 const struct ClientAudioMessage *am)
401{
402 struct GNUNET_CONVERSATION_Call *call = cls;
403
404 switch (call->state)
405 {
406 case CS_LOOKUP:
407 GNUNET_break (0);
408 fail_call (call);
409 break;
410
411 case CS_RINGING:
412 GNUNET_break (0);
413 fail_call (call);
414 break;
415
416 case CS_SUSPENDED_CALLER:
417 /* can happen: we suspended, other peer did not yet
418 learn about this. */
419 break;
420
421 case CS_SUSPENDED_CALLEE:
422 case CS_SUSPENDED_BOTH:
423 /* can (rarely) also happen: other peer suspended, but cadet might
424 have had delayed data on the unreliable channel */
425 break;
426
427 case CS_ACTIVE:
428 call->speaker->play (call->speaker->cls,
429 ntohs (am->header.size) - sizeof(struct
430 ClientAudioMessage),
431 &am[1]);
432 break;
433
434 case CS_SHUTDOWN:
435 GNUNET_CONVERSATION_call_stop (call);
436 break;
437 }
438}
439
440
441/**
442 * Iterator called on obtained result for a GNS lookup.
443 *
444 * @param cls closure with the `struct GNUNET_CONVERSATION_Call`
445 * @param was_gns #GNUNET_NO if name was not a GNS name
446 * @param rd_count number of records in @a rd
447 * @param rd the records in reply
448 */
449static void
450handle_gns_response (void *cls,
451 int was_gns,
452 uint32_t rd_count,
453 const struct GNUNET_GNSRECORD_Data *rd)
454{
455 struct GNUNET_CONVERSATION_Call *call = cls;
456 struct GNUNET_MQ_Envelope *e;
457 struct ClientCallMessage *ccm;
458 const struct GNUNET_CRYPTO_PrivateKey *caller_id;
459 size_t key_len;
460
461 (void) was_gns;
462 GNUNET_break (NULL != call->gns_lookup);
463 GNUNET_break (CS_LOOKUP == call->state);
464 call->gns_lookup = NULL;
465 for (uint32_t i = 0; i < rd_count; i++)
466 {
467 if (GNUNET_GNSRECORD_TYPE_PHONE == rd[i].record_type)
468 {
469 if (rd[i].data_size != sizeof(struct GNUNET_CONVERSATION_PhoneRecord))
470 {
471 GNUNET_break_op (0);
472 continue;
473 }
474 GNUNET_memcpy (&call->phone_record,
475 rd[i].data,
476 rd[i].data_size);
477 caller_id = GNUNET_IDENTITY_ego_get_private_key (call->caller_id);
478 key_len = GNUNET_CRYPTO_private_key_get_length (caller_id);
479 e = GNUNET_MQ_msg_extra (ccm, key_len,
480 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_CALL);
481 ccm->line_port = call->phone_record.line_port;
482 ccm->target = call->phone_record.peer;
483 GNUNET_CRYPTO_write_private_key_to_buffer (caller_id,
484 &ccm[1], key_len);
485 ccm->key_len = htonl (key_len);
486 GNUNET_MQ_send (call->mq,
487 e);
488 call->state = CS_RINGING;
489 call->event_handler (call->event_handler_cls,
490 GNUNET_CONVERSATION_EC_CALL_RINGING);
491 return;
492 }
493 }
494 /* not found */
495 call->event_handler (call->event_handler_cls,
496 GNUNET_CONVERSATION_EC_CALL_GNS_FAIL);
497 GNUNET_CONVERSATION_call_stop (call);
498}
499
500
501/**
502 * We encountered an error talking with the conversation service.
503 *
504 * @param cls the `struct GNUNET_CONVERSATION_Call`
505 * @param error details about the error
506 */
507static void
508call_error_handler (void *cls,
509 enum GNUNET_MQ_Error error)
510{
511 struct GNUNET_CONVERSATION_Call *call = cls;
512
513 (void) error;
514 if (CS_SHUTDOWN == call->state)
515 {
516 GNUNET_CONVERSATION_call_stop (call);
517 return;
518 }
519 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
520 _ (
521 "Connection to conversation service lost, trying to reconnect\n"));
522 fail_call (call);
523}
524
525
526/**
527 * The call got disconnected, destroy the handle.
528 *
529 * @param call call to reconnect
530 */
531static void
532fail_call (struct GNUNET_CONVERSATION_Call *call)
533{
534 if (CS_ACTIVE == call->state)
535 {
536 call->speaker->disable_speaker (call->speaker->cls);
537 call->mic->disable_microphone (call->mic->cls);
538 }
539 if (NULL != call->mq)
540 {
541 GNUNET_MQ_destroy (call->mq);
542 call->mq = NULL;
543 }
544 call->state = CS_SHUTDOWN;
545 call->event_handler (call->event_handler_cls,
546 GNUNET_CONVERSATION_EC_CALL_ERROR);
547 GNUNET_CONVERSATION_call_stop (call);
548}
549
550
551/**
552 * Call the phone of another user.
553 *
554 * @param cfg configuration to use, specifies our phone service
555 * @param caller_id identity of the caller
556 * @param callee GNS name of the callee (used to locate the callee's record)
557 * @param speaker speaker to use (will be used automatically immediately once the
558 * #GNUNET_CONVERSATION_EC_CALL_PICKED_UP event is generated); we will NOT generate
559 * a ring tone on the speaker
560 * @param mic microphone to use (will be used automatically immediately once the
561 * #GNUNET_CONVERSATION_EC_CALL_PICKED_UP event is generated)
562 * @param event_handler how to notify the owner of the phone about events
563 * @param event_handler_cls closure for @a event_handler
564 * @return handle for the call, NULL on hard errors
565 */
566struct GNUNET_CONVERSATION_Call *
567GNUNET_CONVERSATION_call_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
568 struct GNUNET_IDENTITY_Ego *caller_id,
569 const char *callee,
570 struct GNUNET_SPEAKER_Handle *speaker,
571 struct GNUNET_MICROPHONE_Handle *mic,
572 GNUNET_CONVERSATION_CallEventHandler
573 event_handler,
574 void *event_handler_cls)
575{
576 struct GNUNET_CONVERSATION_Call *call
577 = GNUNET_new (struct GNUNET_CONVERSATION_Call);
578 struct GNUNET_MQ_MessageHandler handlers[] = {
579 GNUNET_MQ_hd_fixed_size (call_suspend,
580 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
581 struct ClientPhoneSuspendMessage,
582 call),
583 GNUNET_MQ_hd_fixed_size (call_resume,
584 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
585 struct ClientPhoneResumeMessage,
586 call),
587 GNUNET_MQ_hd_fixed_size (call_picked_up,
588 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP,
589 struct ClientPhonePickedupMessage,
590 call),
591 GNUNET_MQ_hd_fixed_size (call_hangup,
592 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
593 struct ClientPhoneHangupMessage,
594 call),
595 GNUNET_MQ_hd_var_size (call_audio,
596 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
597 struct ClientAudioMessage,
598 call),
599 GNUNET_MQ_handler_end ()
600 };
601
602 call->mq = GNUNET_CLIENT_connect (cfg,
603 "conversation",
604 handlers,
605 &call_error_handler,
606 call);
607 if (NULL == call->mq)
608 {
609 GNUNET_break (0);
610 GNUNET_free (call);
611 return NULL;
612 }
613 call->cfg = cfg;
614 call->caller_id = caller_id;
615 call->callee = GNUNET_strdup (callee);
616 call->speaker = speaker;
617 call->mic = mic;
618 call->event_handler = event_handler;
619 call->event_handler_cls = event_handler_cls;
620 call->gns = GNUNET_GNS_connect (cfg);
621 if (NULL == call->gns)
622 {
623 GNUNET_CONVERSATION_call_stop (call);
624 return NULL;
625 }
626 call->state = CS_LOOKUP;
627 call->gns_lookup = GNUNET_GNS_lookup_with_tld (call->gns,
628 call->callee,
629 GNUNET_GNSRECORD_TYPE_PHONE,
630 GNUNET_NO,
631 &handle_gns_response,
632 call);
633 if (NULL == call->gns_lookup)
634 {
635 GNUNET_CONVERSATION_call_stop (call);
636 return NULL;
637 }
638 return call;
639}
640
641
642/**
643 * Terminate a call. The call may be ringing or ready at this time.
644 *
645 * @param call call to terminate
646 */
647void
648GNUNET_CONVERSATION_call_stop (struct GNUNET_CONVERSATION_Call *call)
649{
650 if ((NULL != call->speaker) &&
651 (CS_ACTIVE == call->state))
652 call->speaker->disable_speaker (call->speaker->cls);
653 if ((NULL != call->mic) &&
654 (CS_ACTIVE == call->state))
655 call->mic->disable_microphone (call->mic->cls);
656 if (CS_SHUTDOWN != call->state)
657 {
658 call->state = CS_SHUTDOWN;
659 }
660 if (NULL != call->mq)
661 {
662 GNUNET_MQ_destroy (call->mq);
663 call->mq = NULL;
664 }
665 if (NULL != call->gns_lookup)
666 {
667 GNUNET_GNS_lookup_with_tld_cancel (call->gns_lookup);
668 call->gns_lookup = NULL;
669 }
670 if (NULL != call->gns)
671 {
672 GNUNET_GNS_disconnect (call->gns);
673 call->gns = NULL;
674 }
675 GNUNET_free (call->callee);
676 GNUNET_free (call);
677}
678
679
680/**
681 * Pause a call. Temporarily suspends the use of speaker and
682 * microphone.
683 *
684 * @param call call to pause
685 */
686void
687GNUNET_CONVERSATION_call_suspend (struct GNUNET_CONVERSATION_Call *call)
688{
689 struct GNUNET_MQ_Envelope *e;
690 struct ClientPhoneSuspendMessage *suspend;
691
692 GNUNET_assert ((CS_SUSPENDED_CALLEE == call->state) ||
693 (CS_ACTIVE == call->state));
694 if (CS_ACTIVE == call->state)
695 {
696 call->speaker->disable_speaker (call->speaker->cls);
697 call->mic->disable_microphone (call->mic->cls);
698 }
699 call->speaker = NULL;
700 call->mic = NULL;
701 e = GNUNET_MQ_msg (suspend,
702 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
703 GNUNET_MQ_send (call->mq,
704 e);
705 if (CS_SUSPENDED_CALLER == call->state)
706 call->state = CS_SUSPENDED_BOTH;
707 else
708 call->state = CS_SUSPENDED_CALLER;
709}
710
711
712/**
713 * Resumes a call after #GNUNET_CONVERSATION_call_suspend.
714 *
715 * @param call call to resume
716 * @param speaker speaker to use
717 * a ring tone on the speaker
718 * @param mic microphone to use
719 */
720void
721GNUNET_CONVERSATION_call_resume (struct GNUNET_CONVERSATION_Call *call,
722 struct GNUNET_SPEAKER_Handle *speaker,
723 struct GNUNET_MICROPHONE_Handle *mic)
724{
725 struct GNUNET_MQ_Envelope *e;
726 struct ClientPhoneResumeMessage *resume;
727
728 GNUNET_assert ((CS_SUSPENDED_CALLER == call->state) ||
729 (CS_SUSPENDED_BOTH == call->state));
730 e = GNUNET_MQ_msg (resume, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
731 GNUNET_MQ_send (call->mq, e);
732 call->speaker = speaker;
733 call->mic = mic;
734 if (CS_SUSPENDED_CALLER == call->state)
735 {
736 call->state = CS_ACTIVE;
737 call->speaker->enable_speaker (call->speaker->cls);
738 call->mic->enable_microphone (call->mic->cls,
739 &transmit_call_audio,
740 call);
741 }
742 else
743 {
744 call->state = CS_SUSPENDED_CALLEE;
745 }
746}
747
748
749/* end of conversation_api_call.c */