aboutsummaryrefslogtreecommitdiff
path: root/src/contrib/service/conversation
diff options
context:
space:
mode:
Diffstat (limited to 'src/contrib/service/conversation')
-rw-r--r--src/contrib/service/conversation/.gitignore8
-rw-r--r--src/contrib/service/conversation/Makefile.am263
-rw-r--r--src/contrib/service/conversation/build_gst_test.sh9
-rw-r--r--src/contrib/service/conversation/conversation.conf.in29
-rw-r--r--src/contrib/service/conversation/conversation.h407
-rw-r--r--src/contrib/service/conversation/conversation_api.c897
-rw-r--r--src/contrib/service/conversation/conversation_api_call.c749
-rw-r--r--src/contrib/service/conversation/displaydot.sh3
-rw-r--r--src/contrib/service/conversation/gnunet-conversation-test.c265
-rw-r--r--src/contrib/service/conversation/gnunet-conversation.c1232
-rw-r--r--src/contrib/service/conversation/gnunet-helper-audio-playback-gst.c405
-rw-r--r--src/contrib/service/conversation/gnunet-helper-audio-playback.c888
-rw-r--r--src/contrib/service/conversation/gnunet-helper-audio-record-gst.c393
-rw-r--r--src/contrib/service/conversation/gnunet-helper-audio-record.c807
-rw-r--r--src/contrib/service/conversation/gnunet-service-conversation.c1461
-rw-r--r--src/contrib/service/conversation/gnunet_gst.c1154
-rw-r--r--src/contrib/service/conversation/gnunet_gst.h62
-rw-r--r--src/contrib/service/conversation/gnunet_gst_def.h219
-rw-r--r--src/contrib/service/conversation/gnunet_gst_test.c135
-rw-r--r--src/contrib/service/conversation/mediahelper.conf7
-rw-r--r--src/contrib/service/conversation/meson.build203
-rw-r--r--src/contrib/service/conversation/microphone.c200
-rw-r--r--src/contrib/service/conversation/plugin_gnsrecord_conversation.c262
-rw-r--r--src/contrib/service/conversation/speaker.c189
-rw-r--r--src/contrib/service/conversation/test.sh4
-rw-r--r--src/contrib/service/conversation/test_conversation.conf12
-rw-r--r--src/contrib/service/conversation/test_conversation_api.c510
-rw-r--r--src/contrib/service/conversation/test_conversation_api_reject.c363
-rw-r--r--src/contrib/service/conversation/test_conversation_api_twocalls.c642
29 files changed, 11778 insertions, 0 deletions
diff --git a/src/contrib/service/conversation/.gitignore b/src/contrib/service/conversation/.gitignore
new file mode 100644
index 000000000..e74e259bf
--- /dev/null
+++ b/src/contrib/service/conversation/.gitignore
@@ -0,0 +1,8 @@
1gnunet-service-conversation
2gnunet-conversation
3gnunet-conversation-test
4gnunet-helper-audio-playback
5gnunet-helper-audio-record
6test_conversation_api
7test_conversation_api_reject
8test_conversation_api_twocalls
diff --git a/src/contrib/service/conversation/Makefile.am b/src/contrib/service/conversation/Makefile.am
new file mode 100644
index 000000000..b573208e6
--- /dev/null
+++ b/src/contrib/service/conversation/Makefile.am
@@ -0,0 +1,263 @@
1# This Makefile.am is in the public domain
2SUBDIRS = .
3
4plugindir = $(libdir)/gnunet
5
6AM_CPPFLAGS = \
7 $(GNUNET_CPPFLAGS) \
8 -I$(top_srcdir)/src/include \
9 -I$(top_srcdir)
10
11
12if BUILD_CONVERSATION
13lib_LTLIBRARIES = \
14 libgnunetmicrophone.la \
15 libgnunetspeaker.la \
16 libgnunetconversation.la
17
18bin_PROGRAMS = \
19 gnunet-conversation-test \
20 gnunet-conversation
21
22libexec_PROGRAMS = \
23 gnunet-service-conversation
24
25check_PROGRAMS = \
26 test_conversation_api \
27 test_conversation_api_reject \
28 test_conversation_api_twocalls
29
30pkgcfg_DATA = conversation.conf
31endif
32
33pkgcfgdir= $(pkgdatadir)/config.d/
34
35libexecdir= $(pkglibdir)/libexec/
36
37plugin_LTLIBRARIES = \
38 libgnunet_plugin_gnsrecord_conversation.la
39
40
41libgnunet_plugin_gnsrecord_conversation_la_SOURCES = \
42 plugin_gnsrecord_conversation.c
43libgnunet_plugin_gnsrecord_conversation_la_LIBADD = \
44 $(top_builddir)/src/lib/util/libgnunetutil.la \
45 $(LTLIBINTL)
46libgnunet_plugin_gnsrecord_conversation_la_LDFLAGS = \
47 $(GN_PLUGIN_LDFLAGS)
48
49
50libgnunetmicrophone_la_SOURCES = \
51 microphone.c
52libgnunetmicrophone_la_LIBADD = \
53 $(top_builddir)/src/lib/util/libgnunetutil.la
54libgnunetmicrophone_la_LDFLAGS = \
55 $(GN_LIB_LDFLAGS) \
56 -version-info 0:0:0
57
58libgnunetspeaker_la_SOURCES = \
59 speaker.c
60libgnunetspeaker_la_LIBADD = \
61 $(top_builddir)/src/lib/util/libgnunetutil.la
62libgnunetspeaker_la_LDFLAGS = \
63 $(GN_LIB_LDFLAGS) \
64 -version-info 0:0:0
65
66
67libgnunetconversation_la_SOURCES = \
68 conversation_api.c \
69 conversation_api_call.c \
70 conversation.h
71libgnunetconversation_la_LIBADD = \
72 $(top_builddir)/src/service/gns/libgnunetgns.la \
73 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
74 $(top_builddir)/src/service/namestore/libgnunetnamestore.la \
75 $(top_builddir)/src/service/identity/libgnunetidentity.la \
76 $(top_builddir)/src/lib/util/libgnunetutil.la
77
78libgnunetconversation_la_LDFLAGS = \
79 $(GN_LIB_LDFLAGS) \
80 -version-info 0:0:0
81
82
83if BUILD_PULSE_HELPERS
84AUDIO_HELPER_RECD=gnunet-helper-audio-record
85AUDIO_HELPER_PLAY=gnunet-helper-audio-playback
86AUDIO_TESTS=$(check_PROGRAMS)
87else
88if BUILD_GST_HELPERS
89AUDIO_HELPER_RECD=gnunet-helper-audio-record
90AUDIO_HELPER_PLAY=gnunet-helper-audio-playback
91AUDIO_TESTS=$(check_PROGRAMS)
92else
93if BUILD_EXPERIMENTAL_HELPERS
94AUDIO_HELPER_RECD=gnunet-helper-audio-record
95AUDIO_HELPER_PLAY=gnunet-helper-audio-playback
96AUDIO_TESTS=$(check_PROGRAMS)
97endif
98endif
99endif
100
101if BUILD_CONVERSATION
102libexec_PROGRAMS += \
103 $(AUDIO_HELPER_RECD) \
104 $(AUDIO_HELPER_PLAY)
105endif
106
107AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
108if ENABLE_TEST_RUN
109TESTS = $(AUDIO_TESTS)
110endif
111
112if BUILD_PULSE_HELPERS
113gnunet_helper_audio_record_SOURCES = \
114 gnunet-helper-audio-record.c
115gnunet_helper_audio_record_LDADD = \
116 $(top_builddir)/src/lib/util/libgnunetutil.la \
117 -lpulse -lopus -logg \
118 $(INTLLIBS)
119#gnunet_helper_audio_record_LDFLAGS = \
120#
121
122gnunet_helper_audio_playback_SOURCES = \
123 gnunet-helper-audio-playback.c
124gnunet_helper_audio_playback_LDADD = \
125 $(top_builddir)/src/lib/util/libgnunetutil.la \
126 -lpulse -lopus -logg \
127 $(INTLLIBS)
128#gnunet_helper_audio_playback_LDFLAGS = \
129#
130else
131if BUILD_GST_HELPERS
132gnunet_helper_audio_record_SOURCES = \
133 gnunet-helper-audio-record-gst.c
134gnunet_helper_audio_record_LDADD = \
135 $(top_builddir)/src/lib/util/libgnunetutil.la \
136 $(GST_LIBS) \
137 $(INTLLIBS)
138gnunet_helper_audio_record_LDFLAGS = \
139 $(GST_LDFLAGS)
140gnunet_helper_audio_record_CFLAGS = \
141 $(GST_CFLAGS)
142
143gnunet_helper_audio_playback_SOURCES = \
144 gnunet-helper-audio-playback-gst.c
145gnunet_helper_audio_playback_LDADD = \
146 $(top_builddir)/src/lib/util/libgnunetutil.la \
147 $(GST_LIBS) \
148 $(INTLLIBS)
149gnunet_helper_audio_playback_LDFLAGS = \
150 $(GST_LDFLAGS)
151gnunet_helper_audio_playback_CFLAGS = \
152 $(GST_CFLAGS) -DIS_SPEAKER
153else
154if BUILD_EXPERIMENTAL_HELPERS
155gnunet_helper_audio_record_SOURCES = \
156 gnunet_gst_test.c gnunet_gst.c
157gnunet_helper_audio_record_LDADD = \
158 $(top_builddir)/src/lib/util/libgnunetutil.la \
159 $(GST_LIBS) \
160 $(INTLLIBS)
161gnunet_helper_audio_record_LDFLAGS = \
162 $(GST_LDFLAGS)
163gnunet_helper_audio_record_CFLAGS = \
164 $(GST_CFLAGS) -DIS_MIC
165
166gnunet_helper_audio_playback_SOURCES = \
167 gnunet_gst_test.c gnunet_gst.c
168gnunet_helper_audio_playback_LDADD = \
169 $(top_builddir)/src/lib/util/libgnunetutil.la \
170 $(GST_LIBS) \
171 $(INTLLIBS)
172gnunet_helper_audio_playback_LDFLAGS = \
173 $(GST_LDFLAGS)
174gnunet_helper_audio_playback_CFLAGS = \
175 $(GST_CFLAGS) -DIS_SPEAKER
176endif
177endif
178endif
179
180gnunet_service_conversation_SOURCES = \
181 gnunet-service-conversation.c
182gnunet_service_conversation_LDADD = \
183 libgnunetconversation.la \
184 libgnunetspeaker.la \
185 libgnunetmicrophone.la \
186 $(top_builddir)/src/service/cadet/libgnunetcadet.la \
187 $(top_builddir)/src/lib/util/libgnunetutil.la \
188 $(top_builddir)/src/service/identity/libgnunetidentity.la \
189 $(INTLLIBS)
190#gnunet_service_conversation_LDFLAGS = \
191#
192
193gnunet_conversation_SOURCES = \
194 gnunet-conversation.c
195gnunet_conversation_LDADD = \
196 libgnunetmicrophone.la \
197 libgnunetspeaker.la \
198 libgnunetconversation.la \
199 $(top_builddir)/src/service/gns/libgnunetgns.la \
200 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
201 $(top_builddir)/src/service/namestore/libgnunetnamestore.la \
202 $(top_builddir)/src/service/identity/libgnunetidentity.la \
203 $(top_builddir)/src/lib/util/libgnunetutil.la \
204 $(INTLLIBS)
205#gnunet_conversation_LDFLAGS = \
206#
207
208gnunet_conversation_test_SOURCES = \
209 gnunet-conversation-test.c
210gnunet_conversation_test_LDADD = \
211 libgnunetmicrophone.la \
212 libgnunetspeaker.la \
213 $(top_builddir)/src/lib/util/libgnunetutil.la \
214 $(INTLLIBS)
215#gnunet_conversation_test_LDFLAGS = \
216#
217
218
219test_conversation_api_SOURCES = \
220 test_conversation_api.c
221test_conversation_api_LDADD = \
222 libgnunetconversation.la \
223 libgnunetspeaker.la \
224 libgnunetmicrophone.la \
225 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
226 $(top_builddir)/src/service/namestore/libgnunetnamestore.la \
227 $(top_builddir)/src/service/identity/libgnunetidentity.la \
228 $(top_builddir)/src/service/testing/libgnunettesting.la \
229 $(top_builddir)/src/lib/util/libgnunetutil.la
230test_conversation_api_LDFLAGS = \
231 -export-dynamic
232
233test_conversation_api_twocalls_SOURCES = \
234 test_conversation_api_twocalls.c
235test_conversation_api_twocalls_LDADD = \
236 libgnunetconversation.la \
237 libgnunetspeaker.la \
238 libgnunetmicrophone.la \
239 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
240 $(top_builddir)/src/service/namestore/libgnunetnamestore.la \
241 $(top_builddir)/src/service/identity/libgnunetidentity.la \
242 $(top_builddir)/src/service/testing/libgnunettesting.la \
243 $(top_builddir)/src/lib/util/libgnunetutil.la
244test_conversation_api_twocalls_LDFLAGS = \
245 -export-dynamic
246
247test_conversation_api_reject_SOURCES = \
248 test_conversation_api_reject.c
249test_conversation_api_reject_LDADD = \
250 libgnunetconversation.la \
251 libgnunetspeaker.la \
252 libgnunetmicrophone.la \
253 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
254 $(top_builddir)/src/service/namestore/libgnunetnamestore.la \
255 $(top_builddir)/src/service/identity/libgnunetidentity.la \
256 $(top_builddir)/src/service/testing/libgnunettesting.la \
257 $(top_builddir)/src/lib/util/libgnunetutil.la
258test_conversation_api_reject_LDFLAGS = \
259 -export-dynamic
260
261
262
263EXTRA_DIST = test_conversation.conf
diff --git a/src/contrib/service/conversation/build_gst_test.sh b/src/contrib/service/conversation/build_gst_test.sh
new file mode 100644
index 000000000..1feb9e1c4
--- /dev/null
+++ b/src/contrib/service/conversation/build_gst_test.sh
@@ -0,0 +1,9 @@
1#!/bin/bash
2
3colorgcc -DIS_MIC -g gnunet_gst_test.c gnunet_gst.c -o gnunet-helper-audio-record-experimental `pkg-config --cflags --libs gstreamer-app-1.0 gnunetutil gnunetconversation gnunetenv gstreamer-app-1.0 gstreamer-1.0 gstreamer-audio-1.0 gstreamer-pbutils-1.0 gstreamer-video-1.0` -O0 -march=native -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-conversion -Wformat -Wformat-security -fstack-protector -D_FORTIFY_SOURCE=2 -std=c99 -D_GNU_SOURCE
4
5colorgcc -DIS_SPEAKER -g gnunet_gst_test.c gnunet_gst.c -o gnunet-helper-audio-playback-experimental `pkg-config --cflags --libs gstreamer-app-1.0 gnunetutil gnunetconversation gnunetenv gstreamer-app-1.0 gstreamer-1.0 gstreamer-audio-1.0 gstreamer-pbutils-1.0 gstreamer-video-1.0` -O0 -march=native -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-conversion -Wformat -Wformat-security -fstack-protector -D_FORTIFY_SOURCE=2 -std=c99 -D_GNU_SOURCE
6
7
8
9#colorgcc -g gnunet_gst_test.c gnunet_gst.c -o gnunet_gst_test `pkg-config --cflags --libs gstreamer-app-1.0 gstreamer-1.0 gstreamer-audio-1.0 gstreamer-pbutils-1.0 gstreamer-video-1.0` -O0 -march=native -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-conversion -Wpedantic -Wformat -Wformat-security -fstack-protector -D_FORTIFY_SOURCE=2 -std=c99 -D_GNU_SOURCE
diff --git a/src/contrib/service/conversation/conversation.conf.in b/src/contrib/service/conversation/conversation.conf.in
new file mode 100644
index 000000000..b28fb6e1f
--- /dev/null
+++ b/src/contrib/service/conversation/conversation.conf.in
@@ -0,0 +1,29 @@
1[conversation]
2START_ON_DEMAND = @START_ON_DEMAND@
3BINARY = gnunet-service-conversation
4UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-conversation.sock
5HOSTNAME = localhost
6@UNIXONLY@ PORT = 2120
7
8# Desired phone line. Change if multiple users are using
9# the same peer and we thus need disjoint phone lines.
10LINE = 1
11
12# Should the phone record be private? (only set to YES if
13# you really know what you are doing, you will then likely
14# not be able to receive calls).
15RECORD_IS_PRIVATE = NO
16
17# How long should phone records remain published in GNS?
18# A long expiration time has the advantage that the phone
19# is more likely found and/or resolved faster, OTOH it
20# then takes longer to change the phone line if necessary.
21# The default should be fine for most users.
22RECORD_EXPIRATION = 1 day
23
24
25ACCEPT_FROM = 127.0.0.1;
26ACCEPT_FROM6 = ::1;
27UNIX_MATCH_UID = NO
28UNIX_MATCH_GID = YES
29
diff --git a/src/contrib/service/conversation/conversation.h b/src/contrib/service/conversation/conversation.h
new file mode 100644
index 000000000..ee4ca372c
--- /dev/null
+++ b/src/contrib/service/conversation/conversation.h
@@ -0,0 +1,407 @@
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.h
23 * @brief constants for network protocols
24 * @author Siomon Dieterle
25 * @author Andreas Fuchs
26 */
27#ifndef CONVERSATION_H
28#define CONVERSATION_H
29
30#include "gnunet_identity_service.h"
31
32#ifdef __cplusplus
33extern "C"
34{
35#if 0 /* keep Emacsens' auto-indent happy */
36}
37#endif
38#endif
39
40
41#define MAX_TRANSMIT_DELAY GNUNET_TIME_relative_multiply ( \
42 GNUNET_TIME_UNIT_SECONDS, 60)
43
44
45/**
46 * Highest bit in a 32-bit unsigned integer,
47 * bit set if we are making an outgoing call,
48 * bit unset for local lines.
49 */
50#define HIGH_BIT ((uint32_t) (1LL << 31))
51
52GNUNET_NETWORK_STRUCT_BEGIN
53
54
55/**
56 * Message to transmit the audio (between client and helpers).
57 */
58struct AudioMessage
59{
60 /**
61 * Type is #GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO
62 */
63 struct GNUNET_MessageHeader header;
64
65 /* followed by audio data */
66};
67
68
69/**
70 * Client -> Service message to register a phone.
71 */
72struct ClientPhoneRegisterMessage
73{
74 /**
75 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_REGISTER
76 */
77 struct GNUNET_MessageHeader header;
78
79 /**
80 * Always zero.
81 */
82 uint32_t reserved GNUNET_PACKED;
83
84 /**
85 * Phone line / CADET port to register.
86 */
87 struct GNUNET_HashCode line_port;
88};
89
90
91/**
92 * Service -> Client message for phone is ringing.
93 */
94struct ClientPhoneRingMessage
95{
96 /**
97 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RING
98 */
99 struct GNUNET_MessageHeader header;
100
101 /**
102 * CID, internal caller ID number used in the future to identify
103 * which active call we are talking about.
104 */
105 uint32_t cid GNUNET_PACKED;
106
107 /**
108 * The identity key length
109 */
110 uint32_t key_len;
111
112 /**
113 * followed by who is calling us?, a public key
114 */
115};
116
117
118/**
119 * Service <-> Client message for phone was suspended.
120 */
121struct ClientPhoneSuspendMessage
122{
123 /**
124 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND
125 */
126 struct GNUNET_MessageHeader header;
127
128 /**
129 * CID, internal caller ID to identify which active call we are
130 * talking about.
131 */
132 uint32_t cid GNUNET_PACKED;
133};
134
135
136/**
137 * Service <-> Client message for phone was resumed.
138 */
139struct ClientPhoneResumeMessage
140{
141 /**
142 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME
143 */
144 struct GNUNET_MessageHeader header;
145
146 /**
147 * CID, internal caller ID to identify which active call we are
148 * talking about.
149 */
150 uint32_t cid GNUNET_PACKED;
151};
152
153
154/**
155 * Client -> Service pick up phone that is ringing.
156 */
157struct ClientPhonePickupMessage
158{
159 /**
160 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICK_UP
161 */
162 struct GNUNET_MessageHeader header;
163
164 /**
165 * CID, internal caller ID to identify which active call we are
166 * talking about.
167 */
168 uint32_t cid GNUNET_PACKED;
169};
170
171
172/**
173 * Client <-> Service hang up phone that may or may not be ringing.
174 * Also sent in response to a (failed) `struct ClientCallMessage`.
175 */
176struct ClientPhoneHangupMessage
177{
178 /**
179 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP
180 */
181 struct GNUNET_MessageHeader header;
182
183 /**
184 * CID, internal caller ID to identify which active call we are
185 * talking about.
186 */
187 uint32_t cid GNUNET_PACKED;
188};
189
190
191/**
192 * Message Client <-> Service to transmit the audio.
193 */
194struct ClientAudioMessage
195{
196 /**
197 * Type is #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO
198 */
199 struct GNUNET_MessageHeader header;
200
201 /**
202 * CID, internal caller ID to identify which active call we are
203 * sending data to.
204 */
205 uint32_t cid GNUNET_PACKED;
206
207 /* followed by audio data */
208};
209
210
211/**
212 * Client -> Service message to call a phone.
213 */
214struct ClientCallMessage
215{
216 /**
217 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_CALL
218 */
219 struct GNUNET_MessageHeader header;
220
221 /**
222 * Always zero.
223 */
224 uint32_t reserved GNUNET_PACKED;
225
226 /**
227 * Which peer is hosting the line?
228 */
229 struct GNUNET_PeerIdentity target;
230
231 /**
232 * Which phone line to call at the peer?
233 */
234 struct GNUNET_HashCode line_port;
235
236 /**
237 * The identity key length
238 */
239 uint32_t key_len;
240
241 /**
242 * followed by the identity of the caller.
243 */
244};
245
246
247/**
248 * Service -> Client: other peer has picked up the phone, we are
249 * now talking.
250 */
251struct ClientPhonePickedupMessage
252{
253 /**
254 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP
255 */
256 struct GNUNET_MessageHeader header;
257
258 /**
259 * Call ID of the corresponding
260 * #GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_CALL
261 */
262 uint32_t cid GNUNET_PACKED;
263};
264
265
266/**
267 * Information signed in a `struct CadetPhoneRingMessage`
268 * whereby the caller self-identifies to the receiver.
269 */
270struct CadetPhoneRingInfoPS
271{
272 /**
273 * Purpose for the signature, must be
274 * #GNUNET_SIGNATURE_PURPOSE_CONVERSATION_RING.
275 */
276 struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
277
278 /**
279 * Which port did the call go to?
280 */
281 struct GNUNET_HashCode line_port;
282
283 /**
284 * Which peer is the call for?
285 */
286 struct GNUNET_PeerIdentity target_peer;
287
288 /**
289 * When does the signature expire?
290 */
291 struct GNUNET_TIME_AbsoluteNBO expiration_time;
292};
293
294
295/**
296 * Cadet message to make a phone ring. Sent to the port
297 * of the respective phone.
298 */
299struct CadetPhoneRingMessage
300{
301 /**
302 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RING
303 */
304 struct GNUNET_MessageHeader header;
305
306 /**
307 * Always zero.
308 */
309 uint32_t reserved GNUNET_PACKED;
310
311 /**
312 * When does the signature expire?
313 */
314 struct GNUNET_TIME_AbsoluteNBO expiration_time;
315
316 /**
317 * The length of the key
318 */
319 uint32_t key_len;
320
321 /**
322 * The length of the signature
323 */
324 uint32_t sig_len;
325
326 /**
327 * Followed by the public key of who is calling us? (also who is signing).
328 * followed by the signature over a `struct CadetPhoneRingInfoPS`
329 */
330};
331
332
333/**
334 * Cadet message for hanging up.
335 */
336struct CadetPhoneHangupMessage
337{
338 /**
339 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_HANG_UP
340 */
341 struct GNUNET_MessageHeader header;
342};
343
344
345/**
346 * Cadet message for picking up.
347 */
348struct CadetPhonePickupMessage
349{
350 /**
351 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_PICK_UP
352 */
353 struct GNUNET_MessageHeader header;
354};
355
356
357/**
358 * Cadet message for phone suspended.
359 */
360struct CadetPhoneSuspendMessage
361{
362 /**
363 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_SUSPEND
364 */
365 struct GNUNET_MessageHeader header;
366};
367
368
369/**
370 * Cadet message for phone resumed.
371 */
372struct CadetPhoneResumeMessage
373{
374 /**
375 * Type is: #GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RESUME
376 */
377 struct GNUNET_MessageHeader header;
378};
379
380
381/**
382 * Cadet message to transmit the audio.
383 */
384struct CadetAudioMessage
385{
386 /**
387 * Type is #GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_AUDIO
388 */
389 struct GNUNET_MessageHeader header;
390
391 /* followed by audio data */
392};
393
394
395GNUNET_NETWORK_STRUCT_END
396
397
398#if 0 /* keep Emacsens' auto-indent happy */
399{
400#endif
401#ifdef __cplusplus
402}
403#endif
404
405/* ifndef GNUNET_PROTOCOLS_CONVERSATION_H */
406#endif
407/* end of gnunet_protocols_conversation.h */
diff --git a/src/contrib/service/conversation/conversation_api.c b/src/contrib/service/conversation/conversation_api.c
new file mode 100644
index 000000000..ed22bf9cf
--- /dev/null
+++ b/src/contrib/service/conversation/conversation_api.c
@@ -0,0 +1,897 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2014 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.c
23 * @brief phone and caller 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 "conversation.h"
31
32
33/**
34 * Possible states of a caller.
35 */
36enum CallerState
37{
38 /**
39 * The phone is ringing (user knows about incoming call).
40 */
41 CS_RINGING,
42
43 /**
44 * The phone is in an active conversation.
45 */
46 CS_ACTIVE,
47
48 /**
49 * We suspended the conversation.
50 */
51 CS_CALLEE_SUSPENDED,
52
53 /**
54 * Caller suspended the conversation.
55 */
56 CS_CALLER_SUSPENDED,
57
58 /**
59 * Both sides suspended the conversation.
60 */
61 CS_BOTH_SUSPENDED
62};
63
64
65/**
66 * A caller is the handle we have for an incoming call.
67 */
68struct GNUNET_CONVERSATION_Caller
69{
70 /**
71 * We keep all callers in a DLL.
72 */
73 struct GNUNET_CONVERSATION_Caller *next;
74
75 /**
76 * We keep all callers in a DLL.
77 */
78 struct GNUNET_CONVERSATION_Caller *prev;
79
80 /**
81 * Our phone.
82 */
83 struct GNUNET_CONVERSATION_Phone *phone;
84
85 /**
86 * Function to call for phone events.
87 */
88 GNUNET_CONVERSATION_CallerEventHandler event_handler;
89
90 /**
91 * Closure for @e event_handler
92 */
93 void *event_handler_cls;
94
95 /**
96 * Speaker, or NULL if none is attached.
97 */
98 struct GNUNET_SPEAKER_Handle *speaker;
99
100 /**
101 * Microphone, or NULL if none is attached.
102 */
103 struct GNUNET_MICROPHONE_Handle *mic;
104
105 /**
106 * Identity of the person calling us.
107 */
108 struct GNUNET_CRYPTO_PublicKey caller_id;
109
110 /**
111 * Internal handle to identify the caller with the service.
112 */
113 uint32_t cid;
114
115 /**
116 * State machine for the phone.
117 */
118 enum CallerState state;
119};
120
121
122/**
123 * Possible states of a phone.
124 */
125enum PhoneState
126{
127 /**
128 * We still need to register the phone.
129 */
130 PS_REGISTER = 0,
131
132 /**
133 * We are waiting for calls.
134 */
135 PS_READY
136};
137
138
139/**
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.
149 */
150struct GNUNET_CONVERSATION_Phone
151{
152 /**
153 * Our configuration.
154 */
155 const struct GNUNET_CONFIGURATION_Handle *cfg;
156
157 /**
158 * We keep all callers in a DLL.
159 */
160 struct GNUNET_CONVERSATION_Caller *caller_head;
161
162 /**
163 * We keep all callers in a DLL.
164 */
165 struct GNUNET_CONVERSATION_Caller *caller_tail;
166
167 /**
168 * Function to call for phone events.
169 */
170 GNUNET_CONVERSATION_PhoneEventHandler event_handler;
171
172 /**
173 * Closure for @e event_handler
174 */
175 void *event_handler_cls;
176
177 /**
178 * Connection to NAMESTORE (for reverse lookup).
179 */
180 struct GNUNET_NAMESTORE_Handle *ns;
181
182 /**
183 * Handle for transmitting to the CONVERSATION service.
184 */
185 struct GNUNET_MQ_Handle *mq;
186
187 /**
188 * This phone's record.
189 */
190 struct GNUNET_CONVERSATION_PhoneRecord my_record;
191
192 /**
193 * My GNS zone.
194 */
195 struct GNUNET_CRYPTO_PrivateKey my_zone;
196
197 /**
198 * State machine for the phone.
199 */
200 enum PhoneState state;
201};
202
203
204/**
205 * The phone got disconnected, reconnect to the service.
206 *
207 * @param phone phone to reconnect
208 */
209static void
210reconnect_phone (struct GNUNET_CONVERSATION_Phone *phone);
211
212
213/**
214 * Process recorded audio data.
215 *
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
219 */
220static void
221transmit_phone_audio (void *cls,
222 size_t data_size,
223 const void *data)
224{
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;
229
230 e = GNUNET_MQ_msg_extra (am,
231 data_size,
232 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO);
233 am->cid = caller->cid;
234 GNUNET_memcpy (&am[1],
235 data,
236 data_size);
237 GNUNET_MQ_send (phone->mq,
238 e);
239}
240
241/**
242 * We received a `struct ClientPhoneRingMessage`
243 *
244 * @param cls the `struct GNUNET_CONVERSATION_Phone`
245 * @param ring the message
246 */
247static enum GNUNET_GenericReturnValue
248check_phone_ring (void *cls,
249 const struct ClientPhoneRingMessage *ring)
250{
251 //FIXME
252 return GNUNET_OK;
253}
254/**
255 * We received a `struct ClientPhoneRingMessage`
256 *
257 * @param cls the `struct GNUNET_CONVERSATION_Phone`
258 * @param ring the message
259 */
260static void
261handle_phone_ring (void *cls,
262 const struct ClientPhoneRingMessage *ring)
263{
264 struct GNUNET_CONVERSATION_Phone *phone = cls;
265 struct GNUNET_CONVERSATION_Caller *caller;
266 struct GNUNET_CRYPTO_PublicKey caller_id;
267 size_t key_len;
268 size_t read;
269
270 key_len = ntohl (ring->key_len);
271 switch (phone->state)
272 {
273 case PS_REGISTER:
274 GNUNET_assert (0);
275 break;
276
277 case PS_READY:
278 if ((GNUNET_SYSERR ==
279 GNUNET_CRYPTO_read_public_key_from_buffer (&ring[1],
280 key_len,
281 &caller_id,
282 &read)) ||
283 (read != key_len))
284 {
285 GNUNET_break (0);
286 break;
287 }
288 caller = GNUNET_new (struct GNUNET_CONVERSATION_Caller);
289 caller->phone = phone;
290 GNUNET_CONTAINER_DLL_insert (phone->caller_head,
291 phone->caller_tail,
292 caller);
293 caller->caller_id = caller_id;
294 caller->cid = ring->cid;
295 caller->state = CS_RINGING;
296 phone->event_handler (phone->event_handler_cls,
297 GNUNET_CONVERSATION_EC_PHONE_RING,
298 caller,
299 &caller->caller_id);
300 break;
301 }
302}
303
304
305/**
306 * Find the record of the caller matching the @a cid
307 *
308 * @param phone phone to search
309 * @param cid caller ID to search for (in NBO)
310 * @return NULL if @a cid was not found
311 */
312static struct GNUNET_CONVERSATION_Caller *
313find_caller (struct GNUNET_CONVERSATION_Phone *phone,
314 uint32_t cid)
315{
316 struct GNUNET_CONVERSATION_Caller *caller;
317
318 for (caller = phone->caller_head; NULL != caller; caller = caller->next)
319 if (cid == caller->cid)
320 return caller;
321 return NULL;
322}
323
324
325/**
326 * We received a `struct ClientPhoneHangupMessage`.
327 *
328 * @param cls the `struct GNUNET_CONVERSATION_Phone *`
329 * @param hang the message
330 */
331static void
332handle_phone_hangup (void *cls,
333 const struct ClientPhoneHangupMessage *hang)
334{
335 struct GNUNET_CONVERSATION_Phone *phone = cls;
336 struct GNUNET_CONVERSATION_Caller *caller;
337
338 caller = find_caller (phone,
339 hang->cid);
340 if (NULL == caller)
341 {
342 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
343 "Received HANG_UP message for unknown caller ID %u\n",
344 (unsigned int) hang->cid);
345 return;
346 }
347
348 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
349 "Received HANG_UP message, terminating call with `%s'\n",
350 GNUNET_GNSRECORD_pkey_to_zkey (&caller->caller_id));
351 switch (caller->state)
352 {
353 case CS_RINGING:
354 phone->event_handler (phone->event_handler_cls,
355 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
356 caller,
357 &caller->caller_id);
358 break;
359
360 case CS_ACTIVE:
361 caller->speaker->disable_speaker (caller->speaker->cls);
362 caller->mic->disable_microphone (caller->mic->cls);
363 phone->event_handler (phone->event_handler_cls,
364 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
365 caller,
366 &caller->caller_id);
367 break;
368
369 case CS_CALLEE_SUSPENDED:
370 case CS_CALLER_SUSPENDED:
371 case CS_BOTH_SUSPENDED:
372 phone->event_handler (phone->event_handler_cls,
373 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
374 caller,
375 &caller->caller_id);
376 break;
377 }
378 GNUNET_CONTAINER_DLL_remove (phone->caller_head,
379 phone->caller_tail,
380 caller);
381 GNUNET_free (caller);
382}
383
384
385/**
386 * We received a `struct ClientPhoneSuspendMessage`.
387 *
388 * @param cls the `struct GNUNET_CONVERSATION_Phone`
389 * @param suspend the message
390 */
391static void
392handle_phone_suspend (void *cls,
393 const struct ClientPhoneSuspendMessage *suspend)
394{
395 struct GNUNET_CONVERSATION_Phone *phone = cls;
396 struct GNUNET_CONVERSATION_Caller *caller;
397
398 caller = find_caller (phone,
399 suspend->cid);
400 if (NULL == caller)
401 return;
402 switch (caller->state)
403 {
404 case CS_RINGING:
405 GNUNET_break_op (0);
406 break;
407
408 case CS_ACTIVE:
409 caller->state = CS_CALLER_SUSPENDED;
410 caller->speaker->disable_speaker (caller->speaker->cls);
411 caller->mic->disable_microphone (caller->mic->cls);
412 caller->event_handler (caller->event_handler_cls,
413 GNUNET_CONVERSATION_EC_CALLER_SUSPEND);
414 break;
415
416 case CS_CALLEE_SUSPENDED:
417 caller->state = CS_BOTH_SUSPENDED;
418 caller->event_handler (caller->event_handler_cls,
419 GNUNET_CONVERSATION_EC_CALLER_SUSPEND);
420 break;
421
422 case CS_CALLER_SUSPENDED:
423 case CS_BOTH_SUSPENDED:
424 GNUNET_break_op (0);
425 break;
426 }
427}
428
429
430/**
431 * We received a `struct ClientPhoneResumeMessage`.
432 *
433 * @param cls the `struct GNUNET_CONVERSATION_Phone`
434 * @param resume the message
435 */
436static void
437handle_phone_resume (void *cls,
438 const struct ClientPhoneResumeMessage *resume)
439{
440 struct GNUNET_CONVERSATION_Phone *phone = cls;
441 struct GNUNET_CONVERSATION_Caller *caller;
442
443 caller = find_caller (phone,
444 resume->cid);
445 if (NULL == caller)
446 return;
447 switch (caller->state)
448 {
449 case CS_RINGING:
450 GNUNET_break_op (0);
451 break;
452
453 case CS_ACTIVE:
454 case CS_CALLEE_SUSPENDED:
455 GNUNET_break_op (0);
456 break;
457
458 case CS_CALLER_SUSPENDED:
459 caller->state = CS_ACTIVE;
460 caller->speaker->enable_speaker (caller->speaker->cls);
461 caller->mic->enable_microphone (caller->mic->cls,
462 &transmit_phone_audio,
463 caller);
464 caller->event_handler (caller->event_handler_cls,
465 GNUNET_CONVERSATION_EC_CALLER_RESUME);
466 break;
467
468 case CS_BOTH_SUSPENDED:
469 caller->state = CS_CALLEE_SUSPENDED;
470 caller->event_handler (caller->event_handler_cls,
471 GNUNET_CONVERSATION_EC_CALLER_RESUME);
472 break;
473 }
474}
475
476
477/**
478 * We received a `struct ClientAudioMessage`, check it is well-formed.
479 *
480 * @param cls the `struct GNUNET_CONVERSATION_Phone`
481 * @param am the message
482 * @return #GNUNET_OK if @a am is well-formed
483 */
484static int
485check_phone_audio (void *cls,
486 const struct ClientAudioMessage *am)
487{
488 (void) cls;
489 (void) am;
490
491 /* any variable-size payload is OK */
492 return GNUNET_OK;
493}
494
495
496/**
497 * We received a `struct ClientAudioMessage`
498 *
499 * @param cls the `struct GNUNET_CONVERSATION_Phone`
500 * @param am the message
501 */
502static void
503handle_phone_audio (void *cls,
504 const struct ClientAudioMessage *am)
505{
506 struct GNUNET_CONVERSATION_Phone *phone = cls;
507 struct GNUNET_CONVERSATION_Caller *caller;
508
509 caller = find_caller (phone,
510 am->cid);
511 if (NULL == caller)
512 return;
513 switch (caller->state)
514 {
515 case CS_RINGING:
516 GNUNET_break_op (0);
517 break;
518
519 case CS_ACTIVE:
520 caller->speaker->play (caller->speaker->cls,
521 ntohs (am->header.size) - sizeof(struct
522 ClientAudioMessage),
523 &am[1]);
524 break;
525
526 case CS_CALLEE_SUSPENDED:
527 case CS_CALLER_SUSPENDED:
528 case CS_BOTH_SUSPENDED:
529 break;
530 }
531}
532
533
534/**
535 * We encountered an error talking with the conversation service.
536 *
537 * @param cls the `struct GNUNET_CONVERSATION_Phone`
538 * @param error details about the error
539 */
540static void
541phone_error_handler (void *cls,
542 enum GNUNET_MQ_Error error)
543{
544 struct GNUNET_CONVERSATION_Phone *phone = cls;
545
546 (void) error;
547 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
548 _ (
549 "Connection to conversation service lost, trying to reconnect\n"));
550 reconnect_phone (phone);
551}
552
553
554/**
555 * Clean up all callers of the given phone.
556 *
557 * @param phone phone to clean up callers for
558 */
559static void
560clean_up_callers (struct GNUNET_CONVERSATION_Phone *phone)
561{
562 struct GNUNET_CONVERSATION_Caller *caller;
563
564 while (NULL != (caller = phone->caller_head))
565 {
566 /* make sure mic/speaker are disabled *before* callback */
567 if (CS_ACTIVE == caller->state)
568 {
569 caller->speaker->disable_speaker (caller->speaker->cls);
570 caller->mic->disable_microphone (caller->mic->cls);
571 caller->state = CS_CALLER_SUSPENDED;
572 }
573 phone->event_handler (phone->event_handler_cls,
574 GNUNET_CONVERSATION_EC_PHONE_HUNG_UP,
575 caller,
576 &caller->caller_id);
577 GNUNET_CONVERSATION_caller_hang_up (caller);
578 }
579}
580
581
582/**
583 * The phone got disconnected, reconnect to the service.
584 *
585 * @param phone phone to reconnect
586 */
587static void
588reconnect_phone (struct GNUNET_CONVERSATION_Phone *phone)
589{
590 struct GNUNET_MQ_MessageHandler handlers[] = {
591 GNUNET_MQ_hd_var_size (phone_ring,
592 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RING,
593 struct ClientPhoneRingMessage,
594 phone),
595 GNUNET_MQ_hd_fixed_size (phone_hangup,
596 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
597 struct ClientPhoneHangupMessage,
598 phone),
599 GNUNET_MQ_hd_fixed_size (phone_suspend,
600 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
601 struct ClientPhoneSuspendMessage,
602 phone),
603 GNUNET_MQ_hd_fixed_size (phone_resume,
604 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
605 struct ClientPhoneResumeMessage,
606 phone),
607 GNUNET_MQ_hd_var_size (phone_audio,
608 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
609 struct ClientAudioMessage,
610 phone),
611 GNUNET_MQ_handler_end ()
612 };
613 struct GNUNET_MQ_Envelope *e;
614 struct ClientPhoneRegisterMessage *reg;
615
616 clean_up_callers (phone);
617 if (NULL != phone->mq)
618 {
619 GNUNET_MQ_destroy (phone->mq);
620 phone->mq = NULL;
621 }
622 phone->state = PS_REGISTER;
623 phone->mq = GNUNET_CLIENT_connect (phone->cfg,
624 "conversation",
625 handlers,
626 &phone_error_handler,
627 phone);
628 if (NULL == phone->mq)
629 return;
630 e = GNUNET_MQ_msg (reg,
631 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_REGISTER);
632 reg->line_port = phone->my_record.line_port;
633 GNUNET_MQ_send (phone->mq,
634 e);
635 phone->state = PS_READY;
636}
637
638
639/**
640 * Create a new phone.
641 *
642 * @param cfg configuration for the phone; specifies the phone service and
643 * which line the phone is to be connected to
644 * @param ego ego to use for name resolution (when determining caller ID)
645 * @param event_handler how to notify the owner of the phone about events
646 * @param event_handler_cls closure for @a event_handler
647 * @return NULL on error (no valid line configured)
648 */
649struct GNUNET_CONVERSATION_Phone *
650GNUNET_CONVERSATION_phone_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
651 const struct GNUNET_IDENTITY_Ego *ego,
652 GNUNET_CONVERSATION_PhoneEventHandler
653 event_handler,
654 void *event_handler_cls)
655{
656 struct GNUNET_CONVERSATION_Phone *phone;
657 char *line;
658 struct GNUNET_HashCode line_port;
659
660 if (GNUNET_OK !=
661 GNUNET_CONFIGURATION_get_value_string (cfg,
662 "CONVERSATION",
663 "LINE",
664 &line))
665 {
666 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
667 "CONVERSATION",
668 "LINE");
669 return NULL;
670 }
671 GNUNET_CRYPTO_hash (line,
672 strlen (line),
673 &line_port);
674 GNUNET_free (line);
675 phone = GNUNET_new (struct GNUNET_CONVERSATION_Phone);
676 if (GNUNET_OK !=
677 GNUNET_CRYPTO_get_peer_identity (cfg,
678 &phone->my_record.peer))
679 {
680 GNUNET_break (0);
681 GNUNET_free (phone);
682 return NULL;
683 }
684 phone->cfg = cfg;
685 phone->my_zone = *GNUNET_IDENTITY_ego_get_private_key (ego);
686 phone->event_handler = event_handler;
687 phone->event_handler_cls = event_handler_cls;
688 phone->ns = GNUNET_NAMESTORE_connect (cfg);
689 phone->my_record.version = htonl (1);
690 phone->my_record.reserved = htonl (0);
691 phone->my_record.line_port = line_port;
692 reconnect_phone (phone);
693 if ((NULL == phone->mq) ||
694 (NULL == phone->ns))
695 {
696 GNUNET_break (0);
697 GNUNET_CONVERSATION_phone_destroy (phone);
698 return NULL;
699 }
700 return phone;
701}
702
703
704/**
705 * Fill in a namestore record with the contact information
706 * for this phone. Note that the filled in "data" value
707 * is only valid until the phone is destroyed.
708 *
709 * @param phone phone to create a record for
710 * @param rd namestore record to fill in
711 */
712void
713GNUNET_CONVERSATION_phone_get_record (struct GNUNET_CONVERSATION_Phone *phone,
714 struct GNUNET_GNSRECORD_Data *rd)
715{
716 rd->data = &phone->my_record;
717 rd->expiration_time = 0;
718 rd->data_size = sizeof(struct GNUNET_CONVERSATION_PhoneRecord);
719 rd->record_type = GNUNET_GNSRECORD_TYPE_PHONE;
720 rd->flags = GNUNET_GNSRECORD_RF_NONE;
721}
722
723
724/**
725 * Picks up a (ringing) phone. This will connect the speaker
726 * to the microphone of the other party, and vice versa.
727 *
728 * @param caller handle that identifies which caller should be answered
729 * @param event_handler how to notify about events by the caller
730 * @param event_handler_cls closure for @a event_handler
731 * @param speaker speaker to use
732 * @param mic microphone to use
733 */
734void
735GNUNET_CONVERSATION_caller_pick_up (struct GNUNET_CONVERSATION_Caller *caller,
736 GNUNET_CONVERSATION_CallerEventHandler
737 event_handler,
738 void *event_handler_cls,
739 struct GNUNET_SPEAKER_Handle *speaker,
740 struct GNUNET_MICROPHONE_Handle *mic)
741{
742 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
743 struct GNUNET_MQ_Envelope *e;
744 struct ClientPhonePickupMessage *pick;
745
746 GNUNET_assert (CS_RINGING == caller->state);
747 caller->speaker = speaker;
748 caller->mic = mic;
749 e = GNUNET_MQ_msg (pick,
750 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICK_UP);
751 pick->cid = caller->cid;
752 GNUNET_MQ_send (phone->mq,
753 e);
754 caller->state = CS_ACTIVE;
755 caller->event_handler = event_handler;
756 caller->event_handler_cls = event_handler_cls;
757 caller->speaker->enable_speaker (caller->speaker->cls);
758 caller->mic->enable_microphone (caller->mic->cls,
759 &transmit_phone_audio,
760 caller);
761}
762
763
764/**
765 * Hang up up a (possibly ringing) phone. This will notify the other
766 * party that we are no longer interested in talking with them.
767 *
768 * @param caller conversation to hang up on
769 */
770void
771GNUNET_CONVERSATION_caller_hang_up (struct GNUNET_CONVERSATION_Caller *caller)
772{
773 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
774 struct GNUNET_MQ_Envelope *e;
775 struct ClientPhoneHangupMessage *hang;
776
777 switch (caller->state)
778 {
779 case CS_ACTIVE:
780 caller->speaker->disable_speaker (caller->speaker->cls);
781 caller->mic->disable_microphone (caller->mic->cls);
782 break;
783
784 default:
785 break;
786 }
787 GNUNET_CONTAINER_DLL_remove (phone->caller_head,
788 phone->caller_tail,
789 caller);
790 e = GNUNET_MQ_msg (hang,
791 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP);
792 hang->cid = caller->cid;
793 GNUNET_MQ_send (phone->mq,
794 e);
795 GNUNET_free (caller);
796}
797
798
799/**
800 * Destroys a phone.
801 *
802 * @param phone phone to destroy
803 */
804void
805GNUNET_CONVERSATION_phone_destroy (struct GNUNET_CONVERSATION_Phone *phone)
806{
807 clean_up_callers (phone);
808 if (NULL != phone->ns)
809 {
810 GNUNET_NAMESTORE_disconnect (phone->ns);
811 phone->ns = NULL;
812 }
813 if (NULL != phone->mq)
814 {
815 GNUNET_MQ_destroy (phone->mq);
816 phone->mq = NULL;
817 }
818 GNUNET_free (phone);
819}
820
821
822/**
823 * Pause conversation of an active call. This will disconnect the speaker
824 * and the microphone. The call can later be resumed with
825 * #GNUNET_CONVERSATION_caller_resume.
826 *
827 * @param caller call to suspend
828 */
829void
830GNUNET_CONVERSATION_caller_suspend (struct GNUNET_CONVERSATION_Caller *caller)
831{
832 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
833 struct GNUNET_MQ_Envelope *e;
834 struct ClientPhoneSuspendMessage *suspend;
835
836 GNUNET_assert ((CS_ACTIVE == caller->state) ||
837 (CS_CALLER_SUSPENDED == caller->state));
838 if (CS_ACTIVE == caller->state)
839 {
840 caller->speaker->disable_speaker (caller->speaker->cls);
841 caller->mic->disable_microphone (caller->mic->cls);
842 }
843 caller->speaker = NULL;
844 caller->mic = NULL;
845 e = GNUNET_MQ_msg (suspend,
846 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
847 suspend->cid = caller->cid;
848 GNUNET_MQ_send (phone->mq,
849 e);
850 if (CS_ACTIVE == caller->state)
851 caller->state = CS_CALLEE_SUSPENDED;
852 else
853 caller->state = CS_BOTH_SUSPENDED;
854}
855
856
857/**
858 * Resume suspended conversation of a phone.
859 *
860 * @param caller call to resume
861 * @param speaker speaker to use
862 * @param mic microphone to use
863 */
864void
865GNUNET_CONVERSATION_caller_resume (struct GNUNET_CONVERSATION_Caller *caller,
866 struct GNUNET_SPEAKER_Handle *speaker,
867 struct GNUNET_MICROPHONE_Handle *mic)
868{
869 struct GNUNET_CONVERSATION_Phone *phone = caller->phone;
870 struct GNUNET_MQ_Envelope *e;
871 struct ClientPhoneResumeMessage *resume;
872
873 GNUNET_assert ((CS_CALLEE_SUSPENDED == caller->state) ||
874 (CS_BOTH_SUSPENDED == caller->state));
875 caller->speaker = speaker;
876 caller->mic = mic;
877 e = GNUNET_MQ_msg (resume,
878 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
879 resume->cid = caller->cid;
880 GNUNET_MQ_send (phone->mq,
881 e);
882 if (CS_CALLEE_SUSPENDED == caller->state)
883 {
884 caller->state = CS_ACTIVE;
885 caller->speaker->enable_speaker (caller->speaker->cls);
886 caller->mic->enable_microphone (caller->mic->cls,
887 &transmit_phone_audio,
888 caller);
889 }
890 else
891 {
892 caller->state = CS_CALLER_SUSPENDED;
893 }
894}
895
896
897/* end of conversation_api.c */
diff --git a/src/contrib/service/conversation/conversation_api_call.c b/src/contrib/service/conversation/conversation_api_call.c
new file mode 100644
index 000000000..cd59678d9
--- /dev/null
+++ b/src/contrib/service/conversation/conversation_api_call.c
@@ -0,0 +1,749 @@
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_GNS_LO_DEFAULT,
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 */
diff --git a/src/contrib/service/conversation/displaydot.sh b/src/contrib/service/conversation/displaydot.sh
new file mode 100644
index 000000000..16ee23409
--- /dev/null
+++ b/src/contrib/service/conversation/displaydot.sh
@@ -0,0 +1,3 @@
1#!/bin/sh
2dot -Tpng `ls -tr1 /tmp/*rec*.dot | tail -1` | display /dev/stdin &
3dot -Tpng `ls -tr1 /tmp/*play*.dot | tail -1` | display /dev/stdin &
diff --git a/src/contrib/service/conversation/gnunet-conversation-test.c b/src/contrib/service/conversation/gnunet-conversation-test.c
new file mode 100644
index 000000000..5e6bc805f
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet-conversation-test.c
@@ -0,0 +1,265 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013 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/gnunet-conversation-test.c
23 * @brief tool to test speaker and microphone (for end users!)
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_speaker_lib.h"
29#include "gnunet_microphone_lib.h"
30
31/**
32 * How long do we record before we replay?
33 */
34#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
35
36
37/**
38 * A recording we made.
39 */
40struct Recording
41{
42 /**
43 * Kept in a DLL.
44 */
45 struct Recording *next;
46
47 /**
48 * Kept in a DLL.
49 */
50 struct Recording *prev;
51
52 /**
53 * Number of bytes that follow.
54 */
55 size_t size;
56};
57
58
59/**
60 * Final status code.
61 */
62static int ret;
63
64/**
65 * Handle to the microphone.
66 */
67static struct GNUNET_MICROPHONE_Handle *microphone;
68
69/**
70 * Handle to the speaker.
71 */
72static struct GNUNET_SPEAKER_Handle *speaker;
73
74/**
75 * Task scheduled to switch from recording to playback.
76 */
77static struct GNUNET_SCHEDULER_Task *switch_task;
78
79/**
80 * The shutdown task.
81 */
82static struct GNUNET_SCHEDULER_Task *st;
83
84/**
85 * Head of DLL with recorded frames.
86 */
87static struct Recording *rec_head;
88
89/**
90 * Tail of DLL with recorded frames.
91 */
92static struct Recording *rec_tail;
93
94
95/**
96 * Terminate test.
97 *
98 * @param cls NULL
99 */
100static void
101do_shutdown (void *cls)
102{
103 struct Recording *rec;
104
105 (void) cls;
106 if (NULL != switch_task)
107 GNUNET_SCHEDULER_cancel (switch_task);
108 if (NULL != microphone)
109 GNUNET_MICROPHONE_destroy (microphone);
110 if (NULL != speaker)
111 GNUNET_SPEAKER_destroy (speaker);
112 while (NULL != (rec = rec_head))
113 {
114 GNUNET_CONTAINER_DLL_remove (rec_head,
115 rec_tail,
116 rec);
117 GNUNET_free (rec);
118 }
119 fprintf (stderr,
120 _ ("\nEnd of transmission. Have a GNU day.\n"));
121}
122
123
124/**
125 * Terminate recording process and switch to playback.
126 *
127 * @param cls NULL
128 */
129static void
130switch_to_speaker (void *cls)
131{
132 (void) cls;
133 switch_task = NULL;
134 microphone->disable_microphone (microphone->cls);
135 if (GNUNET_OK !=
136 speaker->enable_speaker (speaker->cls))
137 {
138 fprintf (stderr,
139 "Failed to enable microphone\n");
140 ret = 1;
141 GNUNET_SCHEDULER_shutdown ();
142 return;
143 }
144 fprintf (stderr,
145 _ (
146 "\new are now playing your recording back. If you can hear it, your audio settings are working..."));
147 for (struct Recording *rec = rec_head; NULL != rec; rec = rec->next)
148 {
149 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
150 "Replaying %u bytes\n",
151 (unsigned int) rec->size);
152 speaker->play (speaker->cls,
153 rec->size,
154 &rec[1]);
155 }
156 GNUNET_SCHEDULER_cancel (st);
157 st = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
158 &do_shutdown,
159 NULL);
160}
161
162
163/**
164 * Process recorded audio data.
165 *
166 * @param cls clsoure
167 * @param data_size number of bytes in @a data
168 * @param data audio data to play
169 */
170static void
171record (void *cls,
172 size_t data_size,
173 const void *data)
174{
175 struct Recording *rec;
176
177 (void) cls;
178 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
179 "Recorded %u bytes\n",
180 (unsigned int) data_size);
181 rec = GNUNET_malloc (sizeof(struct Recording) + data_size);
182 rec->size = data_size;
183 GNUNET_memcpy (&rec[1], data, data_size);
184 GNUNET_CONTAINER_DLL_insert_tail (rec_head,
185 rec_tail,
186 rec);
187}
188
189
190/**
191 * Main function that will be run by the scheduler.
192 *
193 * @param cls closure
194 * @param args remaining command-line arguments
195 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
196 * @param cfg configuration
197 */
198static void
199run (void *cls,
200 char *const *args,
201 const char *cfgfile,
202 const struct GNUNET_CONFIGURATION_Handle *cfg)
203{
204 (void) cls;
205 (void) args;
206 (void) cfgfile;
207 microphone = GNUNET_MICROPHONE_create_from_hardware (cfg);
208 GNUNET_assert (NULL != microphone);
209 speaker = GNUNET_SPEAKER_create_from_hardware (cfg);
210 GNUNET_assert (NULL != speaker);
211 switch_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
212 &switch_to_speaker,
213 NULL);
214 st = GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
215 NULL);
216 fprintf (stderr,
217 _ (
218 "We will now be recording you for %s. After that time, the recording will be played back to you..."),
219 GNUNET_STRINGS_relative_time_to_string (TIMEOUT, GNUNET_YES));
220 if (GNUNET_OK !=
221 microphone->enable_microphone (microphone->cls,
222 &record, NULL))
223 {
224 fprintf (stderr,
225 "Failed to enable microphone\n");
226 ret = 1;
227 GNUNET_SCHEDULER_shutdown ();
228 return;
229 }
230}
231
232
233/**
234 * The main function of our code to test microphone and speaker.
235 *
236 * @param argc number of arguments from the command line
237 * @param argv command line arguments
238 * @return 0 ok, 1 on error
239 */
240int
241main (int argc,
242 char *const *argv)
243{
244 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
245 GNUNET_GETOPT_OPTION_END
246 };
247
248 if (GNUNET_OK !=
249 GNUNET_STRINGS_get_utf8_args (argc, argv,
250 &argc, &argv))
251 return 2;
252
253 ret = (GNUNET_OK ==
254 GNUNET_PROGRAM_run (argc, argv,
255 "gnunet-conversation-test",
256 gettext_noop ("help text"),
257 options,
258 &run,
259 NULL)) ? ret : 1;
260 GNUNET_free_nz ((void *) argv);
261 return ret;
262}
263
264
265/* end of gnunet-conversation-test.c */
diff --git a/src/contrib/service/conversation/gnunet-conversation.c b/src/contrib/service/conversation/gnunet-conversation.c
new file mode 100644
index 000000000..41fc7bd0b
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet-conversation.c
@@ -0,0 +1,1232 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013 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 * @file conversation/gnunet-conversation.c
22 * @brief conversation implementation
23 * @author Simon Dieterle
24 * @author Andreas Fuchs
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_constants.h"
29#include "gnunet_gnsrecord_lib.h"
30#include "gnunet_conversation_service.h"
31#include "gnunet_namestore_service.h"
32
33/**
34 * Maximum length allowed for the command line input.
35 */
36#define MAX_MESSAGE_LENGTH 1024
37
38#define XSTRINGIFY(x) STRINGIFY (x)
39
40#define STRINGIFY(x) (#x)
41
42/**
43 * Possible states of the phone.
44 */
45enum PhoneState
46{
47 /**
48 * We're waiting for our own idenitty.
49 */
50 PS_LOOKUP_EGO,
51
52 /**
53 * We're listening for calls
54 */
55 PS_LISTEN,
56
57 /**
58 * We accepted an incoming phone call.
59 */
60 PS_ACCEPTED,
61
62 /**
63 * Internal error
64 */
65 PS_ERROR
66};
67
68
69/**
70 * States for current outgoing call.
71 */
72enum CallState
73{
74 /**
75 * We are looking up some other participant.
76 */
77 CS_RESOLVING,
78
79 /**
80 * We are now ringing the other participant.
81 */
82 CS_RINGING,
83
84 /**
85 * The other party accepted our call and we are now connected.
86 */
87 CS_CONNECTED,
88
89 /**
90 * The call is currently suspended (by us).
91 */
92 CS_SUSPENDED
93};
94
95
96/**
97 * List of incoming calls
98 */
99struct CallList
100{
101 /**
102 * A DLL.
103 */
104 struct CallList *prev;
105
106 /**
107 * A DLL.
108 */
109 struct CallList *next;
110
111 /**
112 * Handle to hang up or activate.
113 */
114 struct GNUNET_CONVERSATION_Caller *caller;
115
116 /**
117 * Public key identifying the caller.
118 */
119 struct GNUNET_CRYPTO_PublicKey caller_id;
120
121 /**
122 * Unique number of the call.
123 */
124 unsigned int caller_num;
125};
126
127
128/**
129 * Phone handle
130 */
131static struct GNUNET_CONVERSATION_Phone *phone;
132
133/**
134 * Call handle (for active outgoing call).
135 */
136static struct GNUNET_CONVERSATION_Call *call;
137
138/**
139 * Caller handle (for active incoming call).
140 * This call handler is NOT in the #cl_head / #cl_tail list.
141 */
142static struct CallList *cl_active;
143
144/**
145 * Head of calls waiting to be accepted.
146 */
147static struct CallList *cl_head;
148
149/**
150 * Tail of calls waiting to be accepted.
151 */
152static struct CallList *cl_tail;
153
154/**
155 * Desired phone line (string to be converted to a hash).
156 */
157static char *line;
158
159/**
160 * Task which handles the commands
161 */
162static struct GNUNET_SCHEDULER_Task *handle_cmd_task;
163
164/**
165 * Our speaker.
166 */
167static struct GNUNET_SPEAKER_Handle *speaker;
168
169/**
170 * Our microphone.
171 */
172static struct GNUNET_MICROPHONE_Handle *mic;
173
174/**
175 * Our configuration.
176 */
177static struct GNUNET_CONFIGURATION_Handle *cfg;
178
179/**
180 * Our ego.
181 */
182static struct GNUNET_IDENTITY_Ego *my_caller_id;
183
184/**
185 * Handle to identity service.
186 */
187static struct GNUNET_IDENTITY_Handle *id;
188
189/**
190 * Name of our ego.
191 */
192static char *ego_name;
193
194/**
195 * Public key of active conversation partner (if any).
196 */
197static struct GNUNET_CRYPTO_PublicKey peer_key;
198
199/**
200 * Name of active conversation partner (if any).
201 */
202static char *peer_name;
203
204/**
205 * File handle for stdin.
206 */
207static struct GNUNET_DISK_FileHandle *stdin_fh;
208
209/**
210 * Our phone's current state.
211 */
212static enum PhoneState phone_state;
213
214/**
215 * Our call's current state.
216 */
217static enum CallState call_state;
218
219/**
220 * Counts the number of incoming calls we have had so far.
221 */
222static unsigned int caller_num_gen;
223
224/**
225 * GNS address for this phone.
226 */
227static char *address;
228
229/**
230 * Be verbose.
231 */
232static int verbose;
233
234
235/**
236 * Function called with an event emitted by a phone.
237 *
238 * @param cls closure
239 * @param code type of the event
240 * @param caller handle for the caller
241 * @param caller_id public key of the caller (in GNS)
242 */
243static void
244phone_event_handler (void *cls,
245 enum GNUNET_CONVERSATION_PhoneEventCode code,
246 struct GNUNET_CONVERSATION_Caller *caller,
247 const struct GNUNET_CRYPTO_PublicKey *caller_id)
248{
249 struct CallList *cl;
250
251 (void) cls;
252 switch (code)
253 {
254 case GNUNET_CONVERSATION_EC_PHONE_RING:
255 /*
256 * FIXME: we should be playing our ringtones from contrib/sounds now!
257 *
258 ring_my_bell();
259 *
260 * see https://gstreamer.freedesktop.org/documentation/application-development/highlevel/playback-components.html on how to play a wav using the gst framework being used here
261 */fprintf (
262 stdout,
263 _ (
264 "Incoming call from `%s'. Please /accept %u or /cancel %u the call.\n"),
265 GNUNET_GNSRECORD_pkey_to_zkey (caller_id),
266 caller_num_gen,
267 caller_num_gen);
268 cl = GNUNET_new (struct CallList);
269 cl->caller = caller;
270 cl->caller_id = *caller_id;
271 cl->caller_num = caller_num_gen++;
272 GNUNET_CONTAINER_DLL_insert (cl_head, cl_tail, cl);
273 break;
274
275 case GNUNET_CONVERSATION_EC_PHONE_HUNG_UP:
276 for (cl = cl_head; NULL != cl; cl = cl->next)
277 if (caller == cl->caller)
278 break;
279 if ((NULL == cl) && (caller == cl_active->caller))
280 cl = cl_active;
281 if (NULL == cl)
282 {
283 GNUNET_break (0);
284 return;
285 }
286 fprintf (stdout,
287 _ ("Call from `%s' terminated\n"),
288 GNUNET_GNSRECORD_pkey_to_zkey (&cl->caller_id));
289 if (cl == cl_active)
290 {
291 cl_active = NULL;
292 phone_state = PS_LISTEN;
293 }
294 else
295 {
296 GNUNET_CONTAINER_DLL_remove (cl_head, cl_tail, cl);
297 }
298 GNUNET_free (cl);
299 break;
300 }
301}
302
303
304/**
305 * Function called with an event emitted by a caller.
306 *
307 * @param cls closure with the `struct CallList` of the caller
308 * @param code type of the event issued by the caller
309 */
310static void
311caller_event_handler (void *cls, enum GNUNET_CONVERSATION_CallerEventCode code)
312{
313 struct CallList *cl = cls;
314
315 switch (code)
316 {
317 case GNUNET_CONVERSATION_EC_CALLER_SUSPEND:
318 fprintf (stdout,
319 _ ("Call from `%s' suspended by other user\n"),
320 GNUNET_GNSRECORD_pkey_to_zkey (&cl->caller_id));
321 break;
322
323 case GNUNET_CONVERSATION_EC_CALLER_RESUME:
324 fprintf (stdout,
325 _ ("Call from `%s' resumed by other user\n"),
326 GNUNET_GNSRECORD_pkey_to_zkey (&cl->caller_id));
327 break;
328 }
329}
330
331
332/**
333 * Start our phone.
334 */
335static void
336start_phone ()
337{
338 struct GNUNET_GNSRECORD_Data rd;
339
340 if (NULL == my_caller_id)
341 {
342 fprintf (stderr,
343 _ ("Ego `%s' no longer available, phone is now down.\n"),
344 ego_name);
345 phone_state = PS_LOOKUP_EGO;
346 return;
347 }
348 GNUNET_assert (NULL == phone);
349 phone = GNUNET_CONVERSATION_phone_create (cfg,
350 my_caller_id,
351 &phone_event_handler,
352 NULL);
353 /* FIXME: get record and print full GNS record info later here... */
354 if (NULL == phone)
355 {
356 fprintf (stderr, "%s", _ ("Failed to setup phone (internal error)\n"));
357 phone_state = PS_ERROR;
358 }
359 else
360 {
361 GNUNET_CONVERSATION_phone_get_record (phone, &rd);
362 GNUNET_free (address);
363 address =
364 GNUNET_GNSRECORD_value_to_string (rd.record_type, rd.data, rd.data_size);
365 fprintf (
366 stdout,
367 _ (
368 "Phone active at `%s'. Type `/help' for a list of available commands\n"),
369 address);
370 phone_state = PS_LISTEN;
371 }
372}
373
374
375/**
376 * Function called with an event emitted by a call.
377 *
378 * @param cls closure, NULL
379 * @param code type of the event on the call
380 */
381static void
382call_event_handler (void *cls, enum GNUNET_CONVERSATION_CallEventCode code)
383{
384 (void) cls;
385
386 switch (code)
387 {
388 case GNUNET_CONVERSATION_EC_CALL_RINGING:
389 GNUNET_break (CS_RESOLVING == call_state);
390 fprintf (stdout,
391 _ ("Resolved address of `%s'. Now ringing other party.\n"),
392 peer_name);
393 call_state = CS_RINGING;
394 break;
395
396 case GNUNET_CONVERSATION_EC_CALL_PICKED_UP:
397 GNUNET_break (CS_RINGING == call_state);
398 fprintf (stdout, _ ("Connection established to `%s'\n"), peer_name);
399 call_state = CS_CONNECTED;
400 break;
401
402 case GNUNET_CONVERSATION_EC_CALL_GNS_FAIL:
403 GNUNET_break (CS_RESOLVING == call_state);
404 fprintf (stdout, _ ("Failed to resolve `%s'\n"), peer_name);
405 GNUNET_free (peer_name);
406 peer_name = NULL;
407 call = NULL;
408 break;
409
410 case GNUNET_CONVERSATION_EC_CALL_HUNG_UP:
411 fprintf (stdout, _ ("Call to `%s' terminated\n"), peer_name);
412 GNUNET_free (peer_name);
413 peer_name = NULL;
414 call = NULL;
415 break;
416
417 case GNUNET_CONVERSATION_EC_CALL_SUSPENDED:
418 GNUNET_break (CS_CONNECTED == call_state);
419 fprintf (stdout,
420 _ ("Connection to `%s' suspended (by other user)\n"),
421 peer_name);
422 break;
423
424 case GNUNET_CONVERSATION_EC_CALL_RESUMED:
425 GNUNET_break (CS_CONNECTED == call_state);
426 fprintf (stdout,
427 _ ("Connection to `%s' resumed (by other user)\n"),
428 peer_name);
429 break;
430
431 case GNUNET_CONVERSATION_EC_CALL_ERROR:
432 fprintf (stdout, _ ("Error with the call, restarting it\n"));
433 GNUNET_free (peer_name);
434 peer_name = NULL;
435 call = NULL;
436 break;
437 }
438}
439
440
441/**
442 * Function declareation for executing a action
443 *
444 * @param arguments arguments given to the function
445 */
446typedef void (*ActionFunction) (const char *arguments);
447
448
449/**
450 * Structure which defines a command
451 */
452struct VoipCommand
453{
454 /**
455 * Command the user needs to enter.
456 */
457 const char *command;
458
459 /**
460 * Function to call on command.
461 */
462 ActionFunction Action;
463
464 /**
465 * Help text for the command.
466 */
467 const char *helptext;
468};
469
470
471/**
472 * Action function to print help for the command shell.
473 *
474 * @param args arguments given to the command
475 */
476static void
477do_help (const char *args);
478
479
480/**
481 * Terminate the client
482 *
483 * @param args arguments given to the command
484 */
485static void
486do_quit (const char *args)
487{
488 (void) args;
489 GNUNET_SCHEDULER_shutdown ();
490}
491
492
493/**
494 * Handler for unknown command.
495 *
496 * @param msg arguments given to the command
497 */
498static void
499do_unknown (const char *msg)
500{
501 fprintf (stderr, _ ("Unknown command `%s'\n"), msg);
502}
503
504
505/**
506 * Initiating a new call
507 *
508 * @param arg arguments given to the command
509 */
510static void
511do_call (const char *arg)
512{
513 if (NULL == my_caller_id)
514 {
515 fprintf (stderr, _ ("Ego `%s' not available\n"), ego_name);
516 return;
517 }
518 if (NULL != call)
519 {
520 fprintf (stderr,
521 _ ("You are calling someone else already, hang up first!\n"));
522 return;
523 }
524 switch (phone_state)
525 {
526 case PS_LOOKUP_EGO:
527 fprintf (stderr, _ ("Ego `%s' not available\n"), ego_name);
528 return;
529
530 case PS_LISTEN:
531 /* ok to call! */
532 break;
533
534 case PS_ACCEPTED:
535 fprintf (
536 stderr,
537 _ (
538 "You are answering call from `%s', hang up or suspend that call first!\n"),
539 GNUNET_GNSRECORD_pkey_to_zkey (&peer_key));
540 return;
541
542 case PS_ERROR:
543 /* ok to call */
544 break;
545 }
546 if (NULL == arg)
547 {
548 fprintf (stderr, _ ("Call recipient missing.\n"));
549 do_help ("/call");
550 return;
551 }
552 peer_name = GNUNET_strdup (arg);
553 call_state = CS_RESOLVING;
554 GNUNET_assert (NULL == call);
555 call = GNUNET_CONVERSATION_call_start (cfg,
556 my_caller_id,
557 arg,
558 speaker,
559 mic,
560 &call_event_handler,
561 NULL);
562}
563
564
565/**
566 * Accepting an incoming call
567 *
568 * @param args arguments given to the command
569 */
570static void
571do_accept (const char *args)
572{
573 struct CallList *cl;
574 char buf[32];
575
576 if ((NULL != call) && (CS_SUSPENDED != call_state))
577 {
578 fprintf (stderr,
579 _ ("You are calling someone else already, hang up first!\n"));
580 return;
581 }
582 switch (phone_state)
583 {
584 case PS_LOOKUP_EGO:
585 GNUNET_break (0);
586 break;
587
588 case PS_LISTEN:
589 /* this is the expected state */
590 break;
591
592 case PS_ACCEPTED:
593 fprintf (
594 stderr,
595 _ (
596 "You are answering call from `%s', hang up or suspend that call first!\n"),
597 GNUNET_GNSRECORD_pkey_to_zkey (&peer_key));
598 return;
599
600 case PS_ERROR:
601 GNUNET_break (0);
602 break;
603 }
604 cl = cl_head;
605 if (NULL == cl)
606 {
607 fprintf (stderr, _ ("There is no incoming call to accept here!\n"));
608 return;
609 }
610 if ((NULL != cl->next) || (NULL != args))
611 {
612 for (cl = cl_head; NULL != cl; cl = cl->next)
613 {
614 GNUNET_snprintf (buf, sizeof(buf), "%u", cl->caller_num);
615 if (0 == strcmp (buf, args))
616 break;
617 }
618 }
619 if (NULL == cl)
620 {
621 fprintf (stderr,
622 _ ("There is no incoming call `%s' to accept right now!\n"),
623 args);
624 return;
625 }
626 GNUNET_CONTAINER_DLL_remove (cl_head, cl_tail, cl);
627 cl_active = cl;
628 peer_key = cl->caller_id;
629 phone_state = PS_ACCEPTED;
630 GNUNET_CONVERSATION_caller_pick_up (cl->caller,
631 &caller_event_handler,
632 cl,
633 speaker,
634 mic);
635}
636
637
638/**
639 * Print address information for this phone.
640 *
641 * @param args arguments given to the command
642 */
643static void
644do_address (const char *args)
645{
646 (void) args;
647 if (NULL == address)
648 {
649 fprintf (stdout, "%s", _ ("We currently do not have an address.\n"));
650 return;
651 }
652 fprintf (stdout, "%s\n", address);
653}
654
655
656/**
657 * Accepting an incoming call
658 *
659 * @param args arguments given to the command
660 */
661static void
662do_status (const char *args)
663{
664 struct CallList *cl;
665
666 (void) args;
667 switch (phone_state)
668 {
669 case PS_LOOKUP_EGO:
670 fprintf (
671 stdout,
672 _ (
673 "We are currently trying to locate the private key for the ego `%s'.\n"),
674 ego_name);
675 break;
676
677 case PS_LISTEN:
678 fprintf (stdout,
679 _ (
680 "We are listening for incoming calls for ego `%s' on line `%s'.\n"),
681 ego_name,
682 line);
683 break;
684
685 case PS_ACCEPTED:
686 fprintf (stdout,
687 _ ("You are having a conversation with `%s'.\n"),
688 GNUNET_GNSRECORD_pkey_to_zkey (&peer_key));
689 ;
690 break;
691
692 case PS_ERROR:
693 fprintf (
694 stdout,
695 _ (
696 "We had an internal error setting up our phone line. You can still make calls.\n"));
697 break;
698 }
699 if (NULL != call)
700 {
701 switch (call_state)
702 {
703 case CS_RESOLVING:
704 fprintf (stdout,
705 _ ("We are trying to find the network address to call `%s'.\n"),
706 peer_name);
707 break;
708
709 case CS_RINGING:
710 fprintf (stdout,
711 _ ("We are calling `%s', their phone should be ringing.\n"),
712 peer_name);
713 break;
714
715 case CS_CONNECTED:
716 fprintf (stdout,
717 _ ("You are having a conversation with `%s'.\n"),
718 peer_name);
719 break;
720
721 case CS_SUSPENDED:
722 /* ok to accept incoming call right now */
723 break;
724 }
725 }
726 if ((NULL != cl_head) && ((cl_head != cl_active) || (cl_head != cl_tail)))
727 {
728 fprintf (stdout, "%s", _ ("Calls waiting:\n"));
729 for (cl = cl_head; NULL != cl; cl = cl->next)
730 {
731 if (cl == cl_active)
732 continue;
733 fprintf (stdout,
734 _ ("#%u: `%s'\n"),
735 cl->caller_num,
736 GNUNET_GNSRECORD_pkey_to_zkey (&cl->caller_id));
737 }
738 fprintf (stdout, "%s", "\n");
739 }
740}
741
742
743/**
744 * Suspending a call
745 *
746 * @param args arguments given to the command
747 */
748static void
749do_suspend (const char *args)
750{
751 (void) args;
752 if (NULL != call)
753 {
754 switch (call_state)
755 {
756 case CS_RESOLVING:
757 case CS_RINGING:
758 case CS_SUSPENDED:
759 fprintf (stderr,
760 "%s",
761 _ ("There is no call that could be suspended right now.\n"));
762 return;
763
764 case CS_CONNECTED:
765 call_state = CS_SUSPENDED;
766 GNUNET_CONVERSATION_call_suspend (call);
767 return;
768 }
769 }
770 switch (phone_state)
771 {
772 case PS_LOOKUP_EGO:
773 case PS_LISTEN:
774 case PS_ERROR:
775 fprintf (stderr,
776 "%s",
777 _ ("There is no call that could be suspended right now.\n"));
778 return;
779
780 case PS_ACCEPTED:
781 /* expected state, do rejection logic */
782 break;
783 }
784 GNUNET_assert (NULL != cl_active);
785 GNUNET_CONVERSATION_caller_suspend (cl_active->caller);
786 cl_active = NULL;
787 phone_state = PS_LISTEN;
788}
789
790
791/**
792 * Resuming a call
793 *
794 * @param args arguments given to the command
795 */
796static void
797do_resume (const char *args)
798{
799 struct CallList *cl;
800 char buf[32];
801
802 if (NULL != call)
803 {
804 switch (call_state)
805 {
806 case CS_RESOLVING:
807 case CS_RINGING:
808 case CS_CONNECTED:
809 fprintf (stderr,
810 "%s",
811 _ ("There is no call that could be resumed right now.\n"));
812 return;
813
814 case CS_SUSPENDED:
815 call_state = CS_CONNECTED;
816 GNUNET_CONVERSATION_call_resume (call, speaker, mic);
817 return;
818 }
819 }
820 switch (phone_state)
821 {
822 case PS_LOOKUP_EGO:
823 case PS_ERROR:
824 fprintf (stderr,
825 "%s",
826 _ ("There is no call that could be resumed right now.\n"));
827 return;
828
829 case PS_LISTEN:
830 /* expected state, do resume logic */
831 break;
832
833 case PS_ACCEPTED:
834 fprintf (stderr,
835 _ ("Already talking with `%s', cannot resume a call right now.\n"),
836 GNUNET_GNSRECORD_pkey_to_zkey (&peer_key));
837 return;
838 }
839 GNUNET_assert (NULL == cl_active);
840 cl = cl_head;
841 if (NULL == cl)
842 {
843 fprintf (stderr, _ ("There is no incoming call to resume here!\n"));
844 return;
845 }
846 if ((NULL != cl->next) || (NULL != args))
847 {
848 for (cl = cl_head; NULL != cl; cl = cl->next)
849 {
850 GNUNET_snprintf (buf, sizeof(buf), "%u", cl->caller_num);
851 if (0 == strcmp (buf, args))
852 break;
853 }
854 }
855 if (NULL == cl)
856 {
857 fprintf (stderr,
858 _ ("There is no incoming call `%s' to resume right now!\n"),
859 args);
860 return;
861 }
862 cl_active = cl;
863 GNUNET_CONVERSATION_caller_resume (cl_active->caller, speaker, mic);
864 phone_state = PS_ACCEPTED;
865}
866
867
868/**
869 * Rejecting a call
870 *
871 * @param args arguments given to the command
872 */
873static void
874do_reject (const char *args)
875{
876 struct CallList *cl;
877 char buf[32];
878
879 if (NULL != call)
880 {
881 GNUNET_CONVERSATION_call_stop (call);
882 call = NULL;
883 return;
884 }
885 switch (phone_state)
886 {
887 case PS_LOOKUP_EGO:
888 case PS_ERROR:
889 fprintf (stderr,
890 "%s",
891 _ ("There is no call that could be cancelled right now.\n"));
892 return;
893
894 case PS_LISTEN:
895 /* look for active incoming calls to refuse */
896 cl = cl_head;
897 if (NULL == cl)
898 {
899 fprintf (stderr, _ ("There is no incoming call to refuse here!\n"));
900 return;
901 }
902 if ((NULL != cl->next) || (NULL != args))
903 {
904 for (cl = cl_head; NULL != cl; cl = cl->next)
905 {
906 GNUNET_snprintf (buf, sizeof(buf), "%u", cl->caller_num);
907 if (0 == strcmp (buf, args))
908 break;
909 }
910 }
911 if (NULL == cl)
912 {
913 fprintf (stderr,
914 _ ("There is no incoming call `%s' to refuse right now!\n"),
915 args);
916 return;
917 }
918 GNUNET_CONVERSATION_caller_hang_up (cl->caller);
919 GNUNET_CONTAINER_DLL_remove (cl_head, cl_tail, cl);
920 GNUNET_free (cl);
921 break;
922
923 case PS_ACCEPTED:
924 /* expected state, do rejection logic */
925 GNUNET_assert (NULL != cl_active);
926 GNUNET_CONVERSATION_caller_hang_up (cl_active->caller);
927 cl_active = NULL;
928 phone_state = PS_LISTEN;
929 break;
930 }
931}
932
933
934/**
935 * List of supported commands.
936 */
937static struct VoipCommand commands[] = {
938 { "/address",
939 &do_address,
940 gettext_noop (
941 "Use `/address' to find out which address this phone should have in GNS") },
942 { "/call", &do_call, gettext_noop ("Use `/call USER.gnu' to call USER") },
943 { "/accept",
944 &do_accept,
945 gettext_noop ("Use `/accept #NUM' to accept incoming call #NUM") },
946 { "/suspend",
947 &do_suspend,
948 gettext_noop ("Use `/suspend' to suspend the active call") },
949 { "/resume",
950 &do_resume,
951 gettext_noop (
952 "Use `/resume [#NUM]' to resume a call, #NUM is needed to resume incoming calls, no argument is needed to resume the current outgoing call.") },
953 { "/cancel",
954 &do_reject,
955 gettext_noop ("Use `/cancel' to reject or terminate a call") },
956 { "/status",
957 &do_status,
958 gettext_noop ("Use `/status' to print status information") },
959 { "/quit",
960 &do_quit,
961 gettext_noop ("Use `/quit' to terminate gnunet-conversation") },
962 { "/help",
963 &do_help,
964 gettext_noop ("Use `/help command' to get help for a specific command") },
965 { "", &do_unknown, NULL },
966 { NULL, NULL, NULL },
967};
968
969
970/**
971 * Action function to print help for the command shell.
972 *
973 * @param args arguments given to the command
974 */
975static void
976do_help (const char *args)
977{
978 unsigned int i;
979
980 i = 0;
981 while ((NULL != args) && (0 != strlen (args)) &&
982 (commands[i].Action != &do_help))
983 {
984 if (0 == strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1))
985 {
986 fprintf (stdout, "%s\n", gettext (commands[i].helptext));
987 return;
988 }
989 i++;
990 }
991 i = 0;
992 fprintf (stdout, "%s", "Available commands:\n");
993 while (commands[i].Action != &do_help)
994 {
995 fprintf (stdout, "%s\n", gettext (commands[i].command));
996 i++;
997 }
998 fprintf (stdout, "%s", "\n");
999 fprintf (stdout, "%s\n", gettext (commands[i].helptext));
1000}
1001
1002
1003/**
1004 * Task run during shutdown.
1005 *
1006 * @param cls NULL
1007 */
1008static void
1009do_stop_task (void *cls)
1010{
1011 (void) cls;
1012 if (NULL != call)
1013 {
1014 GNUNET_CONVERSATION_call_stop (call);
1015 call = NULL;
1016 }
1017 if (NULL != phone)
1018 {
1019 GNUNET_CONVERSATION_phone_destroy (phone);
1020 phone = NULL;
1021 }
1022 if (NULL != handle_cmd_task)
1023 {
1024 GNUNET_SCHEDULER_cancel (handle_cmd_task);
1025 handle_cmd_task = NULL;
1026 }
1027 if (NULL != id)
1028 {
1029 GNUNET_IDENTITY_disconnect (id);
1030 id = NULL;
1031 }
1032 GNUNET_SPEAKER_destroy (speaker);
1033 speaker = NULL;
1034 GNUNET_MICROPHONE_destroy (mic);
1035 mic = NULL;
1036 GNUNET_free (ego_name);
1037 ego_name = NULL;
1038 GNUNET_free (peer_name);
1039 peer_name = NULL;
1040 phone_state = PS_ERROR;
1041}
1042
1043
1044/**
1045 * Handle user command.
1046 *
1047 * @param message command the user typed in
1048 * @param str_len number of bytes to process in @a message
1049 */
1050static void
1051handle_command_string (char *message, size_t str_len)
1052{
1053 size_t i;
1054 const char *ptr;
1055
1056 if (0 == str_len)
1057 return;
1058 if (message[str_len - 1] == '\n')
1059 message[str_len - 1] = '\0';
1060 if (message[str_len - 2] == '\r')
1061 message[str_len - 2] = '\0';
1062 if (0 == strlen (message))
1063 return;
1064 i = 0;
1065 while (
1066 (NULL != commands[i].command) &&
1067 (0 !=
1068 strncasecmp (commands[i].command, message, strlen (commands[i].command))))
1069 i++;
1070 ptr = &message[strlen (commands[i].command)];
1071 while (isspace ((unsigned char) *ptr))
1072 ptr++;
1073 if ('\0' == *ptr)
1074 ptr = NULL;
1075 commands[i].Action (ptr);
1076}
1077
1078
1079/**
1080 * Task to handle commands from the terminal.
1081 *
1082 * @param cls NULL
1083 */
1084static void
1085handle_command (void *cls)
1086{
1087 char message[MAX_MESSAGE_LENGTH + 1];
1088
1089 (void) cls;
1090 handle_cmd_task =
1091 GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
1092 stdin_fh,
1093 &handle_command,
1094 NULL);
1095 /* read message from command line and handle it */
1096 memset (message, 0, MAX_MESSAGE_LENGTH + 1);
1097 if (NULL == fgets (message, MAX_MESSAGE_LENGTH, stdin))
1098 return;
1099 handle_command_string (message, strlen (message));
1100}
1101
1102
1103/**
1104 * Function called by identity service with information about egos.
1105 *
1106 * @param cls NULL
1107 * @param ego ego handle
1108 * @param ctx unused
1109 * @param name name of the ego
1110 */
1111static void
1112identity_cb (void *cls,
1113 struct GNUNET_IDENTITY_Ego *ego,
1114 void **ctx,
1115 const char *name)
1116{
1117 (void) cls;
1118 (void) ctx;
1119 if (NULL == name)
1120 return;
1121 if (ego == my_caller_id)
1122 {
1123 if (verbose)
1124 fprintf (stdout, _ ("Name of our ego changed to `%s'\n"), name);
1125 GNUNET_free (ego_name);
1126 ego_name = GNUNET_strdup (name);
1127 return;
1128 }
1129 if (0 != strcmp (name, ego_name))
1130 return;
1131 if (NULL == ego)
1132 {
1133 if (verbose)
1134 fprintf (stdout, _ ("Our ego `%s' was deleted!\n"), ego_name);
1135 my_caller_id = NULL;
1136 return;
1137 }
1138 my_caller_id = ego;
1139 GNUNET_CONFIGURATION_set_value_string (cfg, "CONVERSATION", "LINE", line);
1140 start_phone ();
1141}
1142
1143
1144/**
1145 * Main function that will be run by the scheduler.
1146 *
1147 * @param cls closure
1148 * @param args remaining command-line arguments
1149 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1150 * @param c configuration
1151 */
1152static void
1153run (void *cls,
1154 char *const *args,
1155 const char *cfgfile,
1156 const struct GNUNET_CONFIGURATION_Handle *c)
1157{
1158 (void) cls;
1159 (void) args;
1160 (void) cfgfile;
1161 cfg = GNUNET_CONFIGURATION_dup (c);
1162 speaker = GNUNET_SPEAKER_create_from_hardware (cfg);
1163 mic = GNUNET_MICROPHONE_create_from_hardware (cfg);
1164 if (NULL == ego_name)
1165 {
1166 fprintf (stderr, "%s", _ ("You must specify the NAME of an ego to use\n"));
1167 return;
1168 }
1169 id = GNUNET_IDENTITY_connect (cfg, &identity_cb, NULL);
1170 handle_cmd_task =
1171 GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI,
1172 &handle_command,
1173 NULL);
1174 GNUNET_SCHEDULER_add_shutdown (&do_stop_task, NULL);
1175}
1176
1177
1178/**
1179 * The main function to conversation.
1180 *
1181 * @param argc number of arguments from the command line
1182 * @param argv command line arguments
1183 * @return 0 ok, 1 on error
1184 */
1185int
1186main (int argc, char *const *argv)
1187{
1188 struct GNUNET_GETOPT_CommandLineOption options[] =
1189 { GNUNET_GETOPT_option_string (
1190 'e',
1191 "ego",
1192 "NAME",
1193 gettext_noop ("sets the NAME of the ego to use for the caller ID"),
1194 &ego_name),
1195 GNUNET_GETOPT_option_string ('p',
1196 "phone",
1197 "LINE",
1198 gettext_noop (
1199 "sets the LINE to use for the phone"),
1200 &line),
1201 GNUNET_GETOPT_OPTION_END };
1202 int ret;
1203
1204 int flags;
1205 flags = fcntl (0, F_GETFL, 0);
1206 flags |= O_NONBLOCK;
1207 if (0 != fcntl (0, F_SETFL, flags))
1208 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "fcntl");
1209 stdin_fh = GNUNET_DISK_get_handle_from_int_fd (0);
1210
1211 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
1212 return 2;
1213 ret =
1214 GNUNET_PROGRAM_run (argc,
1215 argv,
1216 "gnunet-conversation",
1217 gettext_noop (
1218 "Enables having a conversation with other GNUnet users."),
1219 options,
1220 &run,
1221 NULL);
1222 GNUNET_free_nz ((void *) argv);
1223 if (NULL != cfg)
1224 {
1225 GNUNET_CONFIGURATION_destroy (cfg);
1226 cfg = NULL;
1227 }
1228 return (GNUNET_OK == ret) ? 0 : 1;
1229}
1230
1231
1232/* end of gnunet-conversation.c */
diff --git a/src/contrib/service/conversation/gnunet-helper-audio-playback-gst.c b/src/contrib/service/conversation/gnunet-helper-audio-playback-gst.c
new file mode 100644
index 000000000..f1e1e773d
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet-helper-audio-playback-gst.c
@@ -0,0 +1,405 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013 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 * @file conversation/gnunet-helper-audio-playback-gst.c
22 * @brief program to playback audio data to the speaker (GStreamer version)
23 * @author LRN
24 */
25#include "platform.h"
26#include "gnunet_util_lib.h"
27#include "gnunet_protocols.h"
28#include "conversation.h"
29#include "gnunet_constants.h"
30#include "gnunet_core_service.h"
31
32#include <gst/gst.h>
33#include <gst/audio/gstaudiobasesrc.h>
34#include <gst/app/gstappsrc.h>
35#include <glib.h>
36
37#define DEBUG_READ_PURE_OGG 1
38
39/**
40 * How much data to read in one go
41 */
42#define MAXLINE 4096
43
44/**
45 * Max number of microseconds to buffer in audiosink.
46 * Default is 1000
47 */
48#define BUFFER_TIME 1000
49
50/**
51 * Min number of microseconds to buffer in audiosink.
52 * Default is 1000
53 */
54#define LATENCY_TIME 1000
55
56/**
57 * Tokenizer for the data we get from stdin
58 */
59struct GNUNET_MessageStreamTokenizer *stdin_mst;
60
61/**
62 * Main pipeline.
63 */
64static GstElement *pipeline;
65
66/**
67 * Appsrc instance into which we write data for the pipeline.
68 */
69static GstElement *source;
70
71static GstElement *demuxer;
72static GstElement *decoder;
73static GstElement *conv;
74static GstElement *resampler;
75static GstElement *sink;
76
77/**
78 * Set to 1 to break the reading loop
79 */
80static int abort_read;
81
82
83static void
84sink_child_added (GstChildProxy *child_proxy,
85 GObject *object,
86 gchar *name,
87 gpointer user_data)
88{
89 if (GST_IS_AUDIO_BASE_SRC (object))
90 g_object_set (object,
91 "buffer-time", (gint64) BUFFER_TIME,
92 "latency-time", (gint64) LATENCY_TIME,
93 NULL);
94}
95
96
97static void
98ogg_pad_added (GstElement *element,
99 GstPad *pad,
100 gpointer data)
101{
102 GstPad *sinkpad;
103 GstElement *decoder = (GstElement *) data;
104
105 /* We can now link this pad with the opus-decoder sink pad */
106 sinkpad = gst_element_get_static_pad (decoder, "sink");
107
108 gst_pad_link (pad, sinkpad);
109
110 gst_element_link_many (decoder, conv, resampler, sink, NULL);
111
112 gst_object_unref (sinkpad);
113}
114
115
116static void
117quit ()
118{
119 if (NULL != source)
120 gst_app_src_end_of_stream (GST_APP_SRC (source));
121 if (NULL != pipeline)
122 gst_element_set_state (pipeline, GST_STATE_NULL);
123 abort_read = 1;
124}
125
126
127static gboolean
128bus_call (GstBus *bus, GstMessage *msg, gpointer data)
129{
130 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
131 "Bus message\n");
132 switch (GST_MESSAGE_TYPE (msg))
133 {
134 case GST_MESSAGE_EOS:
135 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
136 "End of stream\n");
137 quit ();
138 break;
139
140 case GST_MESSAGE_ERROR:
141 {
142 gchar *debug;
143 GError *error;
144
145 gst_message_parse_error (msg, &error, &debug);
146 g_free (debug);
147
148 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
149 "Error: %s\n",
150 error->message);
151 g_error_free (error);
152
153 quit ();
154 break;
155 }
156
157 default:
158 break;
159 }
160
161 return TRUE;
162}
163
164
165static void
166signalhandler (int s)
167{
168 quit ();
169}
170
171
172static int
173feed_buffer_to_gst (const char *audio, size_t b_len)
174{
175 GstBuffer *b;
176 gchar *bufspace;
177 GstFlowReturn flow;
178
179 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
180 "Feeding %u bytes to GStreamer\n",
181 (unsigned int) b_len);
182
183 bufspace = g_memdup2 (audio, b_len);
184 b = gst_buffer_new_wrapped (bufspace, b_len);
185 if (NULL == b)
186 {
187 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
188 "Failed to wrap a buffer\n");
189 g_free (bufspace);
190 return GNUNET_SYSERR;
191 }
192 flow = gst_app_src_push_buffer (GST_APP_SRC (source), b);
193 /* They all return GNUNET_OK, because currently player stops when
194 * data stops coming. This might need to be changed for the player
195 * to also stop when pipeline breaks.
196 */
197 switch (flow)
198 {
199 case GST_FLOW_OK:
200 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
201 "Fed %u bytes to the pipeline\n",
202 (unsigned int) b_len);
203 break;
204
205 case GST_FLOW_FLUSHING:
206 /* buffer was dropped, because pipeline state is not PAUSED or PLAYING */
207 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
208 "Dropped a buffer\n");
209 break;
210
211 case GST_FLOW_EOS:
212 /* end of stream */
213 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
214 "EOS\n");
215 break;
216
217 default:
218 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
219 "Unexpected push result\n");
220 break;
221 }
222 return GNUNET_OK;
223}
224
225
226/**
227 * Message callback
228 *
229 * @param msg message we received.
230 * @return #GNUNET_OK on success,
231 * #GNUNET_NO to stop further processing due to disconnect (no error)
232 * #GNUNET_SYSERR to stop further processing due to error
233 */
234static int
235stdin_receiver (void *cls,
236 const struct GNUNET_MessageHeader *msg)
237{
238 struct AudioMessage *audio;
239 size_t b_len;
240
241 switch (ntohs (msg->type))
242 {
243 case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO:
244 audio = (struct AudioMessage *) msg;
245
246 b_len = ntohs (audio->header.size) - sizeof(struct AudioMessage);
247 feed_buffer_to_gst ((const char *) &audio[1], b_len);
248 break;
249
250 default:
251 break;
252 }
253 return GNUNET_OK;
254}
255
256
257int
258main (int argc, char **argv)
259{
260 GstBus *bus;
261 guint bus_watch_id;
262 uint64_t toff;
263
264 typedef void (*SignalHandlerPointer) (int);
265
266 SignalHandlerPointer inthandler, termhandler;
267#ifdef DEBUG_READ_PURE_OGG
268 int read_pure_ogg = getenv ("GNUNET_READ_PURE_OGG") ? 1 : 0;
269#endif
270
271 inthandler = signal (SIGINT,
272 &signalhandler);
273 termhandler = signal (SIGTERM,
274 &signalhandler);
275
276 /* Initialisation */
277 gst_init (&argc, &argv);
278
279 GNUNET_assert (GNUNET_OK ==
280 GNUNET_log_setup ("gnunet-helper-audio-playback-gst",
281 "WARNING",
282 NULL));
283
284 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
285 "Audio sink starts\n");
286
287 stdin_mst = GNUNET_MST_create (&stdin_receiver,
288 NULL);
289
290 /* Create gstreamer elements */
291 pipeline = gst_pipeline_new ("audio-player");
292 source = gst_element_factory_make ("appsrc", "audio-input");
293 demuxer = gst_element_factory_make ("oggdemux", "ogg-demuxer");
294 decoder = gst_element_factory_make ("opusdec", "opus-decoder");
295 conv = gst_element_factory_make ("audioconvert", "converter");
296 resampler = gst_element_factory_make ("audioresample", "resampler");
297 sink = gst_element_factory_make ("autoaudiosink", "audiosink");
298
299 if (! pipeline || ! source || ! conv || ! resampler || ! decoder ||
300 ! demuxer || ! sink)
301 {
302 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
303 "One element could not be created. Exiting.\n");
304 return -1;
305 }
306
307 g_signal_connect (sink,
308 "child-added",
309 G_CALLBACK (sink_child_added),
310 NULL);
311 g_signal_connect (demuxer,
312 "pad-added",
313 G_CALLBACK (ogg_pad_added),
314 decoder);
315
316 /* Keep a reference to it, we operate on it */
317 gst_object_ref (GST_OBJECT (source));
318
319 /* Set up the pipeline */
320
321 /* we feed appsrc as fast as possible, it just blocks when it's full */
322 g_object_set (G_OBJECT (source),
323/* "format", GST_FORMAT_TIME,*/
324 "block", TRUE,
325 "is-live", TRUE,
326 NULL);
327
328 g_object_set (G_OBJECT (decoder),
329/* "plc", FALSE,*/
330/* "apply-gain", TRUE,*/
331 "use-inband-fec", TRUE,
332 NULL);
333
334 /* we add a message handler */
335 bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
336 bus_watch_id = gst_bus_add_watch (bus, bus_call, pipeline);
337 gst_object_unref (bus);
338
339 /* we add all elements into the pipeline */
340 /* audio-input | ogg-demuxer | opus-decoder | converter | resampler | audiosink */
341 gst_bin_add_many (GST_BIN (pipeline), source, demuxer, decoder, conv,
342 resampler, sink, NULL);
343
344 /* we link the elements together */
345 gst_element_link_many (source, demuxer, NULL);
346
347 /* Set the pipeline to "playing" state*/
348 GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n");
349 gst_element_set_state (pipeline, GST_STATE_PLAYING);
350
351 GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running...\n");
352 /* Iterate */
353 toff = 0;
354 while (! abort_read)
355 {
356 char readbuf[MAXLINE];
357 int ret;
358
359 ret = read (0, readbuf, sizeof(readbuf));
360 if (0 > ret)
361 {
362 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
363 _ ("Read error from STDIN: %d %s\n"),
364 ret, strerror (errno));
365 break;
366 }
367 toff += ret;
368 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
369 "Received %d bytes of audio data (total: %llu)\n",
370 (int) ret,
371 (unsigned long long) toff);
372 if (0 == ret)
373 break;
374#ifdef DEBUG_READ_PURE_OGG
375 if (read_pure_ogg)
376 {
377 feed_buffer_to_gst (readbuf, ret);
378 }
379 else
380#endif
381 GNUNET_MST_from_buffer (stdin_mst,
382 readbuf,
383 ret,
384 GNUNET_NO,
385 GNUNET_NO);
386 }
387 GNUNET_MST_destroy (stdin_mst);
388
389 signal (SIGINT, inthandler);
390 signal (SIGINT, termhandler);
391
392 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
393 "Returned, stopping playback\n");
394 quit ();
395
396 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
397 "Deleting pipeline\n");
398 gst_object_unref (GST_OBJECT (source));
399 source = NULL;
400 gst_object_unref (GST_OBJECT (pipeline));
401 pipeline = NULL;
402 g_source_remove (bus_watch_id);
403
404 return 0;
405}
diff --git a/src/contrib/service/conversation/gnunet-helper-audio-playback.c b/src/contrib/service/conversation/gnunet-helper-audio-playback.c
new file mode 100644
index 000000000..dfa400d71
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet-helper-audio-playback.c
@@ -0,0 +1,888 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013 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 * @file conversation/gnunet-helper-audio-playback.c
22 * @brief program to playback audio data to the speaker
23 * @author Siomon Dieterle
24 * @author Andreas Fuchs
25 * @author Christian Grothoff
26 */
27#include "platform.h"
28#include "gnunet_util_lib.h"
29#include "gnunet_protocols.h"
30#include "conversation.h"
31#include "gnunet_constants.h"
32#include "gnunet_core_service.h"
33
34#include <pulse/simple.h>
35#include <pulse/error.h>
36#include <pulse/rtclock.h>
37
38#include <pulse/pulseaudio.h>
39#include <opus/opus.h>
40#include <opus/opus_types.h>
41#include <ogg/ogg.h>
42
43#define DEBUG_READ_PURE_OGG 1
44#define DEBUG_DUMP_DECODED_OGG 1
45
46#define MAXLINE 4096
47
48#define SAMPLING_RATE 48000
49
50#define CHANNELS 1
51
52/* 120ms at 48000 */
53#define MAX_FRAME_SIZE (960 * 6)
54
55/**
56 * Pulseaudio specification. May change in the future.
57 */
58static pa_sample_spec sample_spec = {
59 .format = PA_SAMPLE_FLOAT32LE,
60 .rate = SAMPLING_RATE,
61 .channels = CHANNELS
62};
63
64#ifdef DEBUG_DUMP_DECODED_OGG
65static int dump_to_stdout;
66#endif
67
68/**
69 * Pulseaudio mainloop api
70 */
71static pa_mainloop_api *mainloop_api;
72
73/**
74 * Pulseaudio threaded mainloop
75 */
76static pa_threaded_mainloop *m;
77
78/**
79 * Pulseaudio context
80 */
81static pa_context *context;
82
83/**
84 * Pulseaudio output stream
85 */
86static pa_stream *stream_out;
87
88/**
89 * OPUS decoder
90 */
91static OpusDecoder *dec;
92
93/**
94 * PCM data buffer
95 */
96static float *pcm_buffer;
97
98/**
99 * Number of samples for one frame
100 */
101static int frame_size;
102
103/**
104 * Pipe we use to signal the main loop that we are ready to receive.
105 */
106static int ready_pipe[2];
107
108/**
109 * Ogg I/O state.
110 */
111static ogg_sync_state oy;
112
113/**
114 * Ogg stream state.
115 */
116static ogg_stream_state os;
117
118static int channels;
119
120static int preskip;
121
122static float gain;
123
124GNUNET_NETWORK_STRUCT_BEGIN
125
126/* OggOpus spec says the numbers must be in little-endian order */
127struct OpusHeadPacket
128{
129 uint8_t magic[8];
130 uint8_t version;
131 uint8_t channels;
132 uint16_t preskip GNUNET_PACKED;
133 uint32_t sampling_rate GNUNET_PACKED;
134 uint16_t gain GNUNET_PACKED;
135 uint8_t channel_mapping;
136};
137
138GNUNET_NETWORK_STRUCT_END
139
140/**
141 * Process an Opus header and setup the opus decoder based on it.
142 * It takes several pointers for header values which are needed
143 * elsewhere in the code.
144 */
145static OpusDecoder *
146process_header (ogg_packet *op)
147{
148 int err;
149 OpusDecoder *dec;
150 struct OpusHeadPacket header;
151
152 if (((unsigned int) op->bytes) < sizeof(header))
153 return NULL;
154 GNUNET_memcpy (&header,
155 op->packet,
156 sizeof(header));
157 header.preskip = GNUNET_le16toh (header.preskip);
158 header.sampling_rate = GNUNET_le32toh (header.sampling_rate);
159 header.gain = GNUNET_le16toh (header.gain);
160
161 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
162 "Header: v%u, %u-ch, skip %u, %uHz, %u gain\n",
163 header.version,
164 header.channels,
165 header.preskip,
166 header.sampling_rate,
167 header.gain);
168 channels = header.channels;
169 preskip = header.preskip;
170
171 if (header.channel_mapping != 0)
172 {
173 fprintf (stderr,
174 "This implementation does not support non-mono streams\n");
175 return NULL;
176 }
177
178 dec = opus_decoder_create (SAMPLING_RATE, channels, &err);
179 if (OPUS_OK != err)
180 {
181 fprintf (stderr,
182 "Cannot create encoder: %s\n",
183 opus_strerror (err));
184 return NULL;
185 }
186 if (! dec)
187 {
188 fprintf (stderr,
189 "Decoder initialization failed: %s\n",
190 opus_strerror (err));
191 return NULL;
192 }
193
194 if (0 != header.gain)
195 {
196 /*Gain API added in a newer libopus version, if we don't have it
197 we apply the gain ourselves. We also add in a user provided
198 manual gain at the same time.*/
199 int gainadj = (int) header.gain;
200 err = opus_decoder_ctl (dec, OPUS_SET_GAIN (gainadj));
201 if (OPUS_UNIMPLEMENTED == err)
202 {
203 gain = pow (10.0, gainadj / 5120.0);
204 }
205 else if (OPUS_OK != err)
206 {
207 fprintf (stderr, "Error setting gain: %s\n", opus_strerror (err));
208 return NULL;
209 }
210 }
211
212 return dec;
213}
214
215
216#ifdef DEBUG_DUMP_DECODED_OGG
217static size_t
218fwrite_le32 (opus_int32 i32, FILE *file)
219{
220 unsigned char buf[4];
221
222 buf[0] = (unsigned char) (i32 & 0xFF);
223 buf[1] = (unsigned char) (i32 >> 8 & 0xFF);
224 buf[2] = (unsigned char) (i32 >> 16 & 0xFF);
225 buf[3] = (unsigned char) (i32 >> 24 & 0xFF);
226 return fwrite (buf, 4, 1, file);
227}
228
229
230static size_t
231fwrite_le16 (int i16, FILE *file)
232{
233 unsigned char buf[2];
234
235 buf[0] = (unsigned char) (i16 & 0xFF);
236 buf[1] = (unsigned char) (i16 >> 8 & 0xFF);
237 return fwrite (buf, 2, 1, file);
238}
239
240
241static int
242write_wav_header ()
243{
244 int ret;
245 FILE *file = stdout;
246
247 ret = fprintf (file, "RIFF") >= 0;
248 ret &= fwrite_le32 (0x7fffffff, file);
249
250 ret &= fprintf (file, "WAVEfmt ") >= 0;
251 ret &= fwrite_le32 (16, file);
252 ret &= fwrite_le16 (1, file);
253 ret &= fwrite_le16 (channels, file);
254 ret &= fwrite_le32 (SAMPLING_RATE, file);
255 ret &= fwrite_le32 (2 * channels * SAMPLING_RATE, file);
256 ret &= fwrite_le16 (2 * channels, file);
257 ret &= fwrite_le16 (16, file);
258
259 ret &= fprintf (file, "data") >= 0;
260 ret &= fwrite_le32 (0x7fffffff, file);
261
262 return ! ret ? -1 : 16;
263}
264
265
266#endif
267
268
269static int64_t
270audio_write (int64_t maxout)
271{
272 int64_t sampout = 0;
273 int tmp_skip;
274 unsigned out_len;
275 unsigned to_write;
276 float *output;
277
278#ifdef DEBUG_DUMP_DECODED_OGG
279 static int wrote_wav_header;
280
281 if (dump_to_stdout && ! wrote_wav_header)
282 {
283 write_wav_header ();
284 wrote_wav_header = 1;
285 }
286#endif
287 maxout = 0 > maxout ? 0 : maxout;
288 do
289 {
290 tmp_skip = (preskip > frame_size) ? (int) frame_size : preskip;
291 preskip -= tmp_skip;
292 output = pcm_buffer + channels * tmp_skip;
293 out_len = frame_size - tmp_skip;
294 if (out_len > MAX_FRAME_SIZE)
295 exit (6);
296 frame_size = 0;
297
298 to_write = out_len < maxout ? out_len : (unsigned) maxout;
299 if (0 < maxout)
300 {
301 int64_t wrote = 0;
302 wrote = to_write;
303 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
304 "Writing %u * %u * %u = %llu bytes into PA\n",
305 to_write,
306 channels,
307 (unsigned int) sizeof(float),
308 (unsigned long long) (to_write * channels * sizeof(float)));
309#ifdef DEBUG_DUMP_DECODED_OGG
310 if (dump_to_stdout)
311 {
312# define fminf(_x, _y) ((_x) < (_y) ? (_x) : (_y))
313# define fmaxf(_x, _y) ((_x) > (_y) ? (_x) : (_y))
314# define float2int(flt) ((int) (floor (.5 + flt)))
315 int i;
316 int16_t *out = alloca (sizeof(short) * MAX_FRAME_SIZE * channels);
317 for (i = 0; i < (int) out_len * channels; i++)
318 out[i] = (short) float2int (fmaxf (-32768, fminf (output[i] * 32768.f,
319 32767)));
320
321 fwrite (out, 2 * channels, out_len < maxout ? out_len : maxout, stdout);
322 }
323 else
324#endif
325 if (pa_stream_write
326 (stream_out, output, to_write * channels * sizeof(float), NULL, 0,
327 PA_SEEK_RELATIVE) < 0)
328 {
329 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
330 _ ("pa_stream_write() failed: %s\n"),
331 pa_strerror (pa_context_errno (context)));
332 }
333 sampout += wrote;
334 maxout -= wrote;
335 }
336 }
337 while (0 < frame_size && 0 < maxout);
338
339 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
340 "Wrote %" PRId64 " samples\n",
341 sampout);
342 return sampout;
343}
344
345
346/**
347 * Pulseaudio shutdown task
348 */
349static void
350quit (int ret)
351{
352 mainloop_api->quit (mainloop_api,
353 ret);
354 exit (ret);
355}
356
357
358static void
359ogg_demux_and_decode ()
360{
361 ogg_page og;
362 static int stream_init;
363 int64_t page_granule = 0;
364 ogg_packet op;
365 static int has_opus_stream;
366 static int has_tags_packet;
367 static int32_t opus_serialno;
368 static int64_t link_out;
369 static int64_t packet_count;
370 int eos = 0;
371 static int total_links;
372 static int gran_offset;
373
374 while (1 == ogg_sync_pageout (&oy, &og))
375 {
376 if (0 == stream_init)
377 {
378 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
379 "Initialized the stream\n");
380 ogg_stream_init (&os, ogg_page_serialno (&og));
381 stream_init = 1;
382 }
383 if (ogg_page_serialno (&og) != os.serialno)
384 {
385 /* so all streams are read. */
386 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
387 "Re-set serial number\n");
388 ogg_stream_reset_serialno (&os, ogg_page_serialno (&og));
389 }
390 /*Add page to the bitstream*/
391 ogg_stream_pagein (&os, &og);
392 page_granule = ogg_page_granulepos (&og);
393 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
394 "Reading page that ends at %" PRId64 "\n",
395 page_granule);
396 /*Extract all available packets*/
397 while (1 == ogg_stream_packetout (&os, &op))
398 {
399 /*OggOpus streams are identified by a magic string in the initial
400 stream header.*/
401 if (op.b_o_s && (op.bytes >= 8) && ! memcmp (op.packet, "OpusHead", 8))
402 {
403 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
404 "Got Opus Header\n");
405 if (has_opus_stream && has_tags_packet)
406 {
407 /*If we're seeing another BOS OpusHead now it means
408 the stream is chained without an EOS.
409 This can easily happen if record helper is terminated unexpectedly.
410 */
411 has_opus_stream = 0;
412 if (dec)
413 opus_decoder_destroy (dec);
414 dec = NULL;
415 fprintf (stderr,
416 "\nWarning: stream %" PRId64
417 " ended without EOS and a new stream began.\n",
418 (int64_t) os.serialno);
419 }
420 if (! has_opus_stream)
421 {
422 if ((packet_count > 0) && (opus_serialno == os.serialno) )
423 {
424 fprintf (stderr,
425 "\nError: Apparent chaining without changing serial number (%"
426 PRId64 "==%" PRId64 ").\n",
427 (int64_t) opus_serialno, (int64_t) os.serialno);
428 quit (1);
429 }
430 opus_serialno = os.serialno;
431 has_opus_stream = 1;
432 has_tags_packet = 0;
433 link_out = 0;
434 packet_count = 0;
435 eos = 0;
436 total_links++;
437 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
438 "Got header for stream %" PRId64 ", this is %dth link\n",
439 (int64_t) opus_serialno, total_links);
440 }
441 else
442 {
443 fprintf (stderr, "\nWarning: ignoring opus stream %" PRId64 "\n",
444 (int64_t) os.serialno);
445 }
446 }
447 if (! has_opus_stream || (os.serialno != opus_serialno) )
448 {
449 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
450 "breaking out\n");
451 break;
452 }
453 /*If first packet in a logical stream, process the Opus header*/
454 if (0 == packet_count)
455 {
456 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
457 "Decoding header\n");
458 dec = process_header (&op);
459 if (! dec)
460 quit (1);
461
462 if ((0 != ogg_stream_packetout (&os, &op)) || (255 ==
463 og.header[og.header_len
464 - 1]) )
465 {
466 /*The format specifies that the initial header and tags packets are on their
467 own pages. To aid implementors in discovering that their files are wrong
468 we reject them explicitly here. In some player designs files like this would
469 fail even without an explicit test.*/
470 fprintf (stderr,
471 "Extra packets on initial header page. Invalid stream.\n");
472 quit (1);
473 }
474
475 /*Remember how many samples at the front we were told to skip
476 so that we can adjust the timestamp counting.*/
477 gran_offset = preskip;
478
479 if (! pcm_buffer)
480 {
481 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
482 "Allocating %u * %u * %u = %llu bytes of buffer space\n",
483 MAX_FRAME_SIZE,
484 channels,
485 (unsigned int) sizeof(float),
486 (unsigned long long) (MAX_FRAME_SIZE * channels
487 * sizeof(float)));
488 pcm_buffer = pa_xmalloc (sizeof(float) * MAX_FRAME_SIZE * channels);
489 }
490 }
491 else if (1 == packet_count)
492 {
493 has_tags_packet = 1;
494 if ((0 != ogg_stream_packetout (&os, &op)) || (255 ==
495 og.header[og.header_len
496 - 1]) )
497 {
498 fprintf (stderr,
499 "Extra packets on initial tags page. Invalid stream.\n");
500 quit (1);
501 }
502 }
503 else
504 {
505 int ret;
506 int64_t maxout;
507 int64_t outsamp;
508
509 /*End of stream condition*/
510 if (op.e_o_s && (os.serialno == opus_serialno) )
511 {
512 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
513 "Got EOS\n");
514 eos = 1; /* don't care for anything except opus eos */
515 }
516
517 /*Decode Opus packet*/
518 ret = opus_decode_float (dec,
519 (const unsigned char *) op.packet,
520 op.bytes,
521 pcm_buffer,
522 MAX_FRAME_SIZE, 0);
523
524 /*If the decoder returned less than zero, we have an error.*/
525 if (0 > ret)
526 {
527 fprintf (stderr, "Decoding error: %s\n", opus_strerror (ret));
528 break;
529 }
530 frame_size = ret;
531 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
532 "Decoded %d bytes/channel (%d bytes) from %u compressed bytes\n",
533 ret,
534 ret * channels,
535 (unsigned int) op.bytes);
536
537 /*Apply header gain, if we're not using an opus library new
538 enough to do this internally.*/
539 if (0 != gain)
540 {
541 int i;
542 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
543 "Applying gain %f\n",
544 gain);
545 for (i = 0; i < frame_size * channels; i++)
546 pcm_buffer[i] *= gain;
547 }
548
549 /*This handles making sure that our output duration respects
550 the final end-trim by not letting the output sample count
551 get ahead of the granpos indicated value.*/
552 maxout = ((page_granule - gran_offset) * SAMPLING_RATE / 48000)
553 - link_out;
554 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
555 "Writing audio packet %" PRId64 ", at most %" PRId64
556 " samples\n",
557 packet_count, maxout);
558
559 outsamp = audio_write (0 > maxout ? 0 : maxout);
560 link_out += outsamp;
561 }
562 packet_count++;
563 }
564 if (eos)
565 {
566 has_opus_stream = 0;
567 if (dec)
568 opus_decoder_destroy (dec);
569 dec = NULL;
570 }
571 }
572}
573
574
575/**
576 * Message callback
577 *
578 * @param msg message we received.
579 * @return #GNUNET_OK on success,
580 * #GNUNET_NO to stop further processing due to disconnect (no error)
581 * #GNUNET_SYSERR to stop further processing due to error
582 */
583static int
584stdin_receiver (void *cls,
585 const struct GNUNET_MessageHeader *msg)
586{
587 struct AudioMessage *audio;
588 char *data;
589 size_t payload_len;
590
591 (void) cls;
592 switch (ntohs (msg->type))
593 {
594 case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO:
595 audio = (struct AudioMessage *) msg;
596 payload_len = ntohs (audio->header.size) - sizeof(struct AudioMessage);
597
598 /*Get the ogg buffer for writing*/
599 data = ogg_sync_buffer (&oy, payload_len);
600 /*Read bitstream from input file*/
601 GNUNET_memcpy (data, (const unsigned char *) &audio[1], payload_len);
602 ogg_sync_wrote (&oy, payload_len);
603
604 ogg_demux_and_decode ();
605 break;
606
607 default:
608 break;
609 }
610 return GNUNET_OK;
611}
612
613
614/**
615 * Callback when data is there for playback
616 */
617static void
618stream_write_callback (pa_stream *s,
619 size_t length,
620 void *userdata)
621{
622 /* unblock 'main' */
623 (void) userdata;
624 (void) length;
625 (void) s;
626 if (-1 != ready_pipe[1])
627 {
628 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
629 "Unblocking main loop!\n");
630 (void) write (ready_pipe[1], "r", 1);
631 }
632}
633
634
635/**
636 * Exit callback for SIGTERM and SIGINT
637 */
638static void
639exit_signal_callback (pa_mainloop_api *m,
640 pa_signal_event *e,
641 int sig,
642 void *userdata)
643{
644 (void) m;
645 (void) e;
646 (void) sig;
647 (void) userdata;
648 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
649 _ ("gnunet-helper-audio-playback - Got signal, exiting\n"));
650 quit (1);
651}
652
653
654/**
655 * Pulseaudio stream state callback
656 */
657static void
658context_state_callback (pa_context *c,
659 void *userdata)
660{
661 int p;
662
663 (void) userdata;
664 GNUNET_assert (NULL != c);
665 switch (pa_context_get_state (c))
666 {
667 case PA_CONTEXT_CONNECTING:
668 case PA_CONTEXT_AUTHORIZING:
669 case PA_CONTEXT_SETTING_NAME:
670 break;
671
672 case PA_CONTEXT_READY:
673 {
674 GNUNET_assert (! stream_out);
675 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
676 _ ("Connection established.\n"));
677 if (! (stream_out =
678 pa_stream_new (c, "GNUNET VoIP playback", &sample_spec, NULL)))
679 {
680 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
681 _ ("pa_stream_new() failed: %s\n"),
682 pa_strerror (pa_context_errno (c)));
683 goto fail;
684 }
685 pa_stream_set_write_callback (stream_out,
686 &stream_write_callback,
687 NULL);
688 if ((p =
689 pa_stream_connect_playback (stream_out, NULL,
690 NULL,
691 PA_STREAM_ADJUST_LATENCY
692 | PA_STREAM_INTERPOLATE_TIMING
693 | PA_STREAM_AUTO_TIMING_UPDATE,
694 NULL, NULL)) < 0)
695 {
696 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
697 _ ("pa_stream_connect_playback() failed: %s\n"),
698 pa_strerror (pa_context_errno (c)));
699 goto fail;
700 }
701 break;
702 }
703
704 case PA_CONTEXT_TERMINATED:
705 quit (0);
706 break;
707
708 case PA_CONTEXT_FAILED:
709 default:
710 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
711 _ ("Connection failure: %s\n"),
712 pa_strerror (pa_context_errno (c)));
713 goto fail;
714 }
715 return;
716fail:
717 quit (1);
718}
719
720
721/**
722 * Pulseaudio initialization
723 */
724static void
725pa_init ()
726{
727 int r;
728
729 if (! pa_sample_spec_valid (&sample_spec))
730 {
731 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
732 _ ("Wrong Spec\n"));
733 }
734 /* set up threaded playback mainloop */
735 if (! (m = pa_threaded_mainloop_new ()))
736 {
737 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
738 _ ("pa_mainloop_new() failed.\n"));
739 }
740 mainloop_api = pa_threaded_mainloop_get_api (m);
741 /* listen to signals */
742 r = pa_signal_init (mainloop_api);
743 GNUNET_assert (r == 0);
744 pa_signal_new (SIGINT, exit_signal_callback, NULL);
745 pa_signal_new (SIGTERM, exit_signal_callback, NULL);
746
747
748 /* connect to the main pulseaudio context */
749 if (! (context = pa_context_new (mainloop_api, "GNUnet VoIP")))
750 {
751 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
752 _ ("pa_context_new() failed.\n"));
753 }
754 pa_context_set_state_callback (context, context_state_callback, NULL);
755
756 if (pa_context_connect (context, NULL, 0, NULL) < 0)
757 {
758 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
759 _ ("pa_context_connect() failed: %s\n"),
760 pa_strerror (pa_context_errno (context)));
761 }
762 if (pa_threaded_mainloop_start (m) < 0)
763 {
764 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
765 _ ("pa_mainloop_run() failed.\n"));
766 }
767}
768
769
770static void
771ogg_init ()
772{
773 ogg_sync_init (&oy);
774}
775
776
777static void
778drain_callback (pa_stream*s, int success, void *userdata)
779{
780 (void) s;
781 (void) success;
782 (void) userdata;
783 pa_threaded_mainloop_signal (m,
784 0);
785}
786
787
788/**
789 * The main function for the playback helper.
790 *
791 * @param argc number of arguments from the command line
792 * @param argv command line arguments
793 * @return 0 ok, 1 on error
794 */
795int
796main (int argc, char *argv[])
797{
798 static unsigned long long toff;
799 char readbuf[MAXLINE];
800 struct GNUNET_MessageStreamTokenizer *stdin_mst;
801 char c;
802 ssize_t ret;
803
804#ifdef DEBUG_READ_PURE_OGG
805 int read_pure_ogg = getenv ("GNUNET_READ_PURE_OGG") ? 1 : 0;
806#endif
807
808 (void) argc;
809 (void) argv;
810 GNUNET_assert (GNUNET_OK ==
811 GNUNET_log_setup ("gnunet-helper-audio-playback",
812 "WARNING",
813 NULL));
814 if (0 != pipe (ready_pipe))
815 {
816 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "pipe");
817 return 1;
818 }
819 stdin_mst = GNUNET_MST_create (&stdin_receiver, NULL);
820 ogg_init ();
821 pa_init ();
822 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
823 "Waiting for PulseAudio to be ready.\n");
824 GNUNET_assert (1 == read (ready_pipe[0], &c, 1));
825 close (ready_pipe[0]);
826 close (ready_pipe[1]);
827 ready_pipe[0] = -1;
828 ready_pipe[1] = -1;
829#ifdef DEBUG_DUMP_DECODED_OGG
830 dump_to_stdout = getenv ("GNUNET_DUMP_DECODED_OGG") ? 1 : 0;
831#endif
832 while (1)
833 {
834 ret = read (STDIN_FILENO,
835 readbuf,
836 sizeof(readbuf));
837 toff += ret;
838 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
839 "Received %d bytes of audio data (total: %llu)\n",
840 (int) ret,
841 toff);
842 if (0 > ret)
843 {
844 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
845 _ ("Read error from STDIN: %s\n"),
846 strerror (errno));
847 break;
848 }
849 if (0 == ret)
850 break;
851#ifdef DEBUG_READ_PURE_OGG
852 if (read_pure_ogg)
853 {
854 char *data = ogg_sync_buffer (&oy, ret);
855 GNUNET_memcpy (data, readbuf, ret);
856 ogg_sync_wrote (&oy, ret);
857 ogg_demux_and_decode ();
858 }
859 else
860#endif
861 GNUNET_MST_from_buffer (stdin_mst,
862 readbuf, ret,
863 GNUNET_NO, GNUNET_NO);
864 }
865 GNUNET_MST_destroy (stdin_mst);
866 if (stream_out)
867 {
868 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
869 "Locking\n");
870 pa_threaded_mainloop_lock (m);
871 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
872 "Draining\n");
873 pa_operation *o = pa_stream_drain (stream_out, drain_callback, NULL);
874 while (pa_operation_get_state (o) == PA_OPERATION_RUNNING)
875 {
876 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
877 "Waiting\n");
878 pa_threaded_mainloop_wait (m);
879 }
880 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
881 "Unreffing\n");
882 pa_operation_unref (o);
883 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
884 "Unlocking\n");
885 pa_threaded_mainloop_unlock (m);
886 }
887 return 0;
888}
diff --git a/src/contrib/service/conversation/gnunet-helper-audio-record-gst.c b/src/contrib/service/conversation/gnunet-helper-audio-record-gst.c
new file mode 100644
index 000000000..883dd9eea
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet-helper-audio-record-gst.c
@@ -0,0 +1,393 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013 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 * @file conversation/gnunet-helper-audio-record-gst.c
22 * @brief program to record audio data from the microphone (GStreamer version)
23 * @author LRN
24 */
25#include "platform.h"
26#include "gnunet_util_lib.h"
27#include "gnunet_protocols.h"
28#include "conversation.h"
29#include "gnunet_constants.h"
30#include "gnunet_core_service.h"
31
32#include <gst/gst.h>
33#include <gst/app/gstappsink.h>
34#include <gst/audio/gstaudiobasesrc.h>
35#include <glib.h>
36
37#define DEBUG_RECORD_PURE_OGG 1
38
39/**
40 * Number of channels.
41 * Must be one of the following (from libopusenc documentation):
42 * 1, 2
43 */
44#define OPUS_CHANNELS 1
45
46/**
47 * Maximal size of a single opus packet.
48 */
49#define MAX_PAYLOAD_SIZE (1024 / OPUS_CHANNELS)
50
51/**
52 * Size of a single frame fed to the encoder, in ms.
53 * Must be one of the following (from libopus documentation):
54 * 2.5, 5, 10, 20, 40 or 60
55 */
56#define OPUS_FRAME_SIZE 40
57
58/**
59 * Expected packet loss to prepare for, in percents.
60 */
61#define PACKET_LOSS_PERCENTAGE 1
62
63/**
64 * Set to 1 to enable forward error correction.
65 * Set to 0 to disable.
66 */
67#define INBAND_FEC_MODE 1
68
69/**
70 * Max number of microseconds to buffer in audiosource.
71 * Default is 200000
72 */
73#define BUFFER_TIME 1000 /* 1ms */
74
75/**
76 * Min number of microseconds to buffer in audiosource.
77 * Default is 10000
78 */
79#define LATENCY_TIME 1000 /* 1ms */
80
81/**
82 * Maximum delay in multiplexing streams, in ns.
83 * Setting this to 0 forces page flushing, which
84 * decreases delay, but increases overhead.
85 */
86#define OGG_MAX_DELAY 0
87
88/**
89 * Maximum delay for sending out a page, in ns.
90 * Setting this to 0 forces page flushing, which
91 * decreases delay, but increases overhead.
92 */
93#define OGG_MAX_PAGE_DELAY 0
94
95/**
96 * Main pipeline.
97 */
98static GstElement *pipeline;
99
100#ifdef DEBUG_RECORD_PURE_OGG
101static int dump_pure_ogg;
102#endif
103
104static void
105quit ()
106{
107 if (NULL != pipeline)
108 gst_element_set_state (pipeline, GST_STATE_NULL);
109}
110
111
112static gboolean
113bus_call (GstBus *bus, GstMessage *msg, gpointer data)
114{
115 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Bus message\n");
116 switch (GST_MESSAGE_TYPE (msg))
117 {
118 case GST_MESSAGE_EOS:
119 GNUNET_log (GNUNET_ERROR_TYPE_INFO, "End of stream\n");
120 quit ();
121 break;
122
123 case GST_MESSAGE_ERROR:
124 {
125 gchar *debug;
126 GError *error;
127
128 gst_message_parse_error (msg, &error, &debug);
129 g_free (debug);
130
131 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error: %s\n", error->message);
132 g_error_free (error);
133
134 quit ();
135 break;
136 }
137
138 default:
139 break;
140 }
141
142 return TRUE;
143}
144
145
146void
147source_child_added (GstChildProxy *child_proxy, GObject *object, gchar *name,
148 gpointer user_data)
149{
150 if (GST_IS_AUDIO_BASE_SRC (object))
151 g_object_set (object, "buffer-time", (gint64) BUFFER_TIME, "latency-time",
152 (gint64) LATENCY_TIME, NULL);
153}
154
155
156static void
157signalhandler (int s)
158{
159 quit ();
160}
161
162
163int
164main (int argc, char **argv)
165{
166 GstElement *source, *filter, *encoder, *conv, *resampler, *sink, *oggmux;
167 GstCaps *caps;
168 GstBus *bus;
169 guint bus_watch_id;
170 struct AudioMessage audio_message;
171 int abort_send = 0;
172
173 typedef void (*SignalHandlerPointer) (int);
174
175 SignalHandlerPointer inthandler, termhandler;
176 inthandler = signal (SIGINT, signalhandler);
177 termhandler = signal (SIGTERM, signalhandler);
178
179#ifdef DEBUG_RECORD_PURE_OGG
180 dump_pure_ogg = getenv ("GNUNET_RECORD_PURE_OGG") ? 1 : 0;
181#endif
182
183 /* Initialisation */
184 gst_init (&argc, &argv);
185
186 GNUNET_assert (GNUNET_OK ==
187 GNUNET_log_setup ("gnunet-helper-audio-record",
188 "WARNING",
189 NULL));
190
191 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
192 "Audio source starts\n");
193
194 audio_message.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
195
196 /* Create gstreamer elements */
197 pipeline = gst_pipeline_new ("audio-recorder");
198 source = gst_element_factory_make ("autoaudiosrc", "audiosource");
199 filter = gst_element_factory_make ("capsfilter", "filter");
200 conv = gst_element_factory_make ("audioconvert", "converter");
201 resampler = gst_element_factory_make ("audioresample", "resampler");
202 encoder = gst_element_factory_make ("opusenc", "opus-encoder");
203 oggmux = gst_element_factory_make ("oggmux", "ogg-muxer");
204 sink = gst_element_factory_make ("appsink", "audio-output");
205
206 if (! pipeline || ! filter || ! source || ! conv || ! resampler ||
207 ! encoder || ! oggmux || ! sink)
208 {
209 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
210 "One element could not be created. Exiting.\n");
211 return -1;
212 }
213
214 g_signal_connect (source, "child-added", G_CALLBACK (source_child_added),
215 NULL);
216
217 /* Set up the pipeline */
218
219 caps = gst_caps_new_simple ("audio/x-raw",
220 "format", G_TYPE_STRING, "S16LE",
221/* "rate", G_TYPE_INT, SAMPLING_RATE,*/
222 "channels", G_TYPE_INT, OPUS_CHANNELS,
223/* "layout", G_TYPE_STRING, "interleaved",*/
224 NULL);
225 g_object_set (G_OBJECT (filter),
226 "caps", caps,
227 NULL);
228 gst_caps_unref (caps);
229
230 g_object_set (G_OBJECT (encoder),
231/* "bitrate", 64000, */
232/* "bandwidth", OPUS_BANDWIDTH_FULLBAND, */
233 "inband-fec", INBAND_FEC_MODE,
234 "packet-loss-percentage", PACKET_LOSS_PERCENTAGE,
235 "max-payload-size", MAX_PAYLOAD_SIZE,
236 "audio", FALSE, /* VoIP, not audio */
237 "frame-size", OPUS_FRAME_SIZE,
238 NULL);
239
240 g_object_set (G_OBJECT (oggmux),
241 "max-delay", OGG_MAX_DELAY,
242 "max-page-delay", OGG_MAX_PAGE_DELAY,
243 NULL);
244
245 /* we add a message handler */
246 bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
247 bus_watch_id = gst_bus_add_watch (bus, bus_call, pipeline);
248 gst_object_unref (bus);
249
250 /* we add all elements into the pipeline */
251 /* audiosource | converter | resampler | opus-encoder | audio-output */
252 gst_bin_add_many (GST_BIN (pipeline), source, filter, conv, resampler,
253 encoder,
254 oggmux, sink, NULL);
255
256 /* we link the elements together */
257 gst_element_link_many (source, filter, conv, resampler, encoder, oggmux, sink,
258 NULL);
259
260 /* Set the pipeline to "playing" state*/
261 GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n");
262 gst_element_set_state (pipeline, GST_STATE_PLAYING);
263
264
265 GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running...\n");
266 /* Iterate */
267 while (! abort_send)
268 {
269 GstSample *s;
270 GstBuffer *b;
271 GstMapInfo m;
272 size_t len, msg_size;
273 const char *ptr;
274 int phase;
275
276 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulling...\n");
277 s = gst_app_sink_pull_sample (GST_APP_SINK (sink));
278 if (NULL == s)
279 {
280 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulled NULL\n");
281 break;
282 }
283 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "...pulled!\n");
284 {
285 const GstStructure *si;
286 char *si_str;
287 GstCaps *s_caps;
288 char *caps_str;
289 si = gst_sample_get_info (s);
290 if (si)
291 {
292 si_str = gst_structure_to_string (si);
293 if (si_str)
294 {
295 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample %s\n", si_str);
296 g_free (si_str);
297 }
298 }
299 else
300 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no info\n");
301 s_caps = gst_sample_get_caps (s);
302 if (s_caps)
303 {
304 caps_str = gst_caps_to_string (s_caps);
305 if (caps_str)
306 {
307 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with caps %s\n",
308 caps_str);
309 g_free (caps_str);
310 }
311 }
312 else
313 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no caps\n");
314 }
315 b = gst_sample_get_buffer (s);
316 if ((NULL == b) || ! gst_buffer_map (b, &m, GST_MAP_READ))
317 {
318 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
319 "got NULL buffer %p or failed to map the buffer\n", b);
320 gst_sample_unref (s);
321 continue;
322 }
323
324 len = m.size;
325 if (len > UINT16_MAX - sizeof(struct AudioMessage))
326 {
327 GNUNET_break (0);
328 len = UINT16_MAX - sizeof(struct AudioMessage);
329 }
330 msg_size = sizeof(struct AudioMessage) + len;
331 audio_message.header.size = htons ((uint16_t) msg_size);
332
333 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
334 "Sending %u bytes of audio data\n", (unsigned int) msg_size);
335 for (phase = 0; phase < 2; phase++)
336 {
337 size_t offset;
338 size_t to_send;
339 ssize_t ret;
340 if (0 == phase)
341 {
342#ifdef DEBUG_RECORD_PURE_OGG
343 if (dump_pure_ogg)
344 continue;
345#endif
346 ptr = (const char *) &audio_message;
347 to_send = sizeof(audio_message);
348 }
349 else
350 {
351 ptr = (const char *) m.data;
352 to_send = len;
353 }
354 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
355 "Sending %u bytes on phase %d\n", (unsigned int) to_send,
356 phase);
357 for (offset = 0; offset < to_send; offset += ret)
358 {
359 ret = write (1, &ptr[offset], to_send - offset);
360 if (0 >= ret)
361 {
362 if (-1 == ret)
363 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
364 "Failed to write %u bytes at offset %u (total %u) in phase %d: %s\n",
365 (unsigned int) (to_send - offset),
366 (unsigned int) offset,
367 (unsigned int) (to_send + offset),
368 phase,
369 strerror (errno));
370 abort_send = 1;
371 break;
372 }
373 }
374 if (abort_send)
375 break;
376 }
377 gst_buffer_unmap (b, &m);
378 gst_sample_unref (s);
379 }
380
381 signal (SIGINT, inthandler);
382 signal (SIGINT, termhandler);
383
384 GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returned, stopping playback\n");
385 quit ();
386
387 GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Deleting pipeline\n");
388 gst_object_unref (GST_OBJECT (pipeline));
389 pipeline = NULL;
390 g_source_remove (bus_watch_id);
391
392 return 0;
393}
diff --git a/src/contrib/service/conversation/gnunet-helper-audio-record.c b/src/contrib/service/conversation/gnunet-helper-audio-record.c
new file mode 100644
index 000000000..060a7c779
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet-helper-audio-record.c
@@ -0,0 +1,807 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013 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 * @file conversation/gnunet-helper-audio-record.c
22 * @brief program to record audio data from the microphone
23 * @author Siomon Dieterle
24 * @author Andreas Fuchs
25 * @author Christian Grothoff
26 */
27#include "platform.h"
28#include "gnunet_util_lib.h"
29#include "gnunet_protocols.h"
30#include "conversation.h"
31#include "gnunet_constants.h"
32#include "gnunet_core_service.h"
33
34#include <pulse/simple.h>
35#include <pulse/error.h>
36#include <pulse/rtclock.h>
37
38#include <pulse/pulseaudio.h>
39#include <opus/opus.h>
40#include <opus/opus_types.h>
41#include <ogg/ogg.h>
42
43#define DEBUG_RECORD_PURE_OGG 1
44
45/**
46 * Sampling rate
47 */
48#define SAMPLING_RATE 48000
49
50/**
51 * How many ms of audio to buffer before encoding them.
52 * Possible values:
53 * 60, 40, 20, 10, 5, 2.5
54 */
55#define FRAME_SIZE_MS 40
56
57/**
58 * How many samples to buffer before encoding them.
59 */
60#define FRAME_SIZE (SAMPLING_RATE / 1000 * FRAME_SIZE_MS)
61
62/**
63 * Pages are committed when their size goes over this value.
64 * Note that in practice we flush pages VERY often (every frame),
65 * which means that pages NEVER really get to be this big.
66 * With one-packet-per-page, pages are roughly 100-300 bytes each.
67 *
68 * This value is chosen to make MAX_PAYLOAD_BYTES=1024 fit
69 * into a single page.
70 */
71#define PAGE_WATERLINE 800
72
73/**
74 * Maximum length of opus payload
75 */
76#define MAX_PAYLOAD_BYTES 1024
77
78/**
79 * Number of channels
80 */
81#define CHANNELS 1
82
83/**
84 * Configures the encoder's expected packet loss percentage.
85 *
86 * Higher values will trigger progressively more loss resistant behavior
87 * in the encoder at the expense of quality at a given bitrate
88 * in the lossless case, but greater quality under loss.
89 */
90#define CONV_OPUS_PACKET_LOSS_PERCENTAGE 1
91
92/**
93 * Configures the encoder's computational complexity.
94 *
95 * The supported range is 0-10 inclusive with 10 representing
96 * the highest complexity.
97 */
98#define CONV_OPUS_ENCODING_COMPLEXITY 10
99
100/**
101 * Configures the encoder's use of inband forward error correction (FEC).
102 *
103 * Note: This is only applicable to the LPC layer.
104 */
105#define CONV_OPUS_INBAND_FEC 1
106
107/**
108 * Configures the type of signal being encoded.
109 *
110 * This is a hint which helps the encoder's mode selection.
111 *
112 * Possible values:
113 * OPUS_AUTO - (default) Encoder detects the type automatically.
114 * OPUS_SIGNAL_VOICE - Bias thresholds towards choosing LPC or Hybrid modes.
115 * OPUS_SIGNAL_MUSIC - Bias thresholds towards choosing MDCT modes.
116 */
117#define CONV_OPUS_SIGNAL OPUS_SIGNAL_VOICE
118
119/**
120 * Coding mode.
121 *
122 * Possible values:
123 * OPUS_APPLICATION_VOIP - gives best quality at a given bitrate for voice
124 * signals. It enhances the input signal by high-pass filtering and
125 * emphasizing formants and harmonics. Optionally it includes in-band forward
126 * error correction to protect against packet loss. Use this mode for typical
127 * VoIP applications. Because of the enhancement, even at high bitrates
128 * the output may sound different from the input.
129 * OPUS_APPLICATION_AUDIO - gives best quality at a given bitrate for most
130 * non-voice signals like music. Use this mode for music and mixed
131 * (music/voice) content, broadcast, and applications requiring less than
132 * 15 ms of coding delay.
133 * OPUS_APPLICATION_RESTRICTED_LOWDELAY - configures low-delay mode that
134 * disables the speech-optimized mode in exchange for slightly reduced delay.
135 * This mode can only be set on an newly initialized or freshly reset encoder
136 * because it changes the codec delay.
137 */
138#define CONV_OPUS_APP_TYPE OPUS_APPLICATION_VOIP
139
140/**
141 * Specification for recording. May change in the future to spec negotiation.
142 */
143static pa_sample_spec sample_spec = {
144 .format = PA_SAMPLE_FLOAT32LE,
145 .rate = SAMPLING_RATE,
146 .channels = CHANNELS
147};
148
149GNUNET_NETWORK_STRUCT_BEGIN
150
151/* OggOpus spec says the numbers must be in little-endian order */
152struct OpusHeadPacket
153{
154 uint8_t magic[8];
155 uint8_t version;
156 uint8_t channels;
157 uint16_t preskip GNUNET_PACKED;
158 uint32_t sampling_rate GNUNET_PACKED;
159 uint16_t gain GNUNET_PACKED;
160 uint8_t channel_mapping;
161};
162
163struct OpusCommentsPacket
164{
165 uint8_t magic[8];
166 uint32_t vendor_length;
167 /* followed by:
168 char vendor[vendor_length];
169 uint32_t string_count;
170 followed by @a string_count pairs of:
171 uint32_t string_length;
172 char string[string_length];
173 */
174};
175
176GNUNET_NETWORK_STRUCT_END
177
178/**
179 * Pulseaudio mainloop api
180 */
181static pa_mainloop_api *mainloop_api;
182
183/**
184 * Pulseaudio mainloop
185 */
186static pa_mainloop *m;
187
188/**
189 * Pulseaudio context
190 */
191static pa_context *context;
192
193/**
194 * Pulseaudio recording stream
195 */
196static pa_stream *stream_in;
197
198/**
199 * Pulseaudio io events
200 */
201static pa_io_event *stdio_event;
202
203/**
204 * OPUS encoder
205 */
206static OpusEncoder *enc;
207
208/**
209 * Buffer for encoded data
210 */
211static unsigned char *opus_data;
212
213/**
214 * PCM data buffer for one OPUS frame
215 */
216static float *pcm_buffer;
217
218/**
219 * Length of the pcm data needed for one OPUS frame
220 */
221static int pcm_length;
222
223/**
224 * Audio buffer
225 */
226static char *transmit_buffer;
227
228/**
229 * Length of audio buffer
230 */
231static size_t transmit_buffer_length;
232
233/**
234 * Read index for transmit buffer
235 */
236static size_t transmit_buffer_index;
237
238/**
239 * Audio message skeleton
240 */
241static struct AudioMessage *audio_message;
242
243/**
244 * Ogg muxer state
245 */
246static ogg_stream_state os;
247
248/**
249 * Ogg packet id
250 */
251static int32_t packet_id;
252
253/**
254 * Ogg granule for current packet
255 */
256static int64_t enc_granulepos;
257
258#ifdef DEBUG_RECORD_PURE_OGG
259/**
260 * 1 to not to write GNUnet message headers,
261 * producing pure playable ogg output
262 */
263static int dump_pure_ogg;
264#endif
265
266/**
267 * Pulseaudio shutdown task
268 */
269static void
270quit (int ret)
271{
272 mainloop_api->quit (mainloop_api,
273 ret);
274 exit (ret);
275}
276
277
278static void
279write_data (const char *ptr,
280 size_t msg_size)
281{
282 ssize_t ret;
283 size_t off;
284
285 off = 0;
286 while (off < msg_size)
287 {
288 ret = write (STDOUT_FILENO,
289 &ptr[off],
290 msg_size - off);
291 if (0 >= ret)
292 {
293 if (-1 == ret)
294 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
295 "write");
296 quit (2);
297 }
298 off += ret;
299 }
300}
301
302
303static void
304write_page (ogg_page *og)
305{
306 static unsigned long long toff;
307 size_t msg_size;
308
309 msg_size = sizeof(struct AudioMessage) + og->header_len + og->body_len;
310 audio_message->header.size = htons ((uint16_t) msg_size);
311 GNUNET_memcpy (&audio_message[1], og->header, og->header_len);
312 GNUNET_memcpy (((char *) &audio_message[1]) + og->header_len, og->body,
313 og->body_len);
314
315 toff += msg_size;
316 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
317 "Sending %u bytes of audio data (total: %llu)\n",
318 (unsigned int) msg_size,
319 toff);
320#ifdef DEBUG_RECORD_PURE_OGG
321 if (dump_pure_ogg)
322 write_data ((const char *) &audio_message[1],
323 og->header_len + og->body_len);
324 else
325#endif
326 write_data ((const char *) audio_message,
327 msg_size);
328}
329
330
331/**
332 * Creates OPUS packets from PCM data
333 */
334static void
335packetizer ()
336{
337 char *nbuf;
338 size_t new_size;
339 int32_t len;
340 ogg_packet op;
341 ogg_page og;
342
343 while (transmit_buffer_length >= transmit_buffer_index + pcm_length)
344 {
345 GNUNET_memcpy (pcm_buffer,
346 &transmit_buffer[transmit_buffer_index],
347 pcm_length);
348 transmit_buffer_index += pcm_length;
349 len =
350 opus_encode_float (enc, pcm_buffer, FRAME_SIZE, opus_data,
351 MAX_PAYLOAD_BYTES);
352
353 if (len < 0)
354 {
355 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
356 _ ("opus_encode_float() failed: %s. Aborting\n"),
357 opus_strerror (len));
358 quit (5);
359 }
360 if (((uint32_t) len) > UINT16_MAX - sizeof(struct AudioMessage))
361 {
362 GNUNET_break (0);
363 continue;
364 }
365
366 /* As per OggOpus spec, granule is calculated as if the audio
367 had 48kHz sampling rate. */
368 enc_granulepos += FRAME_SIZE * 48000 / SAMPLING_RATE;
369
370 op.packet = (unsigned char *) opus_data;
371 op.bytes = len;
372 op.b_o_s = 0;
373 op.e_o_s = 0;
374 op.granulepos = enc_granulepos;
375 op.packetno = packet_id++;
376 ogg_stream_packetin (&os, &op);
377
378 while (ogg_stream_flush_fill (&os, &og, PAGE_WATERLINE))
379 {
380 if (((unsigned long long) og.header_len)
381 + ((unsigned long long) og.body_len) >
382 UINT16_MAX - sizeof(struct AudioMessage))
383 {
384 GNUNET_assert (0);
385 continue;
386 }
387 write_page (&og);
388 }
389 }
390
391 new_size = transmit_buffer_length - transmit_buffer_index;
392 if (0 != new_size)
393 {
394 nbuf = pa_xmalloc (new_size);
395 memmove (nbuf,
396 &transmit_buffer[transmit_buffer_index],
397 new_size);
398 pa_xfree (transmit_buffer);
399 transmit_buffer = nbuf;
400 }
401 else
402 {
403 pa_xfree (transmit_buffer);
404 transmit_buffer = NULL;
405 }
406 transmit_buffer_index = 0;
407 transmit_buffer_length = new_size;
408}
409
410
411/**
412 * Pulseaudio callback when new data is available.
413 */
414static void
415stream_read_callback (pa_stream *s,
416 size_t length,
417 void *userdata)
418{
419 const void *data;
420
421 (void) userdata;
422 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
423 "Got %u/%d bytes of PCM data\n",
424 (unsigned int) length,
425 pcm_length);
426
427 GNUNET_assert (NULL != s);
428 GNUNET_assert (length > 0);
429 if (stdio_event)
430 mainloop_api->io_enable (stdio_event, PA_IO_EVENT_OUTPUT);
431
432 if (pa_stream_peek (s, (const void **) &data, &length) < 0)
433 {
434 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
435 _ ("pa_stream_peek() failed: %s\n"),
436 pa_strerror (pa_context_errno (context)));
437 quit (1);
438 return;
439 }
440 GNUNET_assert (NULL != data);
441 GNUNET_assert (length > 0);
442 if (NULL != transmit_buffer)
443 {
444 transmit_buffer = pa_xrealloc (transmit_buffer,
445 transmit_buffer_length + length);
446 GNUNET_memcpy (&transmit_buffer[transmit_buffer_length],
447 data,
448 length);
449 transmit_buffer_length += length;
450 }
451 else
452 {
453 transmit_buffer = pa_xmalloc (length);
454 GNUNET_memcpy (transmit_buffer, data, length);
455 transmit_buffer_length = length;
456 transmit_buffer_index = 0;
457 }
458 pa_stream_drop (s);
459 packetizer ();
460}
461
462
463/**
464 * Exit callback for SIGTERM and SIGINT
465 */
466static void
467exit_signal_callback (pa_mainloop_api *m,
468 pa_signal_event *e,
469 int sig,
470 void *userdata)
471{
472 (void) m;
473 (void) e;
474 (void) sig;
475 (void) userdata;
476 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
477 _ ("Got signal, exiting.\n"));
478 quit (1);
479}
480
481
482/**
483 * Pulseaudio stream state callback
484 */
485static void
486stream_state_callback (pa_stream *s,
487 void *userdata)
488{
489 (void) userdata;
490 GNUNET_assert (NULL != s);
491 switch (pa_stream_get_state (s))
492 {
493 case PA_STREAM_CREATING:
494 case PA_STREAM_TERMINATED:
495 break;
496
497 case PA_STREAM_READY:
498 {
499 const pa_buffer_attr *a;
500 char cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
501 char sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
502
503 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
504 _ ("Stream successfully created.\n"));
505
506 if (! (a = pa_stream_get_buffer_attr (s)))
507 {
508 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
509 _ ("pa_stream_get_buffer_attr() failed: %s\n"),
510 pa_strerror (pa_context_errno
511 (pa_stream_get_context (s))));
512 }
513 else
514 {
515 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
516 _ ("Buffer metrics: maxlength=%u, fragsize=%u\n"),
517 a->maxlength, a->fragsize);
518 }
519 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
520 _ ("Using sample spec '%s', channel map '%s'.\n"),
521 pa_sample_spec_snprint (sst, sizeof(sst),
522 pa_stream_get_sample_spec (s)),
523 pa_channel_map_snprint (cmt, sizeof(cmt),
524 pa_stream_get_channel_map (s)));
525
526 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
527 _ ("Connected to device %s (%u, %ssuspended).\n"),
528 pa_stream_get_device_name (s),
529 pa_stream_get_device_index (s),
530 pa_stream_is_suspended (s) ? "" : "not ");
531 }
532 break;
533
534 case PA_STREAM_FAILED:
535 default:
536 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
537 _ ("Stream error: %s\n"),
538 pa_strerror (pa_context_errno (pa_stream_get_context (s))));
539 quit (1);
540 }
541}
542
543
544/**
545 * Pulseaudio context state callback
546 */
547static void
548context_state_callback (pa_context *c,
549 void *userdata)
550{
551 (void) userdata;
552 GNUNET_assert (c);
553
554 switch (pa_context_get_state (c))
555 {
556 case PA_CONTEXT_CONNECTING:
557 case PA_CONTEXT_AUTHORIZING:
558 case PA_CONTEXT_SETTING_NAME:
559 break;
560
561 case PA_CONTEXT_READY:
562 {
563 int r;
564 pa_buffer_attr na;
565
566 GNUNET_assert (! stream_in);
567 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
568 _ ("Connection established.\n"));
569 if (! (stream_in =
570 pa_stream_new (c, "GNUNET_VoIP recorder", &sample_spec, NULL)))
571 {
572 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
573 _ ("pa_stream_new() failed: %s\n"),
574 pa_strerror (pa_context_errno (c)));
575 goto fail;
576 }
577 pa_stream_set_state_callback (stream_in, &stream_state_callback, NULL);
578 pa_stream_set_read_callback (stream_in, &stream_read_callback, NULL);
579 memset (&na, 0, sizeof(na));
580 na.maxlength = UINT32_MAX;
581 na.fragsize = pcm_length;
582 if ((r = pa_stream_connect_record (stream_in, NULL, &na,
583 PA_STREAM_ADJUST_LATENCY)) < 0)
584 {
585 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
586 _ ("pa_stream_connect_record() failed: %s\n"),
587 pa_strerror (pa_context_errno (c)));
588 goto fail;
589 }
590
591 break;
592 }
593
594 case PA_CONTEXT_TERMINATED:
595 quit (0);
596 break;
597
598 case PA_CONTEXT_FAILED:
599 default:
600 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
601 _ ("Connection failure: %s\n"),
602 pa_strerror (pa_context_errno (c)));
603 goto fail;
604 }
605 return;
606
607fail:
608 quit (1);
609}
610
611
612/**
613 * Pulsaudio init
614 */
615static void
616pa_init ()
617{
618 int r;
619 int i;
620
621 if (! pa_sample_spec_valid (&sample_spec))
622 {
623 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
624 _ ("Wrong Spec\n"));
625 }
626 /* set up main record loop */
627 if (! (m = pa_mainloop_new ()))
628 {
629 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
630 _ ("pa_mainloop_new() failed.\n"));
631 }
632 mainloop_api = pa_mainloop_get_api (m);
633
634 /* listen to signals */
635 r = pa_signal_init (mainloop_api);
636 GNUNET_assert (r == 0);
637 pa_signal_new (SIGINT, &exit_signal_callback, NULL);
638 pa_signal_new (SIGTERM, &exit_signal_callback, NULL);
639
640 /* connect to the main pulseaudio context */
641
642 if (! (context = pa_context_new (mainloop_api, "GNUNET VoIP")))
643 {
644 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
645 _ ("pa_context_new() failed.\n"));
646 }
647 pa_context_set_state_callback (context, &context_state_callback, NULL);
648 if (pa_context_connect (context, NULL, 0, NULL) < 0)
649 {
650 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
651 _ ("pa_context_connect() failed: %s\n"),
652 pa_strerror (pa_context_errno (context)));
653 }
654 if (pa_mainloop_run (m, &i) < 0)
655 {
656 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
657 _ ("pa_mainloop_run() failed.\n"));
658 }
659}
660
661
662/**
663 * OPUS init
664 */
665static void
666opus_init ()
667{
668 int err;
669
670 pcm_length = FRAME_SIZE * CHANNELS * sizeof(float);
671 pcm_buffer = pa_xmalloc (pcm_length);
672 opus_data = GNUNET_malloc (MAX_PAYLOAD_BYTES);
673 enc = opus_encoder_create (SAMPLING_RATE,
674 CHANNELS,
675 CONV_OPUS_APP_TYPE,
676 &err);
677 opus_encoder_ctl (enc,
678 OPUS_SET_PACKET_LOSS_PERC (
679 CONV_OPUS_PACKET_LOSS_PERCENTAGE));
680 opus_encoder_ctl (enc,
681 OPUS_SET_COMPLEXITY (CONV_OPUS_ENCODING_COMPLEXITY));
682 opus_encoder_ctl (enc,
683 OPUS_SET_INBAND_FEC (CONV_OPUS_INBAND_FEC));
684 opus_encoder_ctl (enc,
685 OPUS_SET_SIGNAL (CONV_OPUS_SIGNAL));
686}
687
688
689static void
690ogg_init ()
691{
692 int serialno;
693 struct OpusHeadPacket headpacket;
694 struct OpusCommentsPacket *commentspacket;
695 size_t commentspacket_len;
696
697 serialno = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
698 0x7FFFFFFF);
699 /*Initialize Ogg stream struct*/
700 if (-1 == ogg_stream_init (&os, serialno))
701 {
702 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
703 _ ("ogg_stream_init() failed.\n"));
704 exit (3);
705 }
706
707 packet_id = 0;
708
709 /*Write header*/
710 {
711 ogg_packet op;
712 ogg_page og;
713 const char *opusver;
714 int vendor_length;
715
716 GNUNET_memcpy (headpacket.magic, "OpusHead", 8);
717 headpacket.version = 1;
718 headpacket.channels = CHANNELS;
719 headpacket.preskip = GNUNET_htole16 (0);
720 headpacket.sampling_rate = GNUNET_htole32 (SAMPLING_RATE);
721 headpacket.gain = GNUNET_htole16 (0);
722 headpacket.channel_mapping = 0; /* Mono or stereo */
723
724 op.packet = (unsigned char *) &headpacket;
725 op.bytes = sizeof(headpacket);
726 op.b_o_s = 1;
727 op.e_o_s = 0;
728 op.granulepos = 0;
729 op.packetno = packet_id++;
730 ogg_stream_packetin (&os, &op);
731
732 /* Head packet must be alone on its page */
733 while (ogg_stream_flush (&os, &og))
734 {
735 write_page (&og);
736 }
737
738 commentspacket_len = sizeof(*commentspacket);
739 opusver = opus_get_version_string ();
740 vendor_length = strlen (opusver);
741 commentspacket_len += vendor_length;
742 commentspacket_len += sizeof(uint32_t);
743
744 commentspacket = (struct OpusCommentsPacket *) malloc (commentspacket_len);
745 if (NULL == commentspacket)
746 {
747 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
748 _ ("Failed to allocate %u bytes for second packet\n"),
749 (unsigned int) commentspacket_len);
750 exit (5);
751 }
752
753 GNUNET_memcpy (commentspacket->magic, "OpusTags", 8);
754 commentspacket->vendor_length = GNUNET_htole32 (vendor_length);
755 GNUNET_memcpy (&commentspacket[1], opusver, vendor_length);
756 *(uint32_t *) &((char *) &commentspacket[1])[vendor_length] = \
757 GNUNET_htole32 (0); /* no tags */
758
759 op.packet = (unsigned char *) commentspacket;
760 op.bytes = commentspacket_len;
761 op.b_o_s = 0;
762 op.e_o_s = 0;
763 op.granulepos = 0;
764 op.packetno = packet_id++;
765 ogg_stream_packetin (&os, &op);
766
767 /* Comment packets must not be mixed with audio packets on their pages */
768 while (ogg_stream_flush (&os, &og))
769 {
770 write_page (&og);
771 }
772
773 free (commentspacket);
774 }
775}
776
777
778/**
779 * The main function for the record helper.
780 *
781 * @param argc number of arguments from the command line
782 * @param argv command line arguments
783 * @return 0 ok, 1 on error
784 */
785int
786main (int argc,
787 char *argv[])
788{
789 (void) argc;
790 (void) argv;
791 GNUNET_assert (GNUNET_OK ==
792 GNUNET_log_setup ("gnunet-helper-audio-record",
793 "WARNING",
794 NULL));
795 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
796 "Audio source starts\n");
797 audio_message = GNUNET_malloc (UINT16_MAX);
798 audio_message->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
799
800#ifdef DEBUG_RECORD_PURE_OGG
801 dump_pure_ogg = getenv ("GNUNET_RECORD_PURE_OGG") ? 1 : 0;
802#endif
803 ogg_init ();
804 opus_init ();
805 pa_init ();
806 return 0;
807}
diff --git a/src/contrib/service/conversation/gnunet-service-conversation.c b/src/contrib/service/conversation/gnunet-service-conversation.c
new file mode 100644
index 000000000..06721e535
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet-service-conversation.c
@@ -0,0 +1,1461 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013, 2016, 2017 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 * @file conversation/gnunet-service-conversation.c
22 * @brief conversation service implementation
23 * @author Simon Dieterle
24 * @author Andreas Fuchs
25 * @author Christian Grothoff
26 */
27#include "platform.h"
28#include "gnunet_util_lib.h"
29#include "gnunet_protocols.h"
30#include "gnunet_applications.h"
31#include "gnunet_constants.h"
32#include "gnunet_signatures.h"
33#include "gnunet_cadet_service.h"
34#include "gnunet_conversation_service.h"
35#include "conversation.h"
36
37
38/**
39 * How long is our signature on a call valid? Needs to be long enough for time zone
40 * differences and network latency to not matter. No strong need for it to be short,
41 * but we simply like all signatures to eventually expire.
42 */
43#define RING_TIMEOUT GNUNET_TIME_UNIT_DAYS
44
45
46/**
47 * A line connects a local client with a cadet channel (or, if it is an
48 * open line, is waiting for a cadet channel).
49 */
50struct Line;
51
52/**
53 * The possible connection status
54 */
55enum ChannelStatus
56{
57 /**
58 * We just got the connection, but no introduction yet.
59 */
60 CS_CALLEE_INIT,
61
62 /**
63 * Our phone is ringing, waiting for the client to pick up.
64 */
65 CS_CALLEE_RINGING,
66
67 /**
68 * We are talking!
69 */
70 CS_CALLEE_CONNECTED,
71
72 /**
73 * We're in shutdown, sending hangup messages before cleaning up.
74 */
75 CS_CALLEE_SHUTDOWN,
76
77 /**
78 * We are waiting for the phone to be picked up.
79 */
80 CS_CALLER_CALLING,
81
82 /**
83 * We are talking!
84 */
85 CS_CALLER_CONNECTED,
86
87 /**
88 * We're in shutdown, sending hangup messages before cleaning up.
89 */
90 CS_CALLER_SHUTDOWN
91};
92
93
94/**
95 * A `struct Channel` represents a cadet channel, which is a P2P
96 * connection to another conversation service. Multiple channels can
97 * be attached the the same `struct Line`, which represents a local
98 * client. We keep them in a linked list.
99 */
100struct Channel
101{
102 /**
103 * This is a DLL.
104 */
105 struct Channel *next;
106
107 /**
108 * This is a DLL.
109 */
110 struct Channel *prev;
111
112 /**
113 * Line associated with the channel.
114 */
115 struct Line *line;
116
117 /**
118 * Handle for the channel.
119 */
120 struct GNUNET_CADET_Channel *channel;
121
122 /**
123 * Message queue for control messages
124 */
125 struct GNUNET_MQ_Handle *mq;
126
127 /**
128 * Temporary buffer for audio data in the @e mq.
129 */
130 struct GNUNET_MQ_Envelope *env;
131
132 /**
133 * Channel identifier we use for this call with the client.
134 */
135 uint32_t cid;
136
137 /**
138 * Current status of this line.
139 */
140 enum ChannelStatus status;
141
142 /**
143 * #GNUNET_YES if the channel was suspended by the other peer.
144 */
145 int8_t suspended_remote;
146
147 /**
148 * #GNUNET_YES if the channel was suspended by the local client.
149 */
150 int8_t suspended_local;
151};
152
153
154/**
155 * A `struct Line` connects a local client with cadet channels.
156 */
157struct Line
158{
159 /**
160 * This is a DLL.
161 */
162 struct Channel *channel_head;
163
164 /**
165 * This is a DLL.
166 */
167 struct Channel *channel_tail;
168
169 /**
170 * Handle to the line client.
171 */
172 struct GNUNET_SERVICE_Client *client;
173
174 /**
175 * Message queue for @e client.
176 */
177 struct GNUNET_MQ_Handle *mq;
178
179 /**
180 * Our open port.
181 */
182 struct GNUNET_CADET_Port *port;
183
184 /**
185 * Port number we are listening on (to verify signatures).
186 * Only valid if @e port is non-NULL.
187 */
188 struct GNUNET_HashCode line_port;
189
190 /**
191 * Generator for channel IDs.
192 */
193 uint32_t cid_gen;
194};
195
196
197/**
198 * Our configuration.
199 */
200static const struct GNUNET_CONFIGURATION_Handle *cfg;
201
202/**
203 * Handle for cadet
204 */
205static struct GNUNET_CADET_Handle *cadet;
206
207/**
208 * Identity of this peer.
209 */
210static struct GNUNET_PeerIdentity my_identity;
211
212
213/**
214 * Given a @a cid, find the corresponding channel given
215 * a @a line.
216 *
217 * @param line a line to search
218 * @param cid what to search for
219 * @return NULL for not found
220 */
221static struct Channel *
222find_channel_by_line (struct Line *line, uint32_t cid)
223{
224 for (struct Channel *ch = line->channel_head; NULL != ch; ch = ch->next)
225 if (cid == ch->cid)
226 return ch;
227 return NULL;
228}
229
230
231/**
232 * Function to handle a pickup request message from the client
233 *
234 * @param cls the `struct Line` of the client from which the message is
235 * @param msg the message from the client
236 */
237static void
238handle_client_pickup_message (void *cls,
239 const struct ClientPhonePickupMessage *msg)
240{
241 struct Line *line = cls;
242 struct CadetPhonePickupMessage *mppm;
243 struct GNUNET_MQ_Envelope *env;
244 struct Channel *ch;
245
246 if (NULL == line->port)
247 {
248 /* we never opened the port, bad client! */
249 GNUNET_break_op (0);
250 GNUNET_SERVICE_client_drop (line->client);
251 return;
252 }
253 for (ch = line->channel_head; NULL != ch; ch = ch->next)
254 if (msg->cid == ch->cid)
255 break;
256 if (NULL == ch)
257 {
258 /* could have been destroyed asynchronously, ignore message */
259 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid);
260 GNUNET_SERVICE_client_continue (line->client);
261 return;
262 }
263 switch (ch->status)
264 {
265 case CS_CALLEE_INIT:
266 GNUNET_break (0);
267 GNUNET_SERVICE_client_drop (line->client);
268 return;
269
270 case CS_CALLEE_RINGING:
271 ch->status = CS_CALLEE_CONNECTED;
272 break;
273
274 case CS_CALLEE_CONNECTED:
275 GNUNET_break (0);
276 GNUNET_SERVICE_client_drop (line->client);
277 return;
278
279 case CS_CALLEE_SHUTDOWN:
280 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
281 "Ignoring client's PICKUP message, line is in SHUTDOWN\n");
282 break;
283
284 case CS_CALLER_CALLING:
285 case CS_CALLER_CONNECTED:
286 case CS_CALLER_SHUTDOWN:
287 GNUNET_break (0);
288 GNUNET_SERVICE_client_drop (line->client);
289 return;
290 }
291 GNUNET_break (CS_CALLEE_CONNECTED == ch->status);
292 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending PICK_UP message to cadet\n");
293 env =
294 GNUNET_MQ_msg (mppm, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_PICK_UP);
295 GNUNET_MQ_send (ch->mq, env);
296 GNUNET_SERVICE_client_continue (line->client);
297}
298
299
300/**
301 * Channel went down, notify client and free data
302 * structure.
303 *
304 * @param ch channel that went down
305 */
306static void
307clean_up_channel (struct Channel *ch)
308{
309 struct Line *line = ch->line;
310 struct GNUNET_MQ_Envelope *env;
311 struct ClientPhoneHangupMessage *hup;
312
313 switch (ch->status)
314 {
315 case CS_CALLEE_INIT:
316 case CS_CALLEE_SHUTDOWN:
317 case CS_CALLER_SHUTDOWN:
318 break;
319
320 case CS_CALLEE_RINGING:
321 case CS_CALLEE_CONNECTED:
322 case CS_CALLER_CALLING:
323 case CS_CALLER_CONNECTED:
324 if (NULL != line)
325 {
326 env =
327 GNUNET_MQ_msg (hup, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP);
328 hup->cid = ch->cid;
329 GNUNET_MQ_send (line->mq, env);
330 }
331 break;
332 }
333 if (NULL != line)
334 GNUNET_CONTAINER_DLL_remove (line->channel_head, line->channel_tail, ch);
335 GNUNET_free (ch);
336}
337
338
339/**
340 * Destroy a channel.
341 *
342 * @param ch channel to destroy.
343 */
344static void
345destroy_line_cadet_channels (struct Channel *ch)
346{
347 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Destroying cadet channels\n");
348 if (NULL != ch->channel)
349 {
350 GNUNET_CADET_channel_destroy (ch->channel);
351 ch->channel = NULL;
352 }
353 clean_up_channel (ch);
354}
355
356
357/**
358 * We are done signalling shutdown to the other peer. Close down
359 * the channel.
360 *
361 * @param cls the `struct Channel` to reset/terminate
362 */
363static void
364mq_done_finish_caller_shutdown (void *cls)
365{
366 struct Channel *ch = cls;
367
368 switch (ch->status)
369 {
370 case CS_CALLEE_INIT:
371 GNUNET_break (0);
372 break;
373
374 case CS_CALLEE_RINGING:
375 GNUNET_break (0);
376 break;
377
378 case CS_CALLEE_CONNECTED:
379 GNUNET_break (0);
380 break;
381
382 case CS_CALLEE_SHUTDOWN:
383 destroy_line_cadet_channels (ch);
384 break;
385
386 case CS_CALLER_CALLING:
387 GNUNET_break (0);
388 break;
389
390 case CS_CALLER_CONNECTED:
391 GNUNET_break (0);
392 break;
393
394 case CS_CALLER_SHUTDOWN:
395 destroy_line_cadet_channels (ch);
396 break;
397 }
398}
399
400
401/**
402 * Function to handle a hangup request message from the client
403 *
404 * @param cls the `struct Line` the hangup is for
405 * @param msg the message from the client
406 */
407static void
408handle_client_hangup_message (void *cls,
409 const struct ClientPhoneHangupMessage *msg)
410{
411 struct Line *line = cls;
412 struct GNUNET_MQ_Envelope *e;
413 struct CadetPhoneHangupMessage *mhum;
414 struct Channel *ch;
415
416 for (ch = line->channel_head; NULL != ch; ch = ch->next)
417 if (msg->cid == ch->cid)
418 break;
419 if (NULL == ch)
420 {
421 /* could have been destroyed asynchronously, ignore message */
422 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid);
423 GNUNET_SERVICE_client_continue (line->client);
424 return;
425 }
426 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
427 "Received HANGUP for channel %u which is in state %d\n",
428 msg->cid,
429 ch->status);
430 switch (ch->status)
431 {
432 case CS_CALLEE_INIT:
433 GNUNET_break (0);
434 GNUNET_SERVICE_client_drop (line->client);
435 return;
436
437 case CS_CALLEE_RINGING:
438 ch->status = CS_CALLEE_SHUTDOWN;
439 break;
440
441 case CS_CALLEE_CONNECTED:
442 ch->status = CS_CALLEE_SHUTDOWN;
443 break;
444
445 case CS_CALLEE_SHUTDOWN:
446 /* maybe the other peer closed asynchronously... */
447 GNUNET_SERVICE_client_continue (line->client);
448 return;
449
450 case CS_CALLER_CALLING:
451 ch->status = CS_CALLER_SHUTDOWN;
452 break;
453
454 case CS_CALLER_CONNECTED:
455 ch->status = CS_CALLER_SHUTDOWN;
456 break;
457
458 case CS_CALLER_SHUTDOWN:
459 /* maybe the other peer closed asynchronously... */
460 GNUNET_SERVICE_client_continue (line->client);
461 return;
462 }
463 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending HANG_UP message via cadet\n");
464 e =
465 GNUNET_MQ_msg (mhum, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_HANG_UP);
466 GNUNET_MQ_notify_sent (e, &mq_done_finish_caller_shutdown, ch);
467 GNUNET_MQ_send (ch->mq, e);
468 GNUNET_SERVICE_client_continue (line->client);
469}
470
471
472/**
473 * Function to handle a suspend request message from the client
474 *
475 * @param cls the `struct Line` the message is about
476 * @param msg the message from the client
477 */
478static void
479handle_client_suspend_message (void *cls,
480 const struct ClientPhoneSuspendMessage *msg)
481{
482 struct Line *line = cls;
483 struct GNUNET_MQ_Envelope *e;
484 struct CadetPhoneSuspendMessage *mhum;
485 struct Channel *ch;
486
487 for (ch = line->channel_head; NULL != ch; ch = ch->next)
488 if (msg->cid == ch->cid)
489 break;
490 if (NULL == ch)
491 {
492 /* could have been destroyed asynchronously, ignore message */
493 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid);
494 GNUNET_SERVICE_client_continue (line->client);
495 return;
496 }
497 if (GNUNET_YES == ch->suspended_local)
498 {
499 GNUNET_break (0);
500 GNUNET_SERVICE_client_drop (line->client);
501 return;
502 }
503 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
504 "Received SUSPEND for channel %u which is in state %d\n",
505 msg->cid,
506 ch->status);
507 switch (ch->status)
508 {
509 case CS_CALLEE_INIT:
510 GNUNET_break (0);
511 GNUNET_SERVICE_client_drop (line->client);
512 return;
513
514 case CS_CALLEE_RINGING:
515 GNUNET_break (0);
516 GNUNET_SERVICE_client_drop (line->client);
517 return;
518
519 case CS_CALLEE_CONNECTED:
520 ch->suspended_local = GNUNET_YES;
521 break;
522
523 case CS_CALLEE_SHUTDOWN:
524 /* maybe the other peer closed asynchronously... */
525 GNUNET_SERVICE_client_continue (line->client);
526 return;
527
528 case CS_CALLER_CALLING:
529 GNUNET_break (0);
530 GNUNET_SERVICE_client_drop (line->client);
531 return;
532
533 case CS_CALLER_CONNECTED:
534 ch->suspended_local = GNUNET_YES;
535 break;
536
537 case CS_CALLER_SHUTDOWN:
538 /* maybe the other peer closed asynchronously... */
539 GNUNET_SERVICE_client_continue (line->client);
540 return;
541 }
542 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending SUSPEND message via cadet\n");
543 e =
544 GNUNET_MQ_msg (mhum, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_SUSPEND);
545 GNUNET_MQ_send (ch->mq, e);
546 GNUNET_SERVICE_client_continue (line->client);
547}
548
549
550/**
551 * Function to handle a resume request message from the client
552 *
553 * @param cls the `struct Line` the message is about
554 * @param msg the message from the client
555 */
556static void
557handle_client_resume_message (void *cls,
558 const struct ClientPhoneResumeMessage *msg)
559{
560 struct Line *line = cls;
561 struct GNUNET_MQ_Envelope *e;
562 struct CadetPhoneResumeMessage *mhum;
563 struct Channel *ch;
564
565 for (ch = line->channel_head; NULL != ch; ch = ch->next)
566 if (msg->cid == ch->cid)
567 break;
568 if (NULL == ch)
569 {
570 /* could have been destroyed asynchronously, ignore message */
571 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid);
572 GNUNET_SERVICE_client_continue (line->client);
573 return;
574 }
575 if (GNUNET_YES != ch->suspended_local)
576 {
577 GNUNET_break (0);
578 GNUNET_SERVICE_client_drop (line->client);
579 return;
580 }
581 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
582 "Received RESUME for channel %u which is in state %d\n",
583 msg->cid,
584 ch->status);
585 switch (ch->status)
586 {
587 case CS_CALLEE_INIT:
588 GNUNET_break (0);
589 GNUNET_SERVICE_client_drop (line->client);
590 return;
591
592 case CS_CALLEE_RINGING:
593 GNUNET_break (0);
594 GNUNET_SERVICE_client_drop (line->client);
595 return;
596
597 case CS_CALLEE_CONNECTED:
598 ch->suspended_local = GNUNET_NO;
599 break;
600
601 case CS_CALLEE_SHUTDOWN:
602 /* maybe the other peer closed asynchronously... */
603 GNUNET_SERVICE_client_continue (line->client);
604 return;
605
606 case CS_CALLER_CALLING:
607 GNUNET_break (0);
608 GNUNET_SERVICE_client_drop (line->client);
609 return;
610
611 case CS_CALLER_CONNECTED:
612 ch->suspended_local = GNUNET_NO;
613 break;
614
615 case CS_CALLER_SHUTDOWN:
616 /* maybe the other peer closed asynchronously... */
617 GNUNET_SERVICE_client_drop (line->client);
618 return;
619 }
620 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending RESUME message via cadet\n");
621 e = GNUNET_MQ_msg (mhum, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RESUME);
622 GNUNET_MQ_send (ch->mq, e);
623 GNUNET_SERVICE_client_continue (line->client);
624}
625
626
627/**
628 * Transmission of audio data via cadet channel finished.
629 *
630 * @param cls the `struct Channel` we are transmitting for
631 */
632static void
633channel_audio_sent_notify (void *cls)
634{
635 struct Channel *ch = cls;
636
637 ch->env = NULL;
638}
639
640
641/**
642 * Function to check audio data from the client
643 *
644 * @param cls the `struct Line` the message is about
645 * @param msg the message from the client
646 * @return #GNUNET_OK (any data is ok)
647 */
648static int
649check_client_audio_message (void *cls, const struct ClientAudioMessage *msg)
650{
651 (void) cls;
652 (void) msg;
653 return GNUNET_OK;
654}
655
656
657/**
658 * Function to handle audio data from the client
659 *
660 * @param cls the `struct Line` the message is about
661 * @param msg the message from the client
662 */
663static void
664handle_client_audio_message (void *cls, const struct ClientAudioMessage *msg)
665{
666 struct Line *line = cls;
667 struct CadetAudioMessage *mam;
668 struct Channel *ch;
669 size_t size;
670
671 size = ntohs (msg->header.size) - sizeof(struct ClientAudioMessage);
672 ch = find_channel_by_line (line, msg->cid);
673 if (NULL == ch)
674 {
675 /* could have been destroyed asynchronously, ignore message */
676 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid);
677 GNUNET_SERVICE_client_continue (line->client);
678 return;
679 }
680
681 switch (ch->status)
682 {
683 case CS_CALLEE_INIT:
684 case CS_CALLEE_RINGING:
685 case CS_CALLER_CALLING:
686 GNUNET_break (0);
687 GNUNET_SERVICE_client_drop (line->client);
688 return;
689
690 case CS_CALLEE_CONNECTED:
691 case CS_CALLER_CONNECTED:
692 /* common case, handled below */
693 break;
694
695 case CS_CALLEE_SHUTDOWN:
696 case CS_CALLER_SHUTDOWN:
697 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK,
698 "Cadet audio channel in shutdown; audio data dropped\n");
699 GNUNET_SERVICE_client_continue (line->client);
700 return;
701 }
702 if (GNUNET_YES == ch->suspended_local)
703 {
704 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
705 "This channel is suspended locally\n");
706 GNUNET_SERVICE_client_drop (line->client);
707 return;
708 }
709 if (NULL != ch->env)
710 {
711 /* NOTE: we may want to not do this and instead combine the data */
712 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
713 "Bandwidth insufficient; dropping previous audio data segment\n");
714 GNUNET_MQ_send_cancel (ch->env);
715 ch->env = NULL;
716 }
717
718 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
719 "Received %u bytes of AUDIO data from client CID %u\n",
720 (unsigned int) size,
721 msg->cid);
722 ch->env = GNUNET_MQ_msg_extra (mam,
723 size,
724 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_AUDIO);
725 GNUNET_memcpy (&mam[1], &msg[1], size);
726 /* FIXME: set options for unreliable transmission */
727 GNUNET_MQ_notify_sent (ch->env, &channel_audio_sent_notify, ch);
728 GNUNET_MQ_send (ch->mq, ch->env);
729 GNUNET_SERVICE_client_continue (line->client);
730}
731
732/**
733 * Function to handle a ring message incoming over cadet
734 *
735 * @param cls closure, NULL
736 * @param msg the incoming message
737 */
738static enum GNUNET_GenericReturnValue
739check_cadet_ring_message (void *cls, const struct CadetPhoneRingMessage *msg)
740{
741 // FIXME
742 return GNUNET_OK;
743}
744
745/**
746 * Function to handle a ring message incoming over cadet
747 *
748 * @param cls closure, NULL
749 * @param msg the incoming message
750 */
751static void
752handle_cadet_ring_message (void *cls, const struct CadetPhoneRingMessage *msg)
753{
754 struct Channel *ch = cls;
755 struct Line *line = ch->line;
756 struct GNUNET_MQ_Envelope *env;
757 struct ClientPhoneRingMessage *cring;
758 struct CadetPhoneRingInfoPS rs;
759 struct GNUNET_CRYPTO_PublicKey identity;
760 struct GNUNET_CRYPTO_Signature sig;
761 size_t key_len;
762 size_t sig_len;
763 size_t read;
764
765 rs.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_CONVERSATION_RING);
766 rs.purpose.size = htonl (sizeof(struct CadetPhoneRingInfoPS));
767 rs.line_port = line->line_port;
768 rs.target_peer = my_identity;
769 rs.expiration_time = msg->expiration_time;
770 key_len = ntohl (msg->key_len);
771 sig_len = ntohl (msg->sig_len);
772
773 if ((GNUNET_SYSERR ==
774 GNUNET_CRYPTO_read_public_key_from_buffer (&msg[1],
775 key_len,
776 &identity,
777 &read)) ||
778 (read != key_len))
779 {
780 GNUNET_break_op (0);
781 destroy_line_cadet_channels (ch);
782 return;
783 }
784 GNUNET_CRYPTO_read_signature_from_buffer (&sig,
785 (char*) &msg[1] + read,
786 sig_len);
787 if (GNUNET_OK !=
788 GNUNET_CRYPTO_signature_verify (
789 GNUNET_SIGNATURE_PURPOSE_CONVERSATION_RING,
790 &rs,
791 &sig,
792 &identity))
793 {
794 GNUNET_break_op (0);
795 destroy_line_cadet_channels (ch);
796 return;
797 }
798 if (0 == GNUNET_TIME_absolute_get_remaining (
799 GNUNET_TIME_absolute_ntoh (msg->expiration_time))
800 .rel_value_us)
801 {
802 /* ancient call, replay? */
803 GNUNET_break_op (0);
804 /* Note that our reliance on time here is awkward; better would be
805 to use a more complex challenge-response protocol against
806 replay attacks. Left for future work ;-). */
807 destroy_line_cadet_channels (ch);
808 return;
809 }
810 if (CS_CALLEE_INIT != ch->status)
811 {
812 GNUNET_break_op (0);
813 destroy_line_cadet_channels (ch);
814 return;
815 }
816 GNUNET_CADET_receive_done (ch->channel);
817 ch->status = CS_CALLEE_RINGING;
818 env = GNUNET_MQ_msg_extra (cring,
819 key_len,
820 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RING);
821 cring->cid = ch->cid;
822 memcpy (&cring[1], &msg[1], key_len);
823 cring->key_len = msg->key_len;
824 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
825 "Sending RING message to client. CID is %u\n",
826 (unsigned int) ch->cid);
827 GNUNET_MQ_send (line->mq, env);
828}
829
830
831/**
832 * Function to handle a hangup message incoming over cadet
833 *
834 * @param cls closure, our `struct Channel *`
835 * @param message the incoming message
836 */
837static void
838handle_cadet_hangup_message (void *cls,
839 const struct CadetPhoneHangupMessage *message)
840{
841 struct Channel *ch = cls;
842 struct Line *line = ch->line;
843 struct GNUNET_MQ_Envelope *env;
844 struct ClientPhoneHangupMessage *hup;
845 enum ChannelStatus status;
846 uint32_t cid;
847
848 (void) message;
849 GNUNET_CADET_receive_done (ch->channel);
850 cid = ch->cid;
851 status = ch->status;
852 destroy_line_cadet_channels (ch);
853 switch (status)
854 {
855 case CS_CALLEE_INIT:
856 GNUNET_break_op (0);
857 return;
858
859 case CS_CALLEE_RINGING:
860 case CS_CALLEE_CONNECTED:
861 break;
862
863 case CS_CALLEE_SHUTDOWN:
864 return;
865
866 case CS_CALLER_CALLING:
867 case CS_CALLER_CONNECTED:
868 break;
869
870 case CS_CALLER_SHUTDOWN:
871 return;
872 }
873 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending HANG UP message to client\n");
874 env = GNUNET_MQ_msg (hup, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP);
875 hup->cid = cid;
876 GNUNET_MQ_send (line->mq, env);
877}
878
879
880/**
881 * Function to handle a pickup message incoming over cadet
882 *
883 * @param cls closure, our `struct Channel *`
884 * @param message the incoming message
885 */
886static void
887handle_cadet_pickup_message (void *cls,
888 const struct CadetPhonePickupMessage *message)
889{
890 struct Channel *ch = cls;
891 struct Line *line = ch->line;
892 struct GNUNET_MQ_Envelope *env;
893 struct ClientPhonePickedupMessage *pick;
894
895 (void) message;
896 GNUNET_CADET_receive_done (ch->channel);
897 switch (ch->status)
898 {
899 case CS_CALLEE_INIT:
900 case CS_CALLEE_RINGING:
901 case CS_CALLEE_CONNECTED:
902 GNUNET_break_op (0);
903 destroy_line_cadet_channels (ch);
904 return;
905
906 case CS_CALLEE_SHUTDOWN:
907 GNUNET_break_op (0);
908 destroy_line_cadet_channels (ch);
909 return;
910
911 case CS_CALLER_CALLING:
912 ch->status = CS_CALLER_CONNECTED;
913 break;
914
915 case CS_CALLER_CONNECTED:
916 GNUNET_break_op (0);
917 return;
918
919 case CS_CALLER_SHUTDOWN:
920 GNUNET_break_op (0);
921 mq_done_finish_caller_shutdown (ch);
922 return;
923 }
924 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending PICKED UP message to client\n");
925 env =
926 GNUNET_MQ_msg (pick, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP);
927 pick->cid = ch->cid;
928 GNUNET_MQ_send (line->mq, env);
929}
930
931
932/**
933 * Function to handle a suspend message incoming over cadet
934 *
935 * @param cls closure, our `struct Channel *`
936 * @param message the incoming message
937 */
938static void
939handle_cadet_suspend_message (void *cls,
940 const struct CadetPhoneSuspendMessage *message)
941{
942 struct Channel *ch = cls;
943 struct Line *line = ch->line;
944 struct GNUNET_MQ_Envelope *env;
945 struct ClientPhoneSuspendMessage *suspend;
946
947 (void) message;
948 GNUNET_CADET_receive_done (ch->channel);
949 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending channel CID: %u\n", ch->cid);
950 switch (ch->status)
951 {
952 case CS_CALLEE_INIT:
953 GNUNET_break_op (0);
954 break;
955
956 case CS_CALLEE_RINGING:
957 GNUNET_break_op (0);
958 break;
959
960 case CS_CALLEE_CONNECTED:
961 ch->suspended_remote = GNUNET_YES;
962 break;
963
964 case CS_CALLEE_SHUTDOWN:
965 return;
966
967 case CS_CALLER_CALLING:
968 GNUNET_break_op (0);
969 break;
970
971 case CS_CALLER_CONNECTED:
972 ch->suspended_remote = GNUNET_YES;
973 break;
974
975 case CS_CALLER_SHUTDOWN:
976 return;
977 }
978 env =
979 GNUNET_MQ_msg (suspend, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND);
980 suspend->cid = ch->cid;
981 GNUNET_MQ_send (line->mq, env);
982}
983
984
985/**
986 * Function to handle a resume message incoming over cadet
987 *
988 * @param cls closure, our `struct Channel *`
989 * @param msg the incoming message
990 */
991static void
992handle_cadet_resume_message (void *cls,
993 const struct CadetPhoneResumeMessage *msg)
994{
995 struct Channel *ch = cls;
996 struct Line *line;
997 struct GNUNET_MQ_Envelope *env;
998 struct ClientPhoneResumeMessage *resume;
999
1000 (void) msg;
1001 line = ch->line;
1002 GNUNET_CADET_receive_done (ch->channel);
1003 if (GNUNET_YES != ch->suspended_remote)
1004 {
1005 GNUNET_log (
1006 GNUNET_ERROR_TYPE_DEBUG,
1007 "RESUME message received for non-suspended channel, dropping channel.\n");
1008 destroy_line_cadet_channels (ch);
1009 return;
1010 }
1011 switch (ch->status)
1012 {
1013 case CS_CALLEE_INIT:
1014 GNUNET_break (0);
1015 break;
1016
1017 case CS_CALLEE_RINGING:
1018 GNUNET_break (0);
1019 break;
1020
1021 case CS_CALLEE_CONNECTED:
1022 ch->suspended_remote = GNUNET_NO;
1023 break;
1024
1025 case CS_CALLEE_SHUTDOWN:
1026 return;
1027
1028 case CS_CALLER_CALLING:
1029 GNUNET_break (0);
1030 break;
1031
1032 case CS_CALLER_CONNECTED:
1033 ch->suspended_remote = GNUNET_NO;
1034 break;
1035
1036 case CS_CALLER_SHUTDOWN:
1037 return;
1038 }
1039 env =
1040 GNUNET_MQ_msg (resume, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME);
1041 resume->cid = ch->cid;
1042 GNUNET_MQ_send (line->mq, env);
1043}
1044
1045
1046/**
1047 * Function to check an audio message incoming over cadet
1048 *
1049 * @param cls closure, our `struct Channel *`
1050 * @param msg the incoming message
1051 * @return #GNUNET_OK (always)
1052 */
1053static int
1054check_cadet_audio_message (void *cls, const struct CadetAudioMessage *msg)
1055{
1056 (void) cls;
1057 (void) msg;
1058 return GNUNET_OK; /* any payload is fine */
1059}
1060
1061
1062/**
1063 * Function to handle an audio message incoming over cadet
1064 *
1065 * @param cls closure, our `struct Channel *`
1066 * @param msg the incoming message
1067 */
1068static void
1069handle_cadet_audio_message (void *cls, const struct CadetAudioMessage *msg)
1070{
1071 struct Channel *ch = cls;
1072 size_t msize = ntohs (msg->header.size) - sizeof(struct CadetAudioMessage);
1073 struct GNUNET_MQ_Envelope *env;
1074 struct ClientAudioMessage *cam;
1075
1076 GNUNET_CADET_receive_done (ch->channel);
1077 if ((GNUNET_YES == ch->suspended_local) ||
1078 (GNUNET_YES == ch->suspended_remote))
1079 {
1080 GNUNET_log (
1081 GNUNET_ERROR_TYPE_DEBUG,
1082 "Received %u bytes of AUDIO data on suspended channel CID %u; dropping\n",
1083 (unsigned int) msize,
1084 ch->cid);
1085 return;
1086 }
1087 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1088 "Forwarding %u bytes of AUDIO data to client CID %u\n",
1089 (unsigned int) msize,
1090 ch->cid);
1091 env =
1092 GNUNET_MQ_msg_extra (cam, msize, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO);
1093 cam->cid = ch->cid;
1094 GNUNET_memcpy (&cam[1], &msg[1], msize);
1095 GNUNET_MQ_send (ch->line->mq, env);
1096}
1097
1098
1099/**
1100 * Function called whenever an inbound channel is destroyed. Should clean up
1101 * any associated state.
1102 *
1103 * @param cls closure (set from #GNUNET_CADET_connect)
1104 * @param channel connection to the other end (henceforth invalid)
1105 */
1106static void
1107inbound_end (void *cls, const struct GNUNET_CADET_Channel *channel)
1108{
1109 struct Channel *ch = cls;
1110
1111 GNUNET_assert (channel == ch->channel);
1112 ch->channel = NULL;
1113 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1114 "Channel destroyed by CADET in state %d\n",
1115 ch->status);
1116 clean_up_channel (ch);
1117}
1118
1119/**
1120 * Function to handle call request from the client
1121 *
1122 * @param cls the `struct Line` the message is about
1123 * @param msg the message from the client
1124 */
1125static enum GNUNET_GenericReturnValue
1126check_client_call_message (void *cls, const struct ClientCallMessage *msg)
1127{
1128 // FIXME
1129 return GNUNET_OK;
1130}
1131
1132/**
1133 * Function to handle call request from the client
1134 *
1135 * @param cls the `struct Line` the message is about
1136 * @param msg the message from the client
1137 */
1138static void
1139handle_client_call_message (void *cls, const struct ClientCallMessage *msg)
1140{
1141 struct Line *line = cls;
1142 struct Channel *ch = GNUNET_new (struct Channel);
1143 struct GNUNET_MQ_MessageHandler cadet_handlers[] =
1144 { GNUNET_MQ_hd_fixed_size (cadet_hangup_message,
1145 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_HANG_UP,
1146 struct CadetPhoneHangupMessage,
1147 ch),
1148 GNUNET_MQ_hd_fixed_size (cadet_pickup_message,
1149 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_PICK_UP,
1150 struct CadetPhonePickupMessage,
1151 ch),
1152 GNUNET_MQ_hd_fixed_size (cadet_suspend_message,
1153 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_SUSPEND,
1154 struct CadetPhoneSuspendMessage,
1155 ch),
1156 GNUNET_MQ_hd_fixed_size (cadet_resume_message,
1157 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RESUME,
1158 struct CadetPhoneResumeMessage,
1159 ch),
1160 GNUNET_MQ_hd_var_size (cadet_audio_message,
1161 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_AUDIO,
1162 struct CadetAudioMessage,
1163 ch),
1164 GNUNET_MQ_handler_end () };
1165 struct GNUNET_MQ_Envelope *e;
1166 struct CadetPhoneRingMessage *ring;
1167 struct CadetPhoneRingInfoPS rs;
1168 struct GNUNET_CRYPTO_PrivateKey caller_id;
1169 struct GNUNET_CRYPTO_PublicKey caller_id_pub;
1170 struct GNUNET_CRYPTO_Signature sig;
1171 ssize_t written;
1172 size_t key_len;
1173 size_t pkey_len;
1174 size_t sig_len;
1175 size_t read;
1176
1177 line->line_port = msg->line_port;
1178 rs.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_CONVERSATION_RING);
1179 rs.purpose.size = htonl (sizeof(struct CadetPhoneRingInfoPS));
1180 rs.line_port = line->line_port;
1181 rs.target_peer = msg->target;
1182 rs.expiration_time =
1183 GNUNET_TIME_absolute_hton (GNUNET_TIME_relative_to_absolute (RING_TIMEOUT));
1184 key_len = ntohl (msg->key_len);
1185 if (GNUNET_SYSERR ==
1186 GNUNET_CRYPTO_read_private_key_from_buffer (&msg[1],
1187 key_len,
1188 &caller_id,
1189 &read))
1190 {
1191 GNUNET_break_op (0);
1192 GNUNET_free (ch);
1193 GNUNET_SERVICE_client_drop (line->client);
1194 return;
1195 }
1196 ch->line = line;
1197 GNUNET_CONTAINER_DLL_insert (line->channel_head, line->channel_tail, ch);
1198 ch->status = CS_CALLER_CALLING;
1199 ch->channel = GNUNET_CADET_channel_create (cadet,
1200 ch,
1201 &msg->target,
1202 &msg->line_port,
1203 NULL,
1204 &inbound_end,
1205 cadet_handlers);
1206 ch->mq = GNUNET_CADET_get_mq (ch->channel);
1207 GNUNET_assert (read == key_len);
1208 GNUNET_CRYPTO_sign (&caller_id, &rs, &sig);
1209 sig_len = GNUNET_CRYPTO_signature_get_length (&sig);
1210 GNUNET_CRYPTO_key_get_public (&caller_id, &caller_id_pub);
1211 pkey_len = GNUNET_CRYPTO_public_key_get_length (&caller_id_pub);
1212 e = GNUNET_MQ_msg_extra (ring, pkey_len + sig_len,
1213 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RING);
1214 written = GNUNET_CRYPTO_write_public_key_to_buffer (&caller_id_pub,
1215 &ring[1],
1216 pkey_len);
1217 ring->expiration_time = rs.expiration_time;
1218 ring->key_len = htonl (pkey_len);
1219 ring->sig_len = htonl (sig_len);
1220 GNUNET_CRYPTO_write_signature_to_buffer (&sig,
1221 (char *) &ring[1] + written,
1222 sig_len);
1223 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending RING message via CADET\n");
1224 GNUNET_MQ_send (ch->mq, e);
1225 GNUNET_SERVICE_client_continue (line->client);
1226}
1227
1228
1229/**
1230 * Method called whenever another peer has added us to a channel
1231 * the other peer initiated.
1232 *
1233 * @param cls the `struct Line` receiving a connection
1234 * @param channel new handle to the channel
1235 * @param initiator peer that started the channel
1236 * @return initial channel context for the channel
1237 */
1238static void *
1239inbound_channel (void *cls,
1240 struct GNUNET_CADET_Channel *channel,
1241 const struct GNUNET_PeerIdentity *initiator)
1242{
1243 struct Line *line = cls;
1244 struct Channel *ch;
1245
1246 (void) initiator;
1247 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1248 "Received incoming cadet channel on line %p\n",
1249 line);
1250 ch = GNUNET_new (struct Channel);
1251 ch->status = CS_CALLEE_INIT;
1252 ch->line = line;
1253 ch->channel = channel;
1254 ch->mq = GNUNET_CADET_get_mq (ch->channel);
1255 ch->cid = line->cid_gen++;
1256 GNUNET_CONTAINER_DLL_insert (line->channel_head, line->channel_tail, ch);
1257 return ch;
1258}
1259
1260
1261/**
1262 * A client connected. Initialize the `struct Line` data structure.
1263 *
1264 * @param cls closure, NULL
1265 * @param client identification of the client
1266 * @param mq message queue for @a client
1267 * @return the `struct Line` for the client
1268 */
1269static void *
1270client_connect_cb (void *cls,
1271 struct GNUNET_SERVICE_Client *client,
1272 struct GNUNET_MQ_Handle *mq)
1273{
1274 struct Line *line;
1275
1276 (void) cls;
1277 line = GNUNET_new (struct Line);
1278 line->client = client;
1279 line->mq = mq;
1280 return line;
1281}
1282
1283
1284/**
1285 * A client disconnected. Remove all of its data structure entries.
1286 *
1287 * @param cls closure, NULL
1288 * @param client identification of the client
1289 * @param app_ctx our `struct Line *` for @a client
1290 */
1291static void
1292client_disconnect_cb (void *cls,
1293 struct GNUNET_SERVICE_Client *client,
1294 void *app_ctx)
1295{
1296 struct Line *line = app_ctx;
1297 struct Channel *chn;
1298
1299 (void) cls;
1300 (void) client;
1301 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client disconnected, closing line\n");
1302 if (NULL != line->port)
1303 {
1304 GNUNET_CADET_close_port (line->port);
1305 line->port = NULL;
1306 }
1307 for (struct Channel *ch = line->channel_head; NULL != ch; ch = chn)
1308 {
1309 chn = ch->next;
1310 ch->line = NULL;
1311 destroy_line_cadet_channels (ch);
1312 }
1313 GNUNET_free (line);
1314}
1315
1316
1317/**
1318 * Function to register a phone.
1319 *
1320 * @param cls the `struct Line` of the client from which the message is
1321 * @param msg the message from the client
1322 */
1323static void
1324handle_client_register_message (void *cls,
1325 const struct ClientPhoneRegisterMessage *msg)
1326{
1327 struct Line *line = cls;
1328 struct GNUNET_MQ_MessageHandler cadet_handlers[] =
1329 { GNUNET_MQ_hd_var_size (cadet_ring_message,
1330 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RING,
1331 struct CadetPhoneRingMessage,
1332 NULL),
1333 GNUNET_MQ_hd_fixed_size (cadet_hangup_message,
1334 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_HANG_UP,
1335 struct CadetPhoneHangupMessage,
1336 NULL),
1337 GNUNET_MQ_hd_fixed_size (cadet_pickup_message,
1338 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_PICK_UP,
1339 struct CadetPhonePickupMessage,
1340 NULL),
1341 GNUNET_MQ_hd_fixed_size (cadet_suspend_message,
1342 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_SUSPEND,
1343 struct CadetPhoneSuspendMessage,
1344 NULL),
1345 GNUNET_MQ_hd_fixed_size (cadet_resume_message,
1346 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RESUME,
1347 struct CadetPhoneResumeMessage,
1348 NULL),
1349 GNUNET_MQ_hd_var_size (cadet_audio_message,
1350 GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_AUDIO,
1351 struct CadetAudioMessage,
1352 NULL),
1353 GNUNET_MQ_handler_end () };
1354
1355 line->line_port = msg->line_port;
1356 line->port = GNUNET_CADET_open_port (cadet,
1357 &msg->line_port,
1358 &inbound_channel,
1359 line,
1360 NULL,
1361 &inbound_end,
1362 cadet_handlers);
1363 if (NULL == line->port)
1364 {
1365 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1366 _ ("Could not open line, port %s already in use!\n"),
1367 GNUNET_h2s (&msg->line_port));
1368 GNUNET_SERVICE_client_drop (line->client);
1369 return;
1370 }
1371 GNUNET_SERVICE_client_continue (line->client);
1372}
1373
1374
1375/**
1376 * Shutdown nicely
1377 *
1378 * @param cls closure, NULL
1379 */
1380static void
1381do_shutdown (void *cls)
1382{
1383 (void) cls;
1384 if (NULL != cadet)
1385 {
1386 GNUNET_CADET_disconnect (cadet);
1387 cadet = NULL;
1388 }
1389}
1390
1391
1392/**
1393 * Main function that will be run by the scheduler.
1394 *
1395 * @param cls closure
1396 * @param c configuration
1397 * @param service service handle
1398 */
1399static void
1400run (void *cls,
1401 const struct GNUNET_CONFIGURATION_Handle *c,
1402 struct GNUNET_SERVICE_Handle *service)
1403{
1404 (void) cls;
1405 (void) service;
1406 cfg = c;
1407 GNUNET_assert (GNUNET_OK ==
1408 GNUNET_CRYPTO_get_peer_identity (cfg, &my_identity));
1409 cadet = GNUNET_CADET_connect (cfg);
1410 if (NULL == cadet)
1411 {
1412 GNUNET_break (0);
1413 GNUNET_SCHEDULER_shutdown ();
1414 return;
1415 }
1416 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL);
1417}
1418
1419
1420/**
1421 * Define "main" method using service macro.
1422 */
1423GNUNET_SERVICE_MAIN (
1424 "conversation",
1425 GNUNET_SERVICE_OPTION_NONE,
1426 &run,
1427 &client_connect_cb,
1428 &client_disconnect_cb,
1429 NULL,
1430 GNUNET_MQ_hd_fixed_size (client_register_message,
1431 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_REGISTER,
1432 struct ClientPhoneRegisterMessage,
1433 NULL),
1434 GNUNET_MQ_hd_fixed_size (client_pickup_message,
1435 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICK_UP,
1436 struct ClientPhonePickupMessage,
1437 NULL),
1438 GNUNET_MQ_hd_fixed_size (client_suspend_message,
1439 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND,
1440 struct ClientPhoneSuspendMessage,
1441 NULL),
1442 GNUNET_MQ_hd_fixed_size (client_resume_message,
1443 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME,
1444 struct ClientPhoneResumeMessage,
1445 NULL),
1446 GNUNET_MQ_hd_fixed_size (client_hangup_message,
1447 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP,
1448 struct ClientPhoneHangupMessage,
1449 NULL),
1450 GNUNET_MQ_hd_var_size (client_call_message,
1451 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_CALL,
1452 struct ClientCallMessage,
1453 NULL),
1454 GNUNET_MQ_hd_var_size (client_audio_message,
1455 GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO,
1456 struct ClientAudioMessage,
1457 NULL),
1458 GNUNET_MQ_handler_end ());
1459
1460
1461/* end of gnunet-service-conversation.c */
diff --git a/src/contrib/service/conversation/gnunet_gst.c b/src/contrib/service/conversation/gnunet_gst.c
new file mode 100644
index 000000000..48d41a457
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet_gst.c
@@ -0,0 +1,1154 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 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 * @file conversation/gnunet_gst.c
22 * @brief FIXME
23 * @author Hark
24 */
25#include "platform.h"
26#include "gnunet_gst_def.h"
27
28/**
29 * Our configuration.
30 */
31static struct GNUNET_CONFIGURATION_Handle *cfg;
32
33
34void
35dump_buffer (unsigned n, const unsigned char*buf)
36{
37 const unsigned char *p, *end;
38 unsigned i, j;
39
40 end = buf + n;
41
42 for (i = 0;; i += 16)
43 {
44 p = buf + i;
45 for (j = 0; j < 16; j++)
46 {
47 fprintf (stderr, "%02X ", p[j]);
48 if (p + j >= end)
49 goto BREAKOUT;
50 }
51 fprintf (stderr, " ");
52 p = buf + i;
53 for (j = 0; j < 16; j++)
54 {
55 fprintf (stderr, "%c", isprint (p[j]) ? p[j] :
56 '.');
57 if (p + j >= end)
58 goto BREAKOUT;
59 }
60 fprintf (stderr, "\n");
61 }
62BREAKOUT:
63 return;
64}
65
66
67/***
68 * load gnunet configuration
69 */
70void
71gg_load_configuration (GNUNET_gstData *d)
72{
73 char *audiobackend_string;
74
75 cfg = GNUNET_CONFIGURATION_create ();
76 GNUNET_CONFIGURATION_load (cfg, "mediahelper.conf");
77
78 GNUNET_CONFIGURATION_get_value_string (cfg, "MEDIAHELPER", "JACK_PP_IN",
79 &d->jack_pp_in);
80 GNUNET_CONFIGURATION_get_value_string (cfg, "MEDIAHELPER", "JACK_PP_OUT",
81 &d->jack_pp_out);
82
83 GNUNET_CONFIGURATION_get_value_string (cfg, "MEDIAHELPER", "AUDIOBACKEND",
84 &audiobackend_string);
85
86 // printf("abstring: %s \n", audiobackend_string);
87
88 if (0 == strcasecmp (audiobackend_string, "AUTO"))
89 {
90 d->audiobackend = AUTO;
91 }
92 else if (0 == strcasecmp (audiobackend_string, "JACK"))
93 {
94 d->audiobackend = JACK;
95 }
96 else if (0 == strcasecmp (audiobackend_string, "ALSA"))
97 {
98 d->audiobackend = ALSA;
99 }
100 else if (0 == strcasecmp (audiobackend_string, "FAKE"))
101 {
102 d->audiobackend = FAKE;
103 }
104 else if (0 == strcasecmp (audiobackend_string, "TEST"))
105 {
106 d->audiobackend = TEST;
107 }
108 else
109 {
110 d->audiobackend = AUTO;
111 }
112
113 if (GNUNET_CONFIGURATION_get_value_yesno (cfg, "MEDIAHELPER",
114 "REMOVESILENCE") == GNUNET_YES)
115 {
116 d->dropsilence = TRUE;
117 }
118 else
119 {
120 d->dropsilence = FALSE;
121 }
122
123 if (GNUNET_CONFIGURATION_get_value_yesno (cfg, "MEDIAHELPER",
124 "NO_GN_HEADERS") == GNUNET_YES)
125 {
126 d->pure_ogg = TRUE;
127 }
128 else
129 {
130 d->pure_ogg = FALSE;
131 }
132
133
134 if (GNUNET_CONFIGURATION_get_value_yesno (cfg, "MEDIAHELPER", "USERTP") ==
135 GNUNET_YES)
136 {
137 d->usertp = TRUE;
138 }
139 else
140 {
141 d->usertp = FALSE;
142 }
143
144// GNUNET_CONFIGURATION_write(cfg, "mediahelper.conf");
145}
146
147
148static void
149write_data (const char *ptr, size_t msg_size)
150{
151 ssize_t ret;
152 size_t off;
153
154 off = 0;
155 while (off < msg_size)
156 {
157 ret = write (1, &ptr[off], msg_size - off);
158 if (0 >= ret)
159 {
160 if (-1 == ret)
161 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "write");
162// quit (2);
163 }
164 off += ret;
165 }
166}
167
168
169extern GstFlowReturn
170on_appsink_new_sample (GstElement *element, GNUNET_gstData *d)
171{
172 // size of message including gnunet header
173 size_t msg_size;
174
175 GstSample *s;
176 GstBuffer *b;
177 GstMapInfo map;
178
179/*
180 const GstStructure *si;
181 char *si_str;
182 GstCaps *s_caps;
183 char *caps_str;
184 */if (gst_app_sink_is_eos (GST_APP_SINK (element)))
185 return GST_FLOW_OK;
186
187 // pull sample from appsink
188 s = gst_app_sink_pull_sample (GST_APP_SINK (element));
189
190 if (s == NULL)
191 return GST_FLOW_OK;
192
193 if (! GST_IS_SAMPLE (s))
194 return GST_FLOW_OK;
195
196 b = gst_sample_get_buffer (s);
197
198 GST_WARNING ("caps are %" GST_PTR_FORMAT, gst_sample_get_caps (s));
199
200
201 gst_buffer_map (b, &map, GST_MAP_READ);
202
203 size_t len;
204 len = map.size;
205 if (len > UINT16_MAX - sizeof(struct AudioMessage))
206 {
207 // this should never happen?
208 printf ("GSTREAMER sample too big! \n");
209 exit (20);
210 len = UINT16_MAX - sizeof(struct AudioMessage);
211 }
212
213 msg_size = sizeof(struct AudioMessage) + len;
214
215 // copy the data into audio_message
216 GNUNET_memcpy (((char *) &(d->audio_message)[1]), map.data, len);
217 (d->audio_message)->header.size = htons ((uint16_t) msg_size);
218 if (d->pure_ogg)
219 // write the audio_message without the gnunet headers
220 write_data ((const char *) &(d->audio_message)[1], len);
221 else
222 write_data ((const char *) d->audio_message, msg_size);
223
224 gst_sample_unref (s);
225 return GST_FLOW_OK;
226}
227
228
229/***
230 * Dump a pipeline graph
231 */
232extern void
233pl_graph (GstElement *pipeline)
234{
235#ifdef IS_SPEAKER
236 gst_debug_bin_to_dot_file_with_ts (GST_BIN (pipeline),
237 GST_DEBUG_GRAPH_SHOW_ALL,
238 "playback_helper.dot");
239#endif
240#ifdef IS_MIC
241 gst_debug_bin_to_dot_file_with_ts (GST_BIN (pipeline),
242 GST_DEBUG_GRAPH_SHOW_ALL,
243 "record_helper.dot");
244#endif
245
246
247 // load_configuration();
248}
249
250
251extern gboolean
252gnunet_gst_bus_call (GstBus *bus, GstMessage *msg, gpointer data)
253{
254 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
255 "Bus message\n");
256 switch (GST_MESSAGE_TYPE (msg))
257 {
258 case GST_MESSAGE_EOS:
259 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
260 "End of stream\n");
261 exit (10);
262 break;
263
264 case GST_MESSAGE_ERROR:
265 {
266 gchar *debug;
267 GError *error;
268
269 gst_message_parse_error (msg, &error, &debug);
270 g_free (debug);
271
272 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
273 "Error: %s\n",
274 error->message);
275 g_error_free (error);
276
277 exit (10);
278 break;
279 }
280
281 default:
282 break;
283 }
284
285 return TRUE;
286}
287
288
289/* called when pipeline changes state */
290extern void
291state_changed_cb (GstBus *bus, GstMessage *msg, GNUNET_gstData *d)
292{
293 GstState old_state, new_state, pending_state;
294
295 gst_message_parse_state_changed (msg, &old_state, &new_state,
296 &pending_state);
297 switch (new_state)
298 {
299 case GST_STATE_READY:
300// printf("ready.... \n");
301 // pl_graph(GST_ELEMENT(d->pipeline));
302 break;
303
304 case GST_STATE_PLAYING:
305
306 // GST_LOG ("caps are %" GST_PTR_FORMAT, caps);
307
308 // printf("Playing.... \n");
309 pl_graph (GST_ELEMENT (d->pipeline));
310 break;
311
312 case GST_STATE_VOID_PENDING:
313 // printf("void_pending.... \n");
314 // pl_graph(GST_ELEMENT(d->pipeline));
315 break;
316
317 case GST_STATE_NULL:
318 // printf("null.... \n");
319 // pl_graph(GST_ELEMENT(d->pipeline));
320 break;
321
322 case GST_STATE_PAUSED:
323 // printf("paused.... \n");
324 // pl_graph(GST_ELEMENT(d->pipeline));
325 break;
326 }
327}
328
329
330static void
331application_cb (GstBus *bus, GstMessage *msg, GNUNET_gstData *data)
332{
333 // printf("application cb");
334 return;
335}
336
337
338static void
339error_cb (GstBus *bus, GstMessage *msg, GNUNET_gstData *data)
340{
341 // printf("error cb");
342 return;
343}
344
345
346static void
347eos_cb (GstBus *bus, GstMessage *msg, GNUNET_gstData *data)
348{
349 // printf("eos cb");
350 return;
351}
352
353
354extern void
355gg_setup_gst_bus (GNUNET_gstData *d)
356{
357 GstBus *bus;
358
359 bus = gst_element_get_bus (GST_ELEMENT (d->pipeline));
360 gst_bus_add_signal_watch (bus);
361 g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
362 d);
363 g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb,
364 d);
365 g_signal_connect (G_OBJECT (bus), "message::state-changed",
366 (GCallback) state_changed_cb, d);
367 g_signal_connect (G_OBJECT (bus), "message::application",
368 (GCallback) application_cb, d);
369 g_signal_connect (G_OBJECT (bus), "message::about-to-finish",
370 (GCallback) application_cb, d);
371 gst_object_unref (bus);
372}
373
374
375/*
376 * take buffer from gstreamer and feed it to gnunet
377 */
378/*
379 extern int
380 feed_buffer_to_gnunet (GNUNET_gstData * d)
381 {
382 GstSample *s;
383 GstBuffer *b;
384 GstMapInfo m;
385 size_t len, msg_size;
386 const char *ptr;
387 int phase;
388
389 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulling...\n");
390 s = gst_app_sink_pull_sample (GST_APP_SINK(d->appsink));
391 if (NULL == s)
392 {
393 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulled NULL\n");
394 return OK;
395 }
396 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "...pulled!\n");
397
398 const GstStructure *si;
399 char *si_str;
400 GstCaps *s_caps;
401 char *caps_str;
402 si = gst_sample_get_info (s);
403 if (si)
404 {
405 si_str = gst_structure_to_string (si);
406 if (si_str)
407 {
408 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample %s\n", si_str);
409 g_free (si_str);
410 }
411 }
412 else
413 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no info\n");
414 s_caps = gst_sample_get_caps (s);
415 if (s_caps)
416 {
417 caps_str = gst_caps_to_string (s_caps);
418 if (caps_str)
419 {
420 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with caps %s\n", caps_str);
421 g_free (caps_str);
422 }
423 }
424 else
425 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no caps\n");
426
427 b = gst_sample_get_buffer (s);
428 if (NULL == b || !gst_buffer_map (b, &m, GST_MAP_READ))
429 {
430 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "got NULL buffer %p or failed to map the buffer\n", b);
431 gst_sample_unref (s);
432 return FAIL;
433 }
434
435 len = m.size;
436 if (len > UINT16_MAX - sizeof (struct AudioMessage))
437 {
438 GNUNET_break (0);
439 len = UINT16_MAX - sizeof (struct AudioMessage);
440 }
441 msg_size = sizeof (struct AudioMessage) + len;
442 audio_message.header.size = htons ((uint16_t) msg_size);
443
444
445 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
446 "Sending %u bytes of audio data\n", (unsigned int) msg_size);
447 for (phase = 0; phase < 2; phase++)
448 {
449 size_t offset;
450 size_t to_send;
451 ssize_t ret;
452 if (0 == phase && !d->pure_ogg)
453 {
454 //#ifdef DEBUG_RECORD_PURE_OGG
455
456 // if (d->pure_ogg)
457 // break;
458
459 //#endif
460 ptr = (const char *) &audio_message;
461 to_send = sizeof (audio_message);
462 }
463 else
464 {
465 ptr = (const char *) m.data;
466 to_send = len;
467 }
468 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
469 "Sending %u bytes on phase %d\n", (unsigned int) to_send, phase);
470 for (offset = 0; offset < to_send; offset += ret)
471 {
472 ret = write (1, &ptr[offset], to_send - offset);
473 if (0 >= ret)
474 {
475 if (-1 == ret)
476 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
477 "Failed to write %u bytes at offset %u (total %u) in phase %d: %s\n",
478 (unsigned int) to_send - offset, (unsigned int) offset,
479 (unsigned int) (to_send + offset), phase, strerror (errno));
480 // abort_send = 1;
481 return FAIL;
482 }
483 }
484
485 // if (abort_send)
486 // break;
487
488 }
489 gst_buffer_unmap (b, &m);
490 gst_sample_unref (s);
491 }
492 */
493
494
495extern int
496feed_buffer_to_gst (const char *audio, size_t b_len, GNUNET_gstData *d)
497{
498 GstBuffer *b;
499 gchar *bufspace;
500 GstFlowReturn flow;
501
502 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
503 "Feeding %u bytes to GStreamer\n",
504 (unsigned int) b_len);
505
506 bufspace = g_memdup (audio, b_len);
507 b = gst_buffer_new_wrapped (bufspace, b_len);
508 if (NULL == b)
509 {
510 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
511 "Failed to wrap a buffer\n");
512 g_free (bufspace);
513 return GNUNET_SYSERR;
514 }
515 if (GST_APP_SRC (d->appsrc) == NULL)
516 exit (10);
517 flow = gst_app_src_push_buffer (GST_APP_SRC (d->appsrc), b);
518 /* They all return GNUNET_OK, because currently player stops when
519 * data stops coming. This might need to be changed for the player
520 * to also stop when pipeline breaks.
521 */
522 switch (flow)
523 {
524 case GST_FLOW_OK:
525 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
526 "Fed %u bytes to the pipeline\n",
527 (unsigned int) b_len);
528 break;
529
530 case GST_FLOW_FLUSHING:
531 /* buffer was dropped, because pipeline state is not PAUSED or PLAYING */
532 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
533 "Dropped a buffer\n");
534 break;
535
536 case GST_FLOW_EOS:
537 /* end of stream */
538 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
539 "EOS\n");
540 break;
541
542 default:
543 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
544 "Unexpected push result\n");
545 break;
546 }
547 return GNUNET_OK;
548}
549
550
551/**
552 * debug making elements
553 */
554extern GstElement *
555gst_element_factory_make_debug (gchar *factoryname, gchar *name)
556{
557 GstElement *element;
558
559 element = gst_element_factory_make (factoryname, name);
560
561 if (element == NULL)
562 {
563 printf ("\n Failed to create element - type: %s name: %s \n", factoryname,
564 name);
565 exit (10);
566 return element;
567 }
568 else
569 {
570 return element;
571 }
572}
573
574
575/*
576 static gboolean
577 gst_element_link_many_debug(...)
578 {
579 va_list arguments;
580 gst_element_link_many(argptr);
581 }
582
583 #define gst_element_link_many(...) \
584 gst_element_link_many_debug(__VA_ARGS__)
585 */
586extern void
587lf (char *msg)
588{
589 printf ("linking elements failed: %s", msg);
590 exit (10);
591}
592
593
594/***
595 * used to set properties on autoaudiosink's chosen sink
596 */
597static void
598autoaudiosink_child_added (GstChildProxy *child_proxy,
599 GObject *object,
600 gchar *name,
601 gpointer user_data)
602{
603 if (GST_IS_AUDIO_BASE_SRC (object))
604 g_object_set (object,
605 "buffer-time", (gint64) BUFFER_TIME,
606 "latency-time", (gint64) LATENCY_TIME,
607 NULL);
608}
609
610
611/***
612 * used to set properties on autoaudiosource's chosen sink
613 */
614static void
615autoaudiosource_child_added (GstChildProxy *child_proxy, GObject *object,
616 gchar *name, gpointer user_data)
617{
618 if (GST_IS_AUDIO_BASE_SRC (object))
619 g_object_set (object, "buffer-time", (gint64) BUFFER_TIME, "latency-time",
620 (gint64) LATENCY_TIME, NULL);
621}
622
623
624GstElement *
625get_pipeline (GstElement *element)
626{
627 GstPipeline *p;
628
629 p = GST_PIPELINE (gst_object_get_parent (GST_OBJECT (element)));
630
631 return GST_ELEMENT (p);
632}
633
634
635static void
636decoder_ogg_pad_added (GstElement *element,
637 GstPad *pad,
638 gpointer data)
639{
640 GstPad *sinkpad;
641 GstElement *decoder = (GstElement *) data;
642
643 printf ("==== ogg pad added callback \n");
644 /* We can now link this pad with the opus-decoder sink pad */
645// pl_graph(get_pipeline(element));
646 sinkpad = gst_element_get_static_pad (decoder, "sink");
647
648 gst_pad_link (pad, sinkpad);
649 gst_element_link_many (element, decoder, NULL);
650 gst_object_unref (sinkpad);
651}
652
653
654int
655gnunet_read (GNUNET_gstData *d)
656{
657 char readbuf[MAXLINE];
658 int ret;
659
660 printf ("read \n");
661 ret = read (0, readbuf, sizeof(readbuf));
662 if (0 > ret)
663 {
664 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
665 _ ("Read error from STDIN: %d %s\n"),
666 ret, strerror (errno));
667 return FAIL;
668 }
669 // toff += ret;
670 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
671 "Received %d bytes of audio data\n",
672 (int) ret);
673 if (0 == ret)
674 return FAIL;
675 // #ifdef DEBUG_READ_PURE_OGG
676
677 if (d->pure_ogg)
678 {
679 feed_buffer_to_gst (readbuf, ret, d);
680 }
681 else
682 {
683 // #endif
684 GNUNET_MST_from_buffer (d->stdin_mst,
685 readbuf,
686 ret,
687 GNUNET_NO,
688 GNUNET_NO);
689 }
690 return 0;
691}
692
693
694/**
695 * Message callback
696 *
697 * @param msg message we received.
698 * @return #GNUNET_OK on success,
699 * #GNUNET_NO to stop further processing due to disconnect (no error)
700 * #GNUNET_SYSERR to stop further processing due to error
701 */
702static int
703stdin_receiver (void *cls,
704 const struct GNUNET_MessageHeader *msg)
705{
706 struct AudioMessage *audio;
707 size_t b_len;
708
709 printf ("stdin receiver \n ");
710 dump_buffer (sizeof(msg),
711 (const unsigned char *) msg);
712
713 switch (ntohs (msg->type))
714 {
715 case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO:
716 audio = (struct AudioMessage *) msg;
717
718 b_len = ntohs (audio->header.size) - sizeof(struct AudioMessage);
719 printf ("feeding buffer to gst \n ");
720 feed_buffer_to_gst ((const char *) &audio[1], b_len, cls);
721 break;
722
723 default:
724 printf ("No audio message: %u \n ", ntohs (msg->type));
725 break;
726 }
727 return GNUNET_OK;
728}
729
730
731GstBin *
732get_app (GNUNET_gstData *d, int type)
733{
734 GstBin *bin;
735 GstPad *pad, *ghostpad;
736
737 if (type == SOURCE)
738 {
739 bin = GST_BIN (gst_bin_new ("Gnunet appsrc"));
740
741
742 GNUNET_assert (GNUNET_OK ==
743 GNUNET_log_setup ("gnunet-helper-audio-playback",
744 "WARNING",
745 NULL));
746
747 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
748 "Audio playback starts\n");
749 printf (" creating appsrc \n ");
750 // d->audio_message.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
751
752// d->audio_message = GNUNET_malloc (UINT16_MAX);
753// d->audio_message = (AudioMessage*)malloc(sizeof(struct AudioMessage));
754// d->audio_message = GNUNET_malloc(sizeof(struct AudioMessage));
755
756
757 // d->audio_message.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
758
759
760 d->stdin_mst = GNUNET_MST_create (&stdin_receiver, d);
761
762 if (d->stdin_mst == NULL)
763 printf ("stdin_mst = NULL");
764
765 d->appsrc = gst_element_factory_make ("appsrc", "appsrc");
766
767 gst_bin_add_many (bin, d->appsrc, NULL);
768// gst_element_link_many ( encoder, muxer, NULL);
769
770 pad = gst_element_get_static_pad (d->appsrc, "src");
771 ghostpad = gst_ghost_pad_new ("src", pad);
772 }
773 if (type == SINK)
774 {
775 bin = GST_BIN (gst_bin_new ("Gnunet appsink"));
776
777
778 GNUNET_assert (GNUNET_OK ==
779 GNUNET_log_setup ("gnunet-helper-audio-record",
780 "WARNING",
781 NULL));
782
783 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
784 "Audio source starts\n");
785
786 d->appsink = gst_element_factory_make ("appsink", "appsink");
787
788 // Move this out of here!
789 d->audio_message = GNUNET_malloc (UINT16_MAX);
790 (d->audio_message)->header.type = htons (
791 GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
792 g_object_set (G_OBJECT (d->appsink), "emit-signals", TRUE, "sync", TRUE,
793 NULL);
794
795 g_signal_connect (d->appsink, "new-sample",
796 G_CALLBACK (on_appsink_new_sample), &d);
797
798 gst_bin_add_many (bin, d->appsink, NULL);
799// gst_element_link_many ( encoder, muxer, NULL);
800
801 pad = gst_element_get_static_pad (d->appsink, "sink");
802 ghostpad = gst_ghost_pad_new ("sink", pad);
803 }
804
805 /* set the bin pads */
806 gst_pad_set_active (ghostpad, TRUE);
807 gst_element_add_pad (GST_ELEMENT (bin), ghostpad);
808
809 gst_object_unref (pad);
810
811 return bin;
812}
813
814
815extern GstBin *
816get_coder (GNUNET_gstData *d, int type)
817{
818 GstBin *bin;
819 GstPad *srcpad, *sinkpad, *srcghostpad, *sinkghostpad;
820 GstCaps *rtpcaps;
821 GstElement *encoder, *muxer, *decoder, *demuxer, *jitterbuffer,
822 *rtpcapsfilter;
823
824 if (d->usertp == TRUE)
825 {
826 /*
827 * application/x-rtp, media=(string)audio, clock-rate=(int)48000, encoding-name=(string)OPUS, sprop-maxcapturerate=(string)48000, sprop-stereo=(string)0, payload=(int)96, encoding-params=(string)2, ssrc=(uint)630297634, timestamp-offset=(uint)678334141, seqnum-offset=(uint)16938 */
828/*
829 rtpcaps = gst_caps_new_simple ("application/x-rtp",
830 "media", G_TYPE_STRING, "audio",
831 "clock-rate", G_TYPE_INT, SAMPLING_RATE,
832 "encoding-name", G_TYPE_STRING, "OPUS",
833 "payload", G_TYPE_INT, 96,
834 "sprop-stereo", G_TYPE_STRING, "0",
835 "encoding-params", G_TYPE_STRING, "2",
836 NULL);
837 */ rtpcaps = gst_caps_new_simple ("application/x-rtp",
838 "media", G_TYPE_STRING, "audio",
839 "clock-rate", G_TYPE_INT, SAMPLING_RATE,
840 "encoding-name", G_TYPE_STRING, "OPUS",
841 "payload", G_TYPE_INT, 96,
842 "sprop-stereo", G_TYPE_STRING, "0",
843 "encoding-params", G_TYPE_STRING, "2",
844 NULL);
845
846
847 rtpcapsfilter = gst_element_factory_make ("capsfilter", "rtpcapsfilter");
848
849 g_object_set (G_OBJECT (rtpcapsfilter),
850 "caps", rtpcaps,
851 NULL);
852 gst_caps_unref (rtpcaps);
853 }
854
855
856 if (type == ENCODER)
857 {
858 bin = GST_BIN (gst_bin_new ("Gnunet audioencoder"));
859
860 encoder = gst_element_factory_make ("opusenc", "opus-encoder");
861 if (d->usertp == TRUE)
862 {
863 muxer = gst_element_factory_make ("rtpopuspay", "rtp-payloader");
864 }
865 else
866 {
867 muxer = gst_element_factory_make ("oggmux", "ogg-muxer");
868 }
869 g_object_set (G_OBJECT (encoder),
870 /* "bitrate", 64000, */
871 /* "bandwidth", OPUS_BANDWIDTH_FULLBAND, */
872 "inband-fec", INBAND_FEC_MODE,
873 "packet-loss-percentage", PACKET_LOSS_PERCENTAGE,
874 "max-payload-size", MAX_PAYLOAD_SIZE,
875 "audio", TRUE, /* VoIP, not audio */
876 "frame-size", OPUS_FRAME_SIZE,
877 NULL);
878
879 if (d->usertp != TRUE)
880 {
881 g_object_set (G_OBJECT (muxer),
882 "max-delay", OGG_MAX_DELAY,
883 "max-page-delay", OGG_MAX_PAGE_DELAY,
884 NULL);
885 }
886
887 gst_bin_add_many (bin, encoder, muxer, NULL);
888 gst_element_link_many (encoder, muxer, NULL);
889 sinkpad = gst_element_get_static_pad (encoder, "sink");
890 sinkghostpad = gst_ghost_pad_new ("sink", sinkpad);
891
892 srcpad = gst_element_get_static_pad (muxer, "src");
893 srcghostpad = gst_ghost_pad_new ("src", srcpad);
894 }
895 if (type == DECODER)
896 {
897 bin = GST_BIN (gst_bin_new ("Gnunet audiodecoder"));
898
899 // decoder
900 if (d->usertp == TRUE)
901 {
902 demuxer = gst_element_factory_make ("rtpopusdepay", "ogg-demuxer");
903 jitterbuffer = gst_element_factory_make ("rtpjitterbuffer",
904 "rtpjitterbuffer");
905 }
906 else
907 {
908 demuxer = gst_element_factory_make ("oggdemux", "ogg-demuxer");
909 }
910 decoder = gst_element_factory_make ("opusdec", "opus-decoder");
911
912 if (d->usertp == TRUE)
913 {
914 gst_bin_add_many (bin, rtpcapsfilter, jitterbuffer, demuxer, decoder,
915 NULL);
916 gst_element_link_many (rtpcapsfilter, jitterbuffer, demuxer, decoder,
917 NULL);
918 sinkpad = gst_element_get_static_pad (rtpcapsfilter, "sink");
919 }
920 else
921 {
922 gst_bin_add_many (bin, demuxer, decoder, NULL);
923
924 g_signal_connect (demuxer,
925 "pad-added",
926 G_CALLBACK (decoder_ogg_pad_added),
927 decoder);
928
929 sinkpad = gst_element_get_static_pad (demuxer, "sink");
930 }
931 sinkghostpad = gst_ghost_pad_new ("sink", sinkpad);
932
933 srcpad = gst_element_get_static_pad (decoder, "src");
934 srcghostpad = gst_ghost_pad_new ("src", srcpad);
935 }
936
937 // add pads to the bin
938 gst_pad_set_active (sinkghostpad, TRUE);
939 gst_element_add_pad (GST_ELEMENT (bin), sinkghostpad);
940
941 gst_pad_set_active (srcghostpad, TRUE);
942 gst_element_add_pad (GST_ELEMENT (bin), srcghostpad);
943
944
945 return bin;
946}
947
948
949extern GstBin *
950get_audiobin (GNUNET_gstData *d, int type)
951{
952 GstBin *bin;
953 GstElement *sink, *source, *queue, *conv, *resampler, *removesilence, *filter;
954 GstPad *pad, *ghostpad;
955 GstCaps *caps;
956
957 if (type == SINK)
958 {
959 bin = GST_BIN (gst_bin_new ("Gnunet audiosink"));
960
961 /* Create all the elements */
962 if (d->dropsilence == TRUE)
963 {
964 queue = gst_element_factory_make ("queue", "queue");
965 removesilence = gst_element_factory_make ("removesilence",
966 "removesilence");
967 }
968
969 conv = gst_element_factory_make ("audioconvert", "converter");
970 resampler = gst_element_factory_make ("audioresample", "resampler");
971
972 if (d->audiobackend == AUTO)
973 {
974 sink = gst_element_factory_make ("autoaudiosink", "audiosink");
975 g_signal_connect (sink, "child-added", G_CALLBACK (
976 autoaudiosink_child_added), NULL);
977 }
978
979 if (d->audiobackend == ALSA)
980 {
981 sink = gst_element_factory_make ("alsaaudiosink", "audiosink");
982 }
983
984 if (d->audiobackend == JACK)
985 {
986 sink = gst_element_factory_make ("jackaudiosink", "audiosink");
987
988 g_object_set (G_OBJECT (sink), "client-name", "gnunet", NULL);
989
990 if (g_object_class_find_property
991 (G_OBJECT_GET_CLASS (sink), "port-pattern"))
992 {
993// char *portpattern = "system";
994
995 g_object_set (G_OBJECT (sink), "port-pattern", d->jack_pp_out,
996 NULL);
997 }
998 }
999
1000 if (d->audiobackend == FAKE)
1001 {
1002 sink = gst_element_factory_make ("fakesink", "audiosink");
1003 }
1004
1005 g_object_set (sink,
1006 "buffer-time", (gint64) BUFFER_TIME,
1007 "latency-time", (gint64) LATENCY_TIME,
1008 NULL);
1009
1010 if (d->dropsilence == TRUE)
1011 {
1012 // Do not remove silence by default
1013 g_object_set (removesilence, "remove", FALSE, NULL);
1014 g_object_set (queue, "max-size-buffers", 12, NULL);
1015 /*
1016 g_signal_connect (source,
1017 "need-data",
1018 G_CALLBACK(appsrc_need_data),
1019 NULL);
1020
1021 g_signal_connect (source,
1022 "enough-data",
1023 G_CALLBACK(appsrc_enough_data),
1024 NULL);
1025 *//*
1026 g_signal_connect (queue,
1027 "notify::current-level-bytes",
1028 G_CALLBACK(queue_current_level),
1029 NULL);
1030
1031 g_signal_connect (queue,
1032 "underrun",
1033 G_CALLBACK(queue_underrun),
1034 NULL);
1035
1036 g_signal_connect (queue,
1037 "running",
1038 G_CALLBACK(queue_running),
1039 NULL);
1040
1041 g_signal_connect (queue,
1042 "overrun",
1043 G_CALLBACK(queue_overrun),
1044 NULL);
1045
1046 g_signal_connect (queue,
1047 "pushing",
1048 G_CALLBACK(queue_pushing),
1049 NULL);
1050 */ }
1051
1052
1053 gst_bin_add_many (bin, conv, resampler, sink, NULL);
1054 gst_element_link_many (conv, resampler, sink, NULL);
1055
1056 if (d->dropsilence == TRUE)
1057 {
1058 gst_bin_add_many (bin, queue, removesilence, NULL);
1059
1060 if (! gst_element_link_many (queue, removesilence, conv, NULL))
1061 lf ("queue, removesilence, conv ");
1062
1063 pad = gst_element_get_static_pad (queue, "sink");
1064 }
1065 else
1066 {
1067 pad = gst_element_get_static_pad (conv, "sink");
1068 }
1069
1070 ghostpad = gst_ghost_pad_new ("sink", pad);
1071 }
1072 else
1073 {
1074 // SOURCE
1075
1076 bin = GST_BIN (gst_bin_new ("Gnunet audiosource"));
1077
1078 // source = gst_element_factory_make("audiotestsrc", "audiotestsrcbla");
1079
1080 if (d->audiobackend == AUTO)
1081 {
1082 source = gst_element_factory_make ("autoaudiosrc", "audiosource");
1083 }
1084 if (d->audiobackend == ALSA)
1085 {
1086 source = gst_element_factory_make ("alsasrc", "audiosource");
1087 }
1088 if (d->audiobackend == JACK)
1089 {
1090 source = gst_element_factory_make ("jackaudiosrc", "audiosource");
1091 }
1092 if (d->audiobackend == TEST)
1093 {
1094 source = gst_element_factory_make ("audiotestsrc", "audiosource");
1095 }
1096
1097 filter = gst_element_factory_make ("capsfilter", "filter");
1098 conv = gst_element_factory_make ("audioconvert", "converter");
1099 resampler = gst_element_factory_make ("audioresample", "resampler");
1100
1101 if (d->audiobackend == AUTO)
1102 {
1103 g_signal_connect (source, "child-added", G_CALLBACK (
1104 autoaudiosource_child_added), NULL);
1105 }
1106 else
1107 {
1108 if (GST_IS_AUDIO_BASE_SRC (source))
1109 g_object_set (source, "buffer-time", (gint64) BUFFER_TIME,
1110 "latency-time", (gint64) LATENCY_TIME, NULL);
1111 if (d->audiobackend == JACK)
1112 {
1113 g_object_set (G_OBJECT (source), "client-name", "gnunet", NULL);
1114 if (g_object_class_find_property
1115 (G_OBJECT_GET_CLASS (source), "port-pattern"))
1116 {
1117 char *portpattern = "moc";
1118
1119 g_object_set (G_OBJECT (source), "port-pattern", portpattern,
1120 NULL);
1121 }
1122 }
1123 }
1124
1125 caps = gst_caps_new_simple ("audio/x-raw",
1126 /* "format", G_TYPE_STRING, "S16LE", */
1127 /* "rate", G_TYPE_INT, SAMPLING_RATE,*/
1128 "channels", G_TYPE_INT, OPUS_CHANNELS,
1129 /* "layout", G_TYPE_STRING, "interleaved",*/
1130 NULL);
1131
1132 g_object_set (G_OBJECT (filter),
1133 "caps", caps,
1134 NULL);
1135 gst_caps_unref (caps);
1136
1137 gst_bin_add_many (bin, source, filter, conv, resampler, NULL);
1138 gst_element_link_many (source, filter, conv, resampler, NULL);
1139
1140 pad = gst_element_get_static_pad (resampler, "src");
1141
1142
1143 /* pads */
1144 ghostpad = gst_ghost_pad_new ("src", pad);
1145 }
1146
1147 /* set the bin pads */
1148 gst_pad_set_active (ghostpad, TRUE);
1149 gst_element_add_pad (GST_ELEMENT (bin), ghostpad);
1150
1151 gst_object_unref (pad);
1152
1153 return bin;
1154}
diff --git a/src/contrib/service/conversation/gnunet_gst.h b/src/contrib/service/conversation/gnunet_gst.h
new file mode 100644
index 000000000..479ae14b3
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet_gst.h
@@ -0,0 +1,62 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 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 * @file conversation/gnunet_gst.c
22 * @brief FIXME
23 * @author Hark
24 */
25
26// which audiobackend we use
27//
28
29/*
30 int audiobackend = JACK;
31 int dropsilence = TRUE;
32 int enough = 0;
33 int usertp = TRUE;
34 */
35
36#define gst_element_factory_make(element, name) gst_element_factory_make_debug ( \
37 element, name);
38
39extern void pl_graph ();
40
41
42extern GstElement *
43gst_element_factory_make_debug (gchar *, gchar *);
44
45extern GstBin *
46get_audiobin (GNUNET_gstData *, int);
47
48extern GstBin *
49get_coder (GNUNET_gstData *, int);
50
51
52extern gboolean
53gnunet_gst_bus_call (GstBus *bus, GstMessage *msg, gpointer data);
54
55extern void
56gg_setup_gst_bus (GNUNET_gstData *d);
57
58extern void
59gg_load_configuration (GNUNET_gstData *d);
60
61extern GstFlowReturn
62on_appsink_new_sample (GstElement *, GNUNET_gstData *);
diff --git a/src/contrib/service/conversation/gnunet_gst_def.h b/src/contrib/service/conversation/gnunet_gst_def.h
new file mode 100644
index 000000000..7bfcc5e53
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet_gst_def.h
@@ -0,0 +1,219 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 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 * @file conversation/gnunet_gst_def.h
22 * @brief FIXME
23 * @author Hark
24 */
25
26#include <getopt.h>
27#include <string.h>
28#include <stdio.h>
29#include <ctype.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <time.h>
33#include <regex.h>
34
35
36#include "platform.h"
37#include "gnunet_util_lib.h"
38#include "gnunet_protocols.h"
39// #include "gnunet/conversation.h" doesn't get installed
40#include "conversation.h"
41#include "gnunet_constants.h"
42#include "gnunet_core_service.h"
43#include "gnunet_common.h"
44
45/*
46 #include <gst/gst.h>
47 #include <gst/audio/gstaudiobasesrc.h>
48 #include <gst/app/gstappsrc.h>
49 */
50
51/* huh
52 #include <glib-2.0/glib.h>
53
54 #include <gstreamer-1.0/gst/gst.h>
55 #include <gstreamer-1.0/gst/pbutils/pbutils.h>
56 #include <gstreamer-1.0/gst/video/videooverlay.h>
57 #include <gstreamer-1.0/gst/audio/gstaudiobasesrc.h>
58 #include <gstreamer-1.0/gst/app/gstappsrc.h>
59 */
60
61#include <gst/gst.h>
62#include <gst/audio/gstaudiobasesrc.h>
63#include <gst/app/gstappsrc.h>
64#include <glib.h>
65#include <gst/app/gstappsink.h>
66
67// sockets
68#if HAVE_NETINET_IN_H
69#include <netinet/in.h>
70#endif
71#include <sys/socket.h>
72#include <arpa/inet.h>
73#include <netdb.h>
74
75#include <sys/types.h>
76#include <fcntl.h>
77
78
79// glib stuff
80// #include <glib.h>
81#include <glib-2.0/glib/gprintf.h>
82// #include <glib-unix.h>
83
84// static struct AudioMessage *audio_message;
85
86
87typedef struct GNUNET_gstData GNUNET_gstData;
88struct GNUNET_gstData
89{
90 // general
91 GstPipeline *pipeline;
92
93 // things
94 struct AudioMessage *audio_message;
95 struct GNUNET_MessageStreamTokenizer *stdin_mst;
96 GstElement *appsrc;
97 GstElement *appsink;
98 // settings
99 int audiobackend;
100 int dropsilence;
101 int usertp;
102 int pure_ogg;
103 char *jack_pp_in;
104 char *jack_pp_out;
105};
106
107
108#define DEBUG_READ_PURE_OGG 1
109#define DEBUG_RECORD_PURE_OGG 1
110
111
112/**
113 * How much data to read in one go
114 */
115#define MAXLINE 4096
116
117/**
118 * Max number of microseconds to buffer in audiosink.
119 * Default is 1000
120 */
121#define BUFFER_TIME 1000
122
123/**
124 * Min number of microseconds to buffer in audiosink.
125 * Default is 1000
126 */
127#define LATENCY_TIME 1000
128
129
130/**
131 * Number of channels.
132 * Must be one of the following (from libopusenc documentation):
133 * 1, 2
134 */
135#define OPUS_CHANNELS 1
136
137/**
138 * Maximal size of a single opus packet.
139 */
140#define MAX_PAYLOAD_SIZE (1024 / OPUS_CHANNELS)
141
142/**
143 * Size of a single frame fed to the encoder, in ms.
144 * Must be one of the following (from libopus documentation):
145 * 2.5, 5, 10, 20, 40 or 60
146 */
147#define OPUS_FRAME_SIZE 40
148
149/**
150 * Expected packet loss to prepare for, in percents.
151 */
152#define PACKET_LOSS_PERCENTAGE 1
153
154/**
155 * Set to 1 to enable forward error correction.
156 * Set to 0 to disable.
157 */
158#define INBAND_FEC_MODE 1
159
160/**
161 * Max number of microseconds to buffer in audiosource.
162 * Default is 200000
163 */
164#define BUFFER_TIME 1000 /* 1ms */
165
166/**
167 * Min number of microseconds to buffer in audiosource.
168 * Default is 10000
169 */
170#define LATENCY_TIME 1000 /* 1ms */
171
172/**
173 * Maximum delay in multiplexing streams, in ns.
174 * Setting this to 0 forces page flushing, which
175 * decreases delay, but increases overhead.
176 */
177#define OGG_MAX_DELAY 0
178
179/**
180 * Maximum delay for sending out a page, in ns.
181 * Setting this to 0 forces page flushing, which
182 * decreases delay, but increases overhead.
183 */
184#define OGG_MAX_PAGE_DELAY 0
185
186#define SAMPLING_RATE 48000
187
188enum
189{
190 AUTO,
191 JACK,
192 ALSA,
193 FAKE,
194 TEST
195};
196
197enum
198{
199 SOURCE,
200 SINK
201};
202
203enum
204{
205 ENCODER,
206 DECODER
207};
208
209enum
210{
211 FAIL,
212 OK
213};
214
215enum
216{
217 SPEAKER,
218 MICROPHONE
219};
diff --git a/src/contrib/service/conversation/gnunet_gst_test.c b/src/contrib/service/conversation/gnunet_gst_test.c
new file mode 100644
index 000000000..dd2ef5a38
--- /dev/null
+++ b/src/contrib/service/conversation/gnunet_gst_test.c
@@ -0,0 +1,135 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 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 * @file conversation/gnunet_gst_test.c
22 * @brief FIXME
23 * @author Hark
24 */
25
26#include "platform.h"
27#include "gnunet_gst_def.h"
28#include "gnunet_gst.h"
29
30int
31main (int argc, char *argv[])
32{
33 struct GNUNET_gstData *gst;
34 // GstBus *bus;
35 GstElement *gnunetsrc, *gnunetsink, *source, *sink, *encoder, *decoder;
36
37
38 // audio_message = GNUNET_malloc (UINT16_MAX);
39 // audio_message->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
40
41
42 // GstPipeline *pipeline;
43
44 gst = (GNUNET_gstData *) malloc (sizeof(struct GNUNET_gstData));
45
46 // gst->audio_message.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
47
48
49 gg_load_configuration (gst);
50/*
51 gst->audiobackend = JACK;
52 gst->dropsilence = TRUE;
53 gst->usertp = FALSE;
54 *//* Initialize GStreamer */gst_init (&argc, &argv);
55
56 gst->pipeline = GST_PIPELINE (gst_pipeline_new ("gnunet-media-helper"));
57
58#ifdef IS_SPEAKER
59 int type = SPEAKER;
60 printf ("this is the speaker \n");
61#endif
62#ifdef IS_MIC
63 int type = MICROPHONE;
64 printf ("this is the microphone \n");
65#endif
66 if (type == SPEAKER)
67 {
68 gnunetsrc = GST_ELEMENT (get_app (gst, SOURCE));
69
70 sink = GST_ELEMENT (get_audiobin (gst, SINK));
71 decoder = GST_ELEMENT (get_coder (gst, DECODER));
72 gst_bin_add_many (GST_BIN (gst->pipeline), gnunetsrc, decoder, sink, NULL);
73 gst_element_link_many (gnunetsrc, decoder, sink, NULL);
74 }
75 if (type == MICROPHONE)
76 {
77 source = GST_ELEMENT (get_audiobin (gst, SOURCE));
78
79 encoder = GST_ELEMENT (get_coder (gst, ENCODER));
80
81 gnunetsink = GST_ELEMENT (get_app (gst, SINK));
82
83 gst_bin_add_many (GST_BIN (gst->pipeline), source, encoder, gnunetsink,
84 NULL);
85 gst_element_link_many (source, encoder, gnunetsink, NULL);
86 }
87 /*
88 gst_bin_add_many( GST_BIN(gst->pipeline), appsource, appsink, source, encoder, decoder, sink, NULL);
89 gst_element_link_many( source, encoder, decoder, sink , NULL);
90 */
91 pl_graph (gst->pipeline);
92 /* Start playing */
93 gst_element_set_state (GST_ELEMENT (gst->pipeline), GST_STATE_PLAYING);
94
95 // pl_graph(gst->pipeline);
96
97 /* Wait until error or EOS */
98 // bus = gst_element_get_bus (GST_ELEMENT(gst->pipeline));
99 // bus_watch_id = gst_bus_add_watch (bus, gnunet_gst_bus_call, pipeline);
100
101 gg_setup_gst_bus (gst);
102// g_print ("Running...\n");
103
104
105 // start pushing buffers
106 if (type == MICROPHONE)
107 {
108 GMainLoop *loop;
109 loop = g_main_loop_new (NULL, FALSE);
110
111 g_main_loop_run (loop);
112
113/*
114 while ( 1 )
115 {
116 GstFlowReturn flow;
117 flow = on_appsink_new_sample (gst->appsink, gst);
118 }
119 */}
120 if (type == SPEAKER)
121 {
122 while (1)
123 {
124// printf("read.. \n");
125 gnunet_read (gst);
126 }
127 }
128 g_print ("Returned, stopping playback\n");
129
130 // gst_object_unref (bus);
131 gst_element_set_state (GST_ELEMENT (gst->pipeline), GST_STATE_NULL);
132 gst_object_unref (gst->pipeline);
133
134 return 0;
135}
diff --git a/src/contrib/service/conversation/mediahelper.conf b/src/contrib/service/conversation/mediahelper.conf
new file mode 100644
index 000000000..85c051107
--- /dev/null
+++ b/src/contrib/service/conversation/mediahelper.conf
@@ -0,0 +1,7 @@
1[MEDIAHELPER]
2AUDIOBACKEND = JACK
3REMOVESILENCE = NO
4USERTP = NO
5NO_GN_HEADERS = NO
6JACK_PP_IN = mocp
7JACK_PP_OUT = system
diff --git a/src/contrib/service/conversation/meson.build b/src/contrib/service/conversation/meson.build
new file mode 100644
index 000000000..4ecc0fc93
--- /dev/null
+++ b/src/contrib/service/conversation/meson.build
@@ -0,0 +1,203 @@
1libgnunetconversation_src = ['conversation_api.c', 'conversation_api_call.c']
2
3gnunetserviceconversation_src = ['gnunet-service-conversation.c']
4
5gnunetconversationproxy_src = ['gnunet-conversation-proxy.c']
6
7configure_file(input : 'conversation.conf.in',
8 output : 'conversation.conf',
9 configuration : cdata,
10 install: true,
11 install_dir: pkgcfgdir)
12
13
14if get_option('monolith')
15 foreach p : libgnunetconversation_src + gnunetserviceconversation_src
16 gnunet_src += 'conversation/' + p
17 endforeach
18endif
19
20libgnunetmicrophone = library('gnunetmicrophone',
21 ['microphone.c'],
22 soversion: '0',
23 version: '0.0.0',
24 dependencies: [libgnunetutil_dep],
25 include_directories: [incdir, configuration_inc],
26 install: true,
27 install_dir: get_option('libdir'))
28libgnunetmicrophone_dep = declare_dependency(link_with : libgnunetmicrophone)
29pkg.generate(libgnunetmicrophone, url: 'https://www.gnunet.org',
30 description : 'Provides API to access to microphone')
31
32libgnunetspeaker = library('gnunetspeaker',
33 ['speaker.c'],
34 soversion: '0',
35 version: '0.0.0',
36 dependencies: [libgnunetutil_dep],
37 include_directories: [incdir, configuration_inc],
38 install: true,
39 install_dir: get_option('libdir'))
40libgnunetspeaker_dep = declare_dependency(link_with : libgnunetspeaker)
41pkg.generate(libgnunetspeaker, url: 'https://www.gnunet.org',
42 description : 'Provides API to access to speaker')
43
44libgnunetconversation = library('gnunetconversation',
45 libgnunetconversation_src,
46 soversion: '0',
47 version: '0.0.0',
48 dependencies: [libgnunetutil_dep,
49 libgnunetgnsrecord_dep,
50 libgnunetgns_dep,
51 libgnunetnamestore_dep,
52 libgnunetidentity_dep],
53 include_directories: [incdir, configuration_inc],
54 install: true,
55 install_dir: get_option('libdir'))
56libgnunetconversation_dep = declare_dependency(link_with : libgnunetconversation)
57pkg.generate(libgnunetconversation, url: 'https://www.gnunet.org',
58 description : 'Provides API to access to conversation')
59
60shared_module('gnunet_plugin_gnsrecord_conversation',
61 ['plugin_gnsrecord_conversation.c'],
62 dependencies: [libgnunetutil_dep,
63 libgnunetconversation_dep,
64 libgnunetgns_dep,
65 libgnunetgnsrecord_dep,
66 libgnunetidentity_dep],
67 include_directories: [incdir, configuration_inc],
68 install: true,
69 install_dir: get_option('libdir')/'gnunet')
70
71executable ('gnunet-conversation',
72 'gnunet-conversation.c',
73 dependencies: [libgnunetconversation_dep,
74 libgnunetgns_dep,
75 libgnunetgnsrecord_dep,
76 libgnunetnamestore_dep,
77 libgnunetspeaker_dep,
78 libgnunetmicrophone_dep,
79 libgnunetidentity_dep,
80 libgnunetutil_dep],
81 include_directories: [incdir, configuration_inc],
82 install: true,
83 install_dir: get_option('bindir'))
84
85executable ('gnunet-conversation-test',
86 'gnunet-conversation-test.c',
87 dependencies: [libgnunetconversation_dep,
88 libgnunetspeaker_dep,
89 libgnunetmicrophone_dep,
90 libgnunetutil_dep],
91 include_directories: [incdir, configuration_inc],
92 install: true,
93 install_dir: get_option('bindir'))
94
95executable ('gnunet-service-conversation',
96 gnunetserviceconversation_src,
97 dependencies: [libgnunetconversation_dep,
98 libgnunetutil_dep,
99 libgnunetspeaker_dep,
100 libgnunetmicrophone_dep,
101 libgnunetidentity_dep,
102 libgnunetcadet_dep],
103 include_directories: [incdir, configuration_inc],
104 install: true,
105 install_dir: get_option('libdir') / 'gnunet' / 'libexec')
106
107helperrecord_src = ['gnunet-helper-audio-record.c']
108helperplayback_src = ['gnunet-helper-audio-record.c']
109if conversation_backend == 'gst'
110 helperrecord_src = ['gnunet-helper-audio-record-gst.c']
111 helperplayback_src = ['gnunet-helper-audio-playback-gst.c']
112endif
113
114executable ('gnunet-helper-audio-record',
115 helperrecord_src,
116 dependencies: [libgnunetconversation_dep,
117 libgnunetutil_dep,
118 libgnunetspeaker_dep,
119 libgnunetmicrophone_dep,
120 gst_dep,
121 gst_app_dep,
122 gst_audio_dep,
123 ogg_dep,
124 pulse_dep,
125 opus_dep
126 ],
127 include_directories: [incdir, configuration_inc],
128 install: true,
129 install_dir: get_option('libdir') / 'gnunet' / 'libexec')
130
131executable ('gnunet-helper-audio-playback',
132 helperplayback_src,
133 dependencies: [libgnunetconversation_dep,
134 libgnunetutil_dep,
135 libgnunetspeaker_dep,
136 libgnunetmicrophone_dep,
137 gst_dep,
138 gst_app_dep,
139 gst_audio_dep,
140 ogg_dep,
141 pulse_dep,
142 opus_dep
143 ],
144 include_directories: [incdir, configuration_inc],
145 install: true,
146 install_dir: get_option('libdir') / 'gnunet' / 'libexec')
147
148testconvapi = executable ('test_conversation_api',
149 ['test_conversation_api.c'],
150 dependencies: [libgnunetconversation_dep,
151 libgnunetutil_dep,
152 libgnunetidentity_dep,
153 libgnunettesting_dep,
154 libgnunetgnsrecord_dep,
155 libgnunetnamestore_dep,
156 libgnunetspeaker_dep,
157 libgnunetmicrophone_dep,
158 ],
159 include_directories: [incdir, configuration_inc],
160 install: false)
161
162testconvapitwo = executable ('test_conversation_api_twocalls',
163 ['test_conversation_api_twocalls.c'],
164 dependencies: [libgnunetconversation_dep,
165 libgnunetutil_dep,
166 libgnunetidentity_dep,
167 libgnunettesting_dep,
168 libgnunetgnsrecord_dep,
169 libgnunetnamestore_dep,
170 libgnunetspeaker_dep,
171 libgnunetmicrophone_dep,
172 ],
173 include_directories: [incdir, configuration_inc],
174 install: false)
175
176testconvapireject = executable ('test_conversation_api_reject',
177 ['test_conversation_api_reject.c'],
178 dependencies: [libgnunetconversation_dep,
179 libgnunetutil_dep,
180 libgnunetidentity_dep,
181 libgnunettesting_dep,
182 libgnunetgnsrecord_dep,
183 libgnunetnamestore_dep,
184 libgnunetspeaker_dep,
185 libgnunetmicrophone_dep,
186 ],
187 include_directories: [incdir, configuration_inc],
188 install: false)
189
190configure_file(input : 'test_conversation.conf',
191 output : 'test_conversation.conf',
192 copy: true,
193 install: false)
194
195test('test_conversation_api', testconvapi, workdir: meson.current_build_dir(),
196 suite: ['conversation', 'contrib'])
197
198test('test_conversation_api_twocalls', testconvapitwo, workdir: meson.current_build_dir(),
199 suite: ['conversation', 'contrib'])
200
201test('test_conversation_api_reject', testconvapireject, workdir: meson.current_build_dir(),
202 suite: ['conversation', 'contrib'])
203
diff --git a/src/contrib/service/conversation/microphone.c b/src/contrib/service/conversation/microphone.c
new file mode 100644
index 000000000..a4a40796f
--- /dev/null
+++ b/src/contrib/service/conversation/microphone.c
@@ -0,0 +1,200 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013 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/microphone.c
23 * @brief API to access an audio microphone; provides access to hardware microphones;
24 * actually just wraps the gnunet-helper-audio-record
25 * @author Simon Dieterle
26 * @author Andreas Fuchs
27 * @author Christian Grothoff
28 */
29#include "platform.h"
30#include "gnunet_microphone_lib.h"
31#include "conversation.h"
32
33
34/**
35 * Internal data structures for the microphone.
36 */
37struct Microphone
38{
39 /**
40 * Our configuration.
41 */
42 const struct GNUNET_CONFIGURATION_Handle *cfg;
43
44 /**
45 * Handle for the record helper
46 */
47 struct GNUNET_HELPER_Handle *record_helper;
48
49 /**
50 * Function to call with audio data (if we are enabled).
51 */
52 GNUNET_MICROPHONE_RecordedDataCallback rdc;
53
54 /**
55 * Closure for @e rdc.
56 */
57 void *rdc_cls;
58};
59
60
61/**
62 * Function to process the audio from the record helper
63 *
64 * @param cls clsoure with our `struct Microphone`
65 * @param msg the message from the helper
66 * @return #GNUNET_OK on success,
67 * #GNUNET_NO to stop further processing (no error)
68 * #GNUNET_SYSERR to stop further processing with error
69 */
70static int
71process_record_messages (void *cls,
72 const struct GNUNET_MessageHeader *msg)
73{
74 struct Microphone *mic = cls;
75 const struct AudioMessage *am;
76
77 if (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO)
78 {
79 GNUNET_break (0);
80 return GNUNET_SYSERR;
81 }
82 am = (const struct AudioMessage *) msg;
83 mic->rdc (mic->rdc_cls,
84 ntohs (msg->size) - sizeof(struct AudioMessage),
85 &am[1]);
86 return GNUNET_OK;
87}
88
89
90/**
91 * Enable a microphone.
92 *
93 * @param cls clsoure with our `struct Microphone`
94 * @param rdc function to call with recorded data
95 * @param rdc_cls closure for @a dc
96 */
97static int
98enable (void *cls,
99 GNUNET_MICROPHONE_RecordedDataCallback rdc,
100 void *rdc_cls)
101{
102 struct Microphone *mic = cls;
103 static char *const record_helper_argv[] = {
104 "gnunet-helper-audio-record",
105 NULL
106 };
107
108 mic->rdc = rdc;
109 mic->rdc_cls = rdc_cls;
110 mic->record_helper = GNUNET_HELPER_start (GNUNET_NO,
111 "gnunet-helper-audio-record",
112 record_helper_argv,
113 &process_record_messages,
114 NULL, mic);
115 if (NULL == mic->record_helper)
116 {
117 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
118 _ ("Could not start record audio helper\n"));
119 return GNUNET_SYSERR;
120 }
121 return GNUNET_OK;
122}
123
124
125/**
126 * Function that disables a microphone.
127 *
128 * @param cls clsoure
129 */
130static void
131disable (void *cls)
132{
133 struct Microphone *mic = cls;
134
135 if (NULL == mic->record_helper)
136 {
137 GNUNET_break (0);
138 return;
139 }
140 GNUNET_break (GNUNET_OK ==
141 GNUNET_HELPER_kill (mic->record_helper, GNUNET_NO));
142 GNUNET_HELPER_destroy (mic->record_helper);
143 mic->record_helper = NULL;
144}
145
146
147/**
148 * Function to destroy a microphone.
149 *
150 * @param cls clsoure
151 */
152static void
153destroy (void *cls)
154{
155 struct Microphone *mic = cls;
156
157 if (NULL != mic->record_helper)
158 disable (mic);
159}
160
161
162/**
163 * Create a microphone that corresponds to the microphone hardware
164 * of our system.
165 *
166 * @param cfg configuration to use
167 * @return NULL on error
168 */
169struct GNUNET_MICROPHONE_Handle *
170GNUNET_MICROPHONE_create_from_hardware (const struct
171 GNUNET_CONFIGURATION_Handle *cfg)
172{
173 struct GNUNET_MICROPHONE_Handle *microphone;
174 struct Microphone *mic;
175
176 mic = GNUNET_new (struct Microphone);
177 mic->cfg = cfg;
178 microphone = GNUNET_new (struct GNUNET_MICROPHONE_Handle);
179 microphone->cls = mic;
180 microphone->enable_microphone = &enable;
181 microphone->disable_microphone = &disable;
182 microphone->destroy_microphone = &destroy;
183 return microphone;
184}
185
186
187/**
188 * Destroy a microphone.
189 *
190 * @param microphone microphone to destroy
191 */
192void
193GNUNET_MICROPHONE_destroy (struct GNUNET_MICROPHONE_Handle *microphone)
194{
195 microphone->destroy_microphone (microphone->cls);
196 GNUNET_free (microphone);
197}
198
199
200/* end of microphone.c */
diff --git a/src/contrib/service/conversation/plugin_gnsrecord_conversation.c b/src/contrib/service/conversation/plugin_gnsrecord_conversation.c
new file mode 100644
index 000000000..802732cfe
--- /dev/null
+++ b/src/contrib/service/conversation/plugin_gnsrecord_conversation.c
@@ -0,0 +1,262 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013 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/plugin_gnsrecord_conversation.c
23 * @brief gnsrecord plugin to provide the API for fundamental GNS records
24 * This includes the VPN record because GNS resolution
25 * is expected to understand VPN records and (if needed)
26 * map the result to A/AAAA.
27 * @author Christian Grothoff
28 */
29
30#include "platform.h"
31#include "gnunet_util_lib.h"
32#include "gnunet_gnsrecord_lib.h"
33#include "gnunet_conversation_service.h"
34#include "gnunet_gnsrecord_plugin.h"
35
36
37/**
38 * Convert the 'value' of a record to a string.
39 *
40 * @param cls closure, unused
41 * @param type type of the record
42 * @param data value in binary encoding
43 * @param data_size number of bytes in @a data
44 * @return NULL on error, otherwise human-readable representation of the value
45 */
46static char *
47conversation_value_to_string (void *cls,
48 uint32_t type,
49 const void *data,
50 size_t data_size)
51{
52 char *s;
53
54 (void) cls;
55 switch (type)
56 {
57 case GNUNET_GNSRECORD_TYPE_PHONE:
58 {
59 const struct GNUNET_CONVERSATION_PhoneRecord *pr;
60 char *ret;
61 char *pkey;
62
63 if (data_size != sizeof(struct GNUNET_CONVERSATION_PhoneRecord))
64 {
65 GNUNET_break_op (0);
66 return NULL;
67 }
68 pr = data;
69 if (1 != ntohl (pr->version))
70 {
71 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
72 _ ("PHONE version %u not supported\n"),
73 ntohl (pr->version));
74 return NULL;
75 }
76 pkey = GNUNET_CRYPTO_eddsa_public_key_to_string (&pr->peer.public_key);
77 s = GNUNET_STRINGS_data_to_string_alloc (&pr->line_port,
78 sizeof(struct GNUNET_HashCode));
79
80 GNUNET_asprintf (&ret,
81 "%s-%s",
82 s,
83 pkey);
84 GNUNET_free (s);
85 GNUNET_free (pkey);
86 return ret;
87 }
88
89 default:
90 return NULL;
91 }
92}
93
94
95/**
96 * Convert human-readable version of a 'value' of a record to the binary
97 * representation.
98 *
99 * @param cls closure, unused
100 * @param type type of the record
101 * @param s human-readable string
102 * @param data set to value in binary encoding (will be allocated)
103 * @param data_size set to number of bytes in @a data
104 * @return #GNUNET_OK on success
105 */
106static int
107conversation_string_to_value (void *cls,
108 uint32_t type,
109 const char *s,
110 void **data,
111 size_t *data_size)
112{
113 (void) cls;
114 if (NULL == s)
115 {
116 GNUNET_break (0);
117 return GNUNET_SYSERR;
118 }
119 switch (type)
120 {
121 case GNUNET_GNSRECORD_TYPE_PHONE:
122 {
123 struct GNUNET_CONVERSATION_PhoneRecord *pr;
124 char line_port[104];
125 const char *dash;
126 struct GNUNET_PeerIdentity peer;
127
128 if ((NULL == (dash = strchr (s, '-'))) ||
129 (1 != sscanf (s, "%103s-", line_port)) ||
130 (GNUNET_OK !=
131 GNUNET_CRYPTO_eddsa_public_key_from_string (dash + 1,
132 strlen (dash + 1),
133 &peer.public_key)))
134 {
135 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
136 _ ("Unable to parse PHONE record `%s'\n"),
137 s);
138 return GNUNET_SYSERR;
139 }
140 pr = GNUNET_new (struct GNUNET_CONVERSATION_PhoneRecord);
141 pr->version = htonl (1);
142 pr->reserved = htonl (0);
143 if (GNUNET_OK !=
144 GNUNET_STRINGS_string_to_data (line_port,
145 strlen (line_port),
146 &pr->line_port,
147 sizeof(struct GNUNET_HashCode)))
148 {
149 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
150 _ ("Unable to parse PHONE record `%s'\n"),
151 s);
152 GNUNET_free (pr);
153 return GNUNET_SYSERR;
154 }
155 pr->peer = peer;
156 *data = pr;
157 *data_size = sizeof(struct GNUNET_CONVERSATION_PhoneRecord);
158 return GNUNET_OK;
159 }
160
161 default:
162 return GNUNET_SYSERR;
163 }
164}
165
166
167/**
168 * Mapping of record type numbers to human-readable
169 * record type names.
170 */
171static struct
172{
173 const char *name;
174 uint32_t number;
175} name_map[] = {
176 { "PHONE", GNUNET_GNSRECORD_TYPE_PHONE },
177 { NULL, UINT32_MAX }
178};
179
180
181/**
182 * Convert a type name (e.g. "AAAA") to the corresponding number.
183 *
184 * @param cls closure, unused
185 * @param gns_typename name to convert
186 * @return corresponding number, UINT32_MAX on error
187 */
188static uint32_t
189conversation_typename_to_number (void *cls,
190 const char *gns_typename)
191{
192 unsigned int i;
193
194 (void) cls;
195 i = 0;
196 while ((name_map[i].name != NULL) &&
197 (0 != strcasecmp (gns_typename, name_map[i].name)))
198 i++;
199 return name_map[i].number;
200}
201
202
203/**
204 * Convert a type number to the corresponding type string (e.g. 1 to "A")
205 *
206 * @param cls closure, unused
207 * @param type number of a type to convert
208 * @return corresponding typestring, NULL on error
209 */
210static const char *
211conversation_number_to_typename (void *cls,
212 uint32_t type)
213{
214 unsigned int i;
215
216 (void) cls;
217 i = 0;
218 while ((name_map[i].name != NULL) &&
219 (type != name_map[i].number))
220 i++;
221 return name_map[i].name;
222}
223
224
225/**
226 * Entry point for the plugin.
227 *
228 * @param cls NULL
229 * @return the exported block API
230 */
231void *
232libgnunet_plugin_gnsrecord_conversation_init (void *cls)
233{
234 struct GNUNET_GNSRECORD_PluginFunctions *api;
235
236 (void) cls;
237 api = GNUNET_new (struct GNUNET_GNSRECORD_PluginFunctions);
238 api->value_to_string = &conversation_value_to_string;
239 api->string_to_value = &conversation_string_to_value;
240 api->typename_to_number = &conversation_typename_to_number;
241 api->number_to_typename = &conversation_number_to_typename;
242 return api;
243}
244
245
246/**
247 * Exit point from the plugin.
248 *
249 * @param cls the return value from #libgnunet_plugin_block_test_init
250 * @return NULL
251 */
252void *
253libgnunet_plugin_gnsrecord_conversation_done (void *cls)
254{
255 struct GNUNET_GNSRECORD_PluginFunctions *api = cls;
256
257 GNUNET_free (api);
258 return NULL;
259}
260
261
262/* end of plugin_gnsrecord_conversation.c */
diff --git a/src/contrib/service/conversation/speaker.c b/src/contrib/service/conversation/speaker.c
new file mode 100644
index 000000000..38eb1159c
--- /dev/null
+++ b/src/contrib/service/conversation/speaker.c
@@ -0,0 +1,189 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013 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/speaker.c
23 * @brief API to access an audio speaker; provides access to hardware speakers
24 * @author Simon Dieterle
25 * @author Andreas Fuchs
26 * @author Christian Grothoff
27 */
28#include "platform.h"
29#include "gnunet_speaker_lib.h"
30#include "conversation.h"
31
32
33/**
34 * Internal data structures for the speaker.
35 */
36struct Speaker
37{
38 /**
39 * Our configuration.
40 */
41 const struct GNUNET_CONFIGURATION_Handle *cfg;
42
43 /**
44 * Handle for the playback helper
45 */
46 struct GNUNET_HELPER_Handle *playback_helper;
47};
48
49
50/**
51 * Function that enables a speaker.
52 *
53 * @param cls closure with the `struct Speaker`
54 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
55 */
56static int
57enable (void *cls)
58{
59 struct Speaker *spe = cls;
60 static char *playback_helper_argv[] = {
61 "gnunet-helper-audio-playback",
62 NULL
63 };
64
65 spe->playback_helper = GNUNET_HELPER_start (GNUNET_NO,
66 "gnunet-helper-audio-playback",
67 playback_helper_argv,
68 NULL,
69 NULL, spe);
70 if (NULL == spe->playback_helper)
71 {
72 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
73 _ ("Could not start playback audio helper.\n"));
74 return GNUNET_SYSERR;
75 }
76 return GNUNET_OK;
77}
78
79
80/**
81 * Function that disables a speaker.
82 *
83 * @param cls closure with the `struct Speaker`
84 */
85static void
86disable (void *cls)
87{
88 struct Speaker *spe = cls;
89
90 if (NULL == spe->playback_helper)
91 {
92 GNUNET_break (0);
93 return;
94 }
95 GNUNET_break (GNUNET_OK ==
96 GNUNET_HELPER_kill (spe->playback_helper, GNUNET_NO));
97 GNUNET_HELPER_destroy (spe->playback_helper);
98 spe->playback_helper = NULL;
99}
100
101
102/**
103 * Function to destroy a speaker.
104 *
105 * @param cls closure with the `struct Speaker`
106 */
107static void
108destroy (void *cls)
109{
110 struct Speaker *spe = cls;
111
112 if (NULL != spe->playback_helper)
113 disable (spe);
114}
115
116
117/**
118 * Function to cause a speaker to play audio data.
119 *
120 * @param cls clsoure with the `struct Speaker`
121 * @param data_size number of bytes in @a data
122 * @param data audio data to play, format is
123 * opaque to the API but should be OPUS.
124 */
125static void
126play (void *cls,
127 size_t data_size,
128 const void *data)
129{
130 struct Speaker *spe = cls;
131 char buf[sizeof(struct AudioMessage) + data_size];
132 struct AudioMessage *am;
133
134 if (NULL == spe->playback_helper)
135 {
136 GNUNET_break (0);
137 return;
138 }
139 am = (struct AudioMessage *) buf;
140 am->header.size = htons (sizeof(struct AudioMessage) + data_size);
141 am->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
142 GNUNET_memcpy (&am[1], data, data_size);
143 (void) GNUNET_HELPER_send (spe->playback_helper,
144 &am->header,
145 GNUNET_NO,
146 NULL, NULL);
147}
148
149
150/**
151 * Create a speaker that corresponds to the speaker hardware
152 * of our system.
153 *
154 * @param cfg configuration to use
155 * @return NULL on error
156 */
157struct GNUNET_SPEAKER_Handle *
158GNUNET_SPEAKER_create_from_hardware (const struct
159 GNUNET_CONFIGURATION_Handle *cfg)
160{
161 struct GNUNET_SPEAKER_Handle *speaker;
162 struct Speaker *spe;
163
164 spe = GNUNET_new (struct Speaker);
165 spe->cfg = cfg;
166 speaker = GNUNET_new (struct GNUNET_SPEAKER_Handle);
167 speaker->cls = spe;
168 speaker->enable_speaker = &enable;
169 speaker->play = &play;
170 speaker->disable_speaker = &disable;
171 speaker->destroy_speaker = &destroy;
172 return speaker;
173}
174
175
176/**
177 * Destroy a speaker.
178 *
179 * @param speaker speaker to destroy
180 */
181void
182GNUNET_SPEAKER_destroy (struct GNUNET_SPEAKER_Handle *speaker)
183{
184 speaker->destroy_speaker (speaker->cls);
185 GNUNET_free (speaker);
186}
187
188
189/* end of speaker.c */
diff --git a/src/contrib/service/conversation/test.sh b/src/contrib/service/conversation/test.sh
new file mode 100644
index 000000000..20e82bc88
--- /dev/null
+++ b/src/contrib/service/conversation/test.sh
@@ -0,0 +1,4 @@
1#!/bin/sh
2
3export GST_DEBUG_DUMP_DOT_DIR=/tmp/
4GST_DEBUG_DUMP_DOT_DIR=/tmp/ ./gnunet-helper-audio-record |GST_DEBUG_DUMP_DOT_DIR=/tmp/ ./gnunet-helper-audio-playback
diff --git a/src/contrib/service/conversation/test_conversation.conf b/src/contrib/service/conversation/test_conversation.conf
new file mode 100644
index 000000000..097aed592
--- /dev/null
+++ b/src/contrib/service/conversation/test_conversation.conf
@@ -0,0 +1,12 @@
1@INLINE@ ../../../../contrib/conf/gnunet/no_forcestart.conf
2
3[conversation]
4LINE=1
5#PREFIX = valgrind
6
7[zonemaster]
8IMMEDIATE_START = YES
9START_ON_DEMAND = YES
10
11[nse]
12WORKBITS = 0
diff --git a/src/contrib/service/conversation/test_conversation_api.c b/src/contrib/service/conversation/test_conversation_api.c
new file mode 100644
index 000000000..decc40b64
--- /dev/null
+++ b/src/contrib/service/conversation/test_conversation_api.c
@@ -0,0 +1,510 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013, 2014, 2018 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 * @file conversation/test_conversation_api.c
22 * @brief testcase for conversation_api.c
23 *
24 * This test performs the operations of a call to a phone
25 * where the phone user picks up and then the call is
26 * terminated by the party that initiated the call.
27 */
28#include "platform.h"
29#include "gnunet_util_lib.h"
30#include "gnunet_testing_lib.h"
31#include "gnunet_gnsrecord_lib.h"
32#include "gnunet_conversation_service.h"
33#include "gnunet_identity_service.h"
34#include "gnunet_namestore_service.h"
35
36#define FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 250)
37
38#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 25)
39
40static int ok = 1;
41
42static const struct GNUNET_CONFIGURATION_Handle *cfg;
43
44static struct GNUNET_IDENTITY_Handle *id;
45
46static struct GNUNET_IDENTITY_Operation *op;
47
48static struct GNUNET_CONVERSATION_Phone *phone;
49
50static struct GNUNET_NAMESTORE_Handle *ns;
51
52static struct GNUNET_CONVERSATION_Call *call;
53
54static struct GNUNET_NAMESTORE_QueueEntry *qe;
55
56static struct GNUNET_CONVERSATION_Caller *active_caller;
57
58static char *gns_name;
59
60static char *gns_caller_id;
61
62static GNUNET_MICROPHONE_RecordedDataCallback phone_rdc;
63
64static void *phone_rdc_cls;
65
66static GNUNET_MICROPHONE_RecordedDataCallback call_rdc;
67
68static void *call_rdc_cls;
69
70static struct GNUNET_SCHEDULER_Task *phone_task;
71
72static struct GNUNET_SCHEDULER_Task *call_task;
73
74
75static void
76phone_send (void *cls)
77{
78 static unsigned int i;
79 char buf[32];
80
81 (void) cls;
82 GNUNET_assert (NULL != phone_rdc);
83 GNUNET_snprintf (buf, sizeof(buf), "phone-%u", i++);
84 phone_rdc (phone_rdc_cls, strlen (buf) + 1, buf);
85 phone_task = GNUNET_SCHEDULER_add_delayed (FREQ, &phone_send, NULL);
86}
87
88
89static void
90call_send (void *cls)
91{
92 static unsigned int i;
93 char buf[32];
94
95 (void) cls;
96 GNUNET_assert (NULL != call_rdc);
97 GNUNET_snprintf (buf, sizeof(buf), "call-%u", i++);
98 call_rdc (call_rdc_cls, strlen (buf) + 1, buf);
99 call_task = GNUNET_SCHEDULER_add_delayed (FREQ, &call_send, NULL);
100}
101
102
103static int
104enable_speaker (void *cls)
105{
106 const char *origin = cls;
107
108 fprintf (stderr, "Speaker %s enabled\n", origin);
109 return GNUNET_OK;
110}
111
112
113static void
114disable_speaker (void *cls)
115{
116 const char *origin = cls;
117
118 fprintf (stderr, "Speaker %s disabled\n", origin);
119}
120
121
122static void
123play (void *cls, size_t data_size, const void *data)
124{
125 const char *origin = cls;
126 static unsigned int phone_i = 1;
127 static unsigned int call_i;
128 char buf[32];
129
130 if (0 == strcmp (origin, "phone"))
131 GNUNET_snprintf (buf, sizeof(buf), "call-%u", call_i++);
132 else
133 GNUNET_snprintf (buf, sizeof(buf), "phone-%u", phone_i++);
134 if ((data_size != strlen (buf) + 1) || (0 != strncmp (buf, data, data_size)))
135 {
136 fprintf (stderr,
137 "Expected %s, received %.*s\n",
138 buf,
139 (int) data_size,
140 (const char *) data);
141 }
142 else
143 {
144 fprintf (stderr, ".");
145 }
146 if ((20 < call_i) && (20 < phone_i) && (NULL != call))
147 {
148 /* time to hang up ... */
149 GNUNET_CONVERSATION_call_stop (call);
150 call = NULL;
151 }
152}
153
154
155static void
156destroy_speaker (void *cls)
157{
158 const char *origin = cls;
159
160 fprintf (stderr, "Speaker %s destroyed\n", origin);
161}
162
163
164static struct GNUNET_SPEAKER_Handle call_speaker = { &enable_speaker,
165 &play,
166 &disable_speaker,
167 &destroy_speaker,
168 "caller" };
169
170
171static struct GNUNET_SPEAKER_Handle phone_speaker = { &enable_speaker,
172 &play,
173 &disable_speaker,
174 &destroy_speaker,
175 "phone" };
176
177
178static int
179enable_mic (void *cls,
180 GNUNET_MICROPHONE_RecordedDataCallback rdc,
181 void *rdc_cls)
182{
183 const char *origin = cls;
184
185 fprintf (stderr, "Mic %s enabled\n", origin);
186 if (0 == strcmp (origin, "phone"))
187 {
188 phone_rdc = rdc;
189 phone_rdc_cls = rdc_cls;
190 phone_task = GNUNET_SCHEDULER_add_now (&phone_send, NULL);
191 }
192 else
193 {
194 call_rdc = rdc;
195 call_rdc_cls = rdc_cls;
196 call_task = GNUNET_SCHEDULER_add_now (&call_send, NULL);
197 }
198 return GNUNET_OK;
199}
200
201
202static void
203disable_mic (void *cls)
204{
205 const char *origin = cls;
206
207 fprintf (stderr, "Mic %s disabled\n", origin);
208 if (0 == strcmp (origin, "phone"))
209 {
210 phone_rdc = NULL;
211 phone_rdc_cls = NULL;
212 GNUNET_SCHEDULER_cancel (phone_task);
213 phone_task = NULL;
214 }
215 else
216 {
217 call_rdc = NULL;
218 call_rdc_cls = NULL;
219 GNUNET_SCHEDULER_cancel (call_task);
220 call_task = NULL;
221 }
222}
223
224
225static void
226destroy_mic (void *cls)
227{
228 const char *origin = cls;
229
230 fprintf (stderr, "Mic %s destroyed\n", origin);
231}
232
233
234static struct GNUNET_MICROPHONE_Handle call_mic = { &enable_mic,
235 &disable_mic,
236 &destroy_mic,
237 "caller" };
238
239
240static struct GNUNET_MICROPHONE_Handle phone_mic = { &enable_mic,
241 &disable_mic,
242 &destroy_mic,
243 "phone" };
244
245
246/**
247 * Signature of the main function of a task.
248 *
249 * @param cls closure
250 */
251static void
252end_test (void *cls)
253{
254 (void) cls;
255 GNUNET_SCHEDULER_shutdown ();
256 if (NULL != op)
257 {
258 GNUNET_IDENTITY_cancel (op);
259 op = NULL;
260 }
261 if (NULL != call)
262 {
263 GNUNET_CONVERSATION_call_stop (call);
264 call = NULL;
265 }
266 if (NULL != phone)
267 {
268 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from PHONE service.\n");
269 GNUNET_CONVERSATION_phone_destroy (phone);
270 phone = NULL;
271 }
272 if (NULL != id)
273 {
274 GNUNET_IDENTITY_disconnect (id);
275 id = NULL;
276 }
277 if (NULL != qe)
278 {
279 GNUNET_NAMESTORE_cancel (qe);
280 qe = NULL;
281 }
282 if (NULL != ns)
283 {
284 GNUNET_NAMESTORE_disconnect (ns);
285 ns = NULL;
286 }
287}
288
289
290static void
291caller_event_handler (void *cls, enum GNUNET_CONVERSATION_CallerEventCode code)
292{
293 (void) cls;
294 switch (code)
295 {
296 case GNUNET_CONVERSATION_EC_CALLER_SUSPEND:
297 case GNUNET_CONVERSATION_EC_CALLER_RESUME:
298 fprintf (stderr, "Unexpected caller code: %d\n", code);
299 break;
300 }
301}
302
303
304static void
305phone_event_handler (void *cls,
306 enum GNUNET_CONVERSATION_PhoneEventCode code,
307 struct GNUNET_CONVERSATION_Caller *caller,
308 const struct GNUNET_CRYPTO_PublicKey *caller_id)
309{
310 static enum GNUNET_CONVERSATION_PhoneEventCode expect =
311 GNUNET_CONVERSATION_EC_PHONE_RING;
312
313 (void) cls;
314 (void) caller_id;
315 GNUNET_break (code == expect);
316 switch (code)
317 {
318 case GNUNET_CONVERSATION_EC_PHONE_RING:
319 active_caller = caller;
320 GNUNET_CONVERSATION_caller_pick_up (caller,
321 &caller_event_handler,
322 NULL,
323 &phone_speaker,
324 &phone_mic);
325 expect = GNUNET_CONVERSATION_EC_PHONE_HUNG_UP;
326 break;
327
328 case GNUNET_CONVERSATION_EC_PHONE_HUNG_UP:
329 GNUNET_break (caller == active_caller);
330 active_caller = NULL;
331 if (1 == ok)
332 ok = 0;
333 GNUNET_SCHEDULER_shutdown ();
334 break;
335
336 default:
337 fprintf (stderr, "Unexpected phone code: %d\n", code);
338 break;
339 }
340}
341
342
343static void
344call_event_handler (void *cls, enum GNUNET_CONVERSATION_CallEventCode code)
345{
346 static enum GNUNET_CONVERSATION_CallEventCode expect =
347 GNUNET_CONVERSATION_EC_CALL_RINGING;
348
349 (void) cls;
350 GNUNET_break (code == expect);
351 switch (code)
352 {
353 case GNUNET_CONVERSATION_EC_CALL_RINGING:
354 expect = GNUNET_CONVERSATION_EC_CALL_PICKED_UP;
355 break;
356
357 case GNUNET_CONVERSATION_EC_CALL_PICKED_UP:
358 expect = -1;
359 break;
360
361 case GNUNET_CONVERSATION_EC_CALL_GNS_FAIL:
362 case GNUNET_CONVERSATION_EC_CALL_HUNG_UP:
363 call = NULL;
364 ok = 2;
365 GNUNET_break (0);
366 fprintf (stderr, "Unexpected call code: %d\n", code);
367 break;
368
369 case GNUNET_CONVERSATION_EC_CALL_SUSPENDED:
370 case GNUNET_CONVERSATION_EC_CALL_RESUMED:
371 GNUNET_break (0);
372 fprintf (stderr, "Unexpected call code: %d\n", code);
373 ok = 2;
374 break;
375
376 case GNUNET_CONVERSATION_EC_CALL_ERROR:
377 GNUNET_break (0);
378 fprintf (stderr, "Unexpected call code: %d\n", code);
379 call = NULL;
380 ok = 2;
381 break;
382 }
383}
384
385
386static void
387caller_ego_create_cont (void *cls,
388 const struct GNUNET_CRYPTO_PrivateKey *pk,
389 enum GNUNET_ErrorCode ec)
390{
391 (void) cls;
392 op = NULL;
393 GNUNET_assert (GNUNET_EC_NONE == ec);
394}
395
396
397static void
398namestore_put_cont (void *cls, enum GNUNET_ErrorCode ec)
399{
400 (void) cls;
401 qe = NULL;
402 GNUNET_assert (GNUNET_EC_NONE == ec);
403 GNUNET_assert (NULL == op);
404 op = GNUNET_IDENTITY_create (id, "caller-ego", NULL,
405 GNUNET_PUBLIC_KEY_TYPE_ECDSA,
406 &caller_ego_create_cont,
407 NULL);
408}
409
410
411static void
412identity_cb (void *cls,
413 struct GNUNET_IDENTITY_Ego *ego,
414 void **ctx,
415 const char *name)
416{
417 struct GNUNET_GNSRECORD_Data rd;
418 struct GNUNET_CRYPTO_PublicKey pub;
419
420 (void) cls;
421 (void) ctx;
422 if (NULL == name)
423 return;
424 if (NULL == ego)
425 return;
426 if (0 == strcmp (name, "phone-ego"))
427 {
428 GNUNET_IDENTITY_ego_get_public_key (ego, &pub);
429 GNUNET_asprintf (&gns_name,
430 "phone.%s",
431 GNUNET_GNSRECORD_pkey_to_zkey (&pub));
432 phone =
433 GNUNET_CONVERSATION_phone_create (cfg, ego, &phone_event_handler, NULL);
434 GNUNET_assert (NULL != phone);
435 memset (&rd, 0, sizeof(rd));
436 GNUNET_CONVERSATION_phone_get_record (phone, &rd);
437 GNUNET_assert (rd.record_type == GNUNET_GNSRECORD_TYPE_PHONE);
438 rd.expiration_time = UINT64_MAX;
439 qe =
440 GNUNET_NAMESTORE_record_set_store (ns,
441 GNUNET_IDENTITY_ego_get_private_key (ego),
442 "phone" /* GNS label */,
443 1,
444 &rd,
445 &namestore_put_cont,
446 NULL);
447 return;
448 }
449 if (0 == strcmp (name, "caller-ego"))
450 {
451 GNUNET_IDENTITY_ego_get_public_key (ego, &pub);
452 GNUNET_asprintf (&gns_caller_id,
453 "%s",
454 GNUNET_GNSRECORD_pkey_to_zkey (&pub));
455 call = GNUNET_CONVERSATION_call_start (cfg,
456 ego,
457 gns_name,
458 &call_speaker,
459 &call_mic,
460 &call_event_handler,
461 NULL);
462 return;
463 }
464}
465
466
467static void
468phone_ego_create_cont (void *cls,
469 const struct GNUNET_CRYPTO_PrivateKey *pk,
470 enum GNUNET_ErrorCode ec)
471{
472 (void) cls;
473 op = NULL;
474 GNUNET_assert (GNUNET_EC_NONE == ec);
475}
476
477
478static void
479run (void *cls,
480 const struct GNUNET_CONFIGURATION_Handle *c,
481 struct GNUNET_TESTING_Peer *peer)
482{
483 (void) cls;
484 (void) peer;
485 cfg = c;
486 GNUNET_SCHEDULER_add_delayed (TIMEOUT, &end_test, NULL);
487 id = GNUNET_IDENTITY_connect (cfg, &identity_cb, NULL);
488 op = GNUNET_IDENTITY_create (id, "phone-ego", NULL,
489 GNUNET_PUBLIC_KEY_TYPE_ECDSA,
490 &phone_ego_create_cont,
491 NULL);
492 ns = GNUNET_NAMESTORE_connect (cfg);
493}
494
495
496int
497main (int argc, char *argv[])
498{
499 (void) argc;
500 (void) argv;
501 if (0 != GNUNET_TESTING_peer_run ("test_conversation_api",
502 "test_conversation.conf",
503 &run,
504 NULL))
505 return 1;
506 return ok;
507}
508
509
510/* end of test_conversation_api.c */
diff --git a/src/contrib/service/conversation/test_conversation_api_reject.c b/src/contrib/service/conversation/test_conversation_api_reject.c
new file mode 100644
index 000000000..248c7835d
--- /dev/null
+++ b/src/contrib/service/conversation/test_conversation_api_reject.c
@@ -0,0 +1,363 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013, 2014, 2018 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 * @file conversation/test_conversation_api_reject.c
22 * @brief testcase for conversation_api.c
23 *
24 * This test performs the operations of a call to a phone
25 * where the phone user immediately hangs up (rejecting the
26 * call).
27 */
28#include "platform.h"
29#include "gnunet_util_lib.h"
30#include "gnunet_testing_lib.h"
31#include "gnunet_gnsrecord_lib.h"
32#include "gnunet_conversation_service.h"
33#include "gnunet_identity_service.h"
34#include "gnunet_namestore_service.h"
35
36#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 25)
37
38static int ok = 1;
39
40static const struct GNUNET_CONFIGURATION_Handle *cfg;
41
42static struct GNUNET_IDENTITY_Handle *id;
43
44static struct GNUNET_IDENTITY_Operation *op;
45
46static struct GNUNET_CONVERSATION_Phone *phone;
47
48static struct GNUNET_NAMESTORE_Handle *ns;
49
50static struct GNUNET_CONVERSATION_Call *call;
51
52static struct GNUNET_NAMESTORE_QueueEntry *qe;
53
54static char *gns_name;
55
56static char *gns_caller_id;
57
58
59static int
60enable_speaker (void *cls)
61{
62 (void) cls;
63 GNUNET_break (0);
64 return GNUNET_SYSERR;
65}
66
67
68static void
69disable_speaker (void *cls)
70{
71 (void) cls;
72 GNUNET_break (0);
73}
74
75
76static void
77play (void *cls, size_t data_size, const void *data)
78{
79 (void) cls;
80 (void) data_size;
81 (void) data;
82 GNUNET_break (0);
83}
84
85
86static void
87destroy_speaker (void *cls)
88{
89 (void) cls;
90}
91
92
93static struct GNUNET_SPEAKER_Handle call_speaker = { &enable_speaker,
94 &play,
95 &disable_speaker,
96 &destroy_speaker,
97 "caller" };
98
99
100static int
101enable_mic (void *cls,
102 GNUNET_MICROPHONE_RecordedDataCallback rdc,
103 void *rdc_cls)
104{
105 (void) cls;
106 (void) rdc;
107 (void) rdc_cls;
108 GNUNET_break (0);
109 return GNUNET_SYSERR;
110}
111
112
113static void
114disable_mic (void *cls)
115{
116 (void) cls;
117 GNUNET_break (0);
118}
119
120
121static void
122destroy_mic (void *cls)
123{
124 (void) cls;
125}
126
127
128static struct GNUNET_MICROPHONE_Handle call_mic = { &enable_mic,
129 &disable_mic,
130 &destroy_mic,
131 "caller" };
132
133
134/**
135 * Signature of the main function of a task.
136 *
137 * @param cls closure
138 */
139static void
140end_test (void *cls)
141{
142 (void) cls;
143 GNUNET_SCHEDULER_shutdown ();
144 if (NULL != op)
145 {
146 GNUNET_IDENTITY_cancel (op);
147 op = NULL;
148 }
149 if (NULL != call)
150 {
151 GNUNET_CONVERSATION_call_stop (call);
152 call = NULL;
153 }
154 if (NULL != phone)
155 {
156 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from PHONE service.\n");
157 GNUNET_CONVERSATION_phone_destroy (phone);
158 phone = NULL;
159 }
160 if (NULL != id)
161 {
162 GNUNET_IDENTITY_disconnect (id);
163 id = NULL;
164 }
165 if (NULL != qe)
166 {
167 GNUNET_NAMESTORE_cancel (qe);
168 qe = NULL;
169 }
170 if (NULL != ns)
171 {
172 GNUNET_NAMESTORE_disconnect (ns);
173 ns = NULL;
174 }
175}
176
177
178static void
179phone_event_handler (void *cls,
180 enum GNUNET_CONVERSATION_PhoneEventCode code,
181 struct GNUNET_CONVERSATION_Caller *caller,
182 const struct GNUNET_CRYPTO_PublicKey *caller_id)
183{
184 static enum GNUNET_CONVERSATION_PhoneEventCode expect =
185 GNUNET_CONVERSATION_EC_PHONE_RING;
186
187 (void) cls;
188 (void) caller_id;
189 GNUNET_break (code == expect);
190 switch (code)
191 {
192 case GNUNET_CONVERSATION_EC_PHONE_RING:
193 GNUNET_CONVERSATION_caller_hang_up (caller);
194 break;
195
196 default:
197 fprintf (stderr, "Unexpected phone code: %d\n", code);
198 break;
199 }
200}
201
202
203static void
204call_event_handler (void *cls, enum GNUNET_CONVERSATION_CallEventCode code)
205{
206 static enum GNUNET_CONVERSATION_CallEventCode expect =
207 GNUNET_CONVERSATION_EC_CALL_RINGING;
208
209 (void) cls;
210 GNUNET_break (code == expect);
211 switch (code)
212 {
213 case GNUNET_CONVERSATION_EC_CALL_RINGING:
214 expect = GNUNET_CONVERSATION_EC_CALL_HUNG_UP;
215 break;
216
217 case GNUNET_CONVERSATION_EC_CALL_HUNG_UP:
218 call = NULL;
219 ok = 0;
220 GNUNET_SCHEDULER_shutdown ();
221 expect = -1;
222 break;
223
224 case GNUNET_CONVERSATION_EC_CALL_PICKED_UP:
225 case GNUNET_CONVERSATION_EC_CALL_GNS_FAIL:
226 case GNUNET_CONVERSATION_EC_CALL_SUSPENDED:
227 case GNUNET_CONVERSATION_EC_CALL_RESUMED:
228 fprintf (stderr, "Unexpected call code: %d\n", code);
229 break;
230
231 case GNUNET_CONVERSATION_EC_CALL_ERROR:
232 fprintf (stderr, "Unexpected call code: %d\n", code);
233 call = NULL;
234 break;
235 }
236}
237
238
239static void
240caller_ego_create_cont (void *cls,
241 const struct GNUNET_CRYPTO_PrivateKey *pk,
242 enum GNUNET_ErrorCode ec)
243{
244 (void) cls;
245 op = NULL;
246 GNUNET_assert (GNUNET_EC_NONE == ec);
247}
248
249
250static void
251namestore_put_cont (void *cls, enum GNUNET_ErrorCode ec)
252{
253 (void) cls;
254 qe = NULL;
255 GNUNET_assert (GNUNET_EC_NONE == ec);
256 GNUNET_assert (NULL == op);
257 op = GNUNET_IDENTITY_create (id, "caller-ego", NULL,
258 GNUNET_PUBLIC_KEY_TYPE_ECDSA,
259 &caller_ego_create_cont,
260 NULL);
261}
262
263
264static void
265identity_cb (void *cls,
266 struct GNUNET_IDENTITY_Ego *ego,
267 void **ctx,
268 const char *name)
269{
270 struct GNUNET_GNSRECORD_Data rd;
271 struct GNUNET_CRYPTO_PublicKey pub;
272
273 (void) cls;
274 (void) ctx;
275 if (NULL == name)
276 return;
277 if (NULL == ego)
278 return;
279 if (0 == strcmp (name, "phone-ego"))
280 {
281 GNUNET_IDENTITY_ego_get_public_key (ego, &pub);
282 GNUNET_asprintf (&gns_name,
283 "phone.%s",
284 GNUNET_GNSRECORD_pkey_to_zkey (&pub));
285 phone =
286 GNUNET_CONVERSATION_phone_create (cfg, ego, &phone_event_handler, NULL);
287 GNUNET_assert (NULL != phone);
288 memset (&rd, 0, sizeof(rd));
289 GNUNET_CONVERSATION_phone_get_record (phone, &rd);
290 GNUNET_assert (rd.record_type == GNUNET_GNSRECORD_TYPE_PHONE);
291 rd.expiration_time = UINT64_MAX;
292 qe =
293 GNUNET_NAMESTORE_record_set_store (ns,
294 GNUNET_IDENTITY_ego_get_private_key (ego),
295 "phone" /* GNS label */,
296 1,
297 &rd,
298 &namestore_put_cont,
299 NULL);
300 return;
301 }
302 if (0 == strcmp (name, "caller-ego"))
303 {
304 GNUNET_IDENTITY_ego_get_public_key (ego, &pub);
305 GNUNET_asprintf (&gns_caller_id,
306 "%s",
307 GNUNET_GNSRECORD_pkey_to_zkey (&pub));
308 call = GNUNET_CONVERSATION_call_start (cfg,
309 ego,
310 gns_name,
311 &call_speaker,
312 &call_mic,
313 &call_event_handler,
314 NULL);
315 return;
316 }
317}
318
319
320static void
321phone_ego_create_cont (void *cls,
322 const struct GNUNET_CRYPTO_PrivateKey *pk,
323 enum GNUNET_ErrorCode ec)
324{
325 (void) cls;
326 op = NULL;
327 GNUNET_assert (GNUNET_EC_NONE == ec);
328}
329
330
331static void
332run (void *cls,
333 const struct GNUNET_CONFIGURATION_Handle *c,
334 struct GNUNET_TESTING_Peer *peer)
335{
336 (void) cls;
337 (void) peer;
338 cfg = c;
339 GNUNET_SCHEDULER_add_delayed (TIMEOUT, &end_test, NULL);
340 id = GNUNET_IDENTITY_connect (cfg, &identity_cb, NULL);
341 op = GNUNET_IDENTITY_create (id, "phone-ego", NULL,
342 GNUNET_PUBLIC_KEY_TYPE_ECDSA,
343 &phone_ego_create_cont,
344 NULL);
345 ns = GNUNET_NAMESTORE_connect (cfg);
346}
347
348
349int
350main (int argc, char *argv[])
351{
352 (void) argc;
353 (void) argv;
354 if (0 != GNUNET_TESTING_peer_run ("test_conversation_api",
355 "test_conversation.conf",
356 &run,
357 NULL))
358 return 1;
359 return ok;
360}
361
362
363/* end of test_conversation_api_reject.c */
diff --git a/src/contrib/service/conversation/test_conversation_api_twocalls.c b/src/contrib/service/conversation/test_conversation_api_twocalls.c
new file mode 100644
index 000000000..cc4969e09
--- /dev/null
+++ b/src/contrib/service/conversation/test_conversation_api_twocalls.c
@@ -0,0 +1,642 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2013, 2018x 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 * @file conversation/test_conversation_api_twocalls.c
22 * @brief testcase for conversation_api.c
23 *
24 * This test performs the operations of TWO calls made to a phone
25 * where the phone user picks up one, suspends it, picks up the
26 * second one; eventually, the initiator hangs up, the callee
27 * resumes the first call, and then the initiator hangs up the
28 * second call.
29 */
30#include "platform.h"
31#include "gnunet_util_lib.h"
32#include "gnunet_testing_lib.h"
33#include "gnunet_gnsrecord_lib.h"
34#include "gnunet_conversation_service.h"
35#include "gnunet_identity_service.h"
36#include "gnunet_namestore_service.h"
37
38#define FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 250)
39
40#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 25)
41
42#define LOG(kind, ...) GNUNET_log (kind, __VA_ARGS__)
43
44#define LOG_DEBUG(...) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
45
46static const struct GNUNET_CONFIGURATION_Handle *cfg;
47
48static struct GNUNET_IDENTITY_Handle *id;
49
50static struct GNUNET_IDENTITY_Operation *op;
51
52static struct GNUNET_CONVERSATION_Phone *phone;
53
54static struct GNUNET_NAMESTORE_Handle *ns;
55
56static struct GNUNET_CONVERSATION_Call *call1;
57
58static struct GNUNET_CONVERSATION_Call *call2;
59
60static struct GNUNET_NAMESTORE_QueueEntry *qe;
61
62static struct GNUNET_CONVERSATION_Caller *active_caller1;
63
64static struct GNUNET_CONVERSATION_Caller *active_caller2;
65
66static char *gns_name;
67
68static char *gns_caller_id;
69
70static GNUNET_MICROPHONE_RecordedDataCallback phone_rdc;
71
72static void *phone_rdc_cls;
73
74static struct GNUNET_SCHEDULER_Task *phone_task;
75
76static struct GNUNET_SCHEDULER_Task *timeout_task;
77
78/**
79 * Variable for recognizing caller1
80 */
81static const char *caller1 = "caller1";
82
83/**
84 * Variable for recognizing caller2
85 */
86static const char *caller2 = "caller2";
87
88/**
89 * Variable for recognizing callee
90 */
91static const char *phone0 = "phone";
92
93
94#define CALLER1 &caller1
95#define CALLER2 &caller2
96#define PHONE0 &phone0
97
98#define CLS_STR(caller) (*((char **) caller))
99
100
101/**
102 * Did caller1 call finish successfully
103 */
104static int call1_finished;
105
106/**
107 * Did caller2 call finish successfully
108 */
109static int call2_finished;
110
111struct MicContext
112{
113 GNUNET_MICROPHONE_RecordedDataCallback rdc;
114
115 void *rdc_cls;
116
117 struct GNUNET_SCHEDULER_Task *call_task;
118};
119
120static struct MicContext call1_mic_ctx;
121static struct MicContext call2_mic_ctx;
122// static struct MicContext phone_mic_ctx;
123
124
125static void
126phone_send (void *cls)
127{
128 char buf[32];
129
130 (void) cls;
131 GNUNET_assert (NULL != phone_rdc);
132 GNUNET_snprintf (buf, sizeof(buf), "phone");
133 phone_rdc (phone_rdc_cls, strlen (buf) + 1, buf);
134 phone_task = GNUNET_SCHEDULER_add_delayed (FREQ, &phone_send, NULL);
135}
136
137
138static void
139call_send (void *cls)
140{
141 struct MicContext *mc = cls;
142 char buf[32];
143
144 (void) cls;
145 GNUNET_assert (NULL != mc->rdc);
146 GNUNET_snprintf (buf, sizeof(buf), "call");
147 mc->rdc (mc->rdc_cls, strlen (buf) + 1, buf);
148 mc->call_task = GNUNET_SCHEDULER_add_delayed (FREQ, &call_send, mc);
149}
150
151
152static int
153enable_speaker (void *cls)
154{
155 const char *origin = CLS_STR (cls);
156
157 (void) cls;
158 LOG_DEBUG ("Speaker %s enabled\n", origin);
159 return GNUNET_OK;
160}
161
162
163static void
164disable_speaker (void *cls)
165{
166 const char *origin = CLS_STR (cls);
167
168 (void) cls;
169 LOG_DEBUG ("Speaker %s disabled\n", origin);
170}
171
172
173static void
174play (void *cls, size_t data_size, const void *data)
175{
176 static unsigned int phone_i;
177 static unsigned int call_i;
178
179 (void) cls;
180 if (0 == strncmp ("call", data, data_size))
181 call_i++;
182 else if (0 == strncmp ("phone", data, data_size))
183 phone_i++;
184 else
185 {
186 LOG_DEBUG ("Received %u bytes of unexpected data `%.*s'\n",
187 (unsigned int) data_size,
188 (int) data_size,
189 (const char *) data);
190 }
191
192 if ((20 < call_i) && (20 < phone_i) && (CALLER2 == cls))
193 {
194 /* time to hang up ... */
195 GNUNET_CONVERSATION_call_stop (call2);
196 call2 = NULL;
197 /* reset counters */
198 call_i = 0;
199 phone_i = 0;
200 call2_finished = GNUNET_YES;
201 }
202 if ((20 < call_i) && (20 < phone_i) && (CALLER1 == cls))
203 {
204 /* time to hang up ... */
205 GNUNET_CONVERSATION_call_stop (call1);
206 call1 = NULL;
207 call_i = 0;
208 phone_i = 0;
209 call1_finished = GNUNET_YES;
210 }
211}
212
213
214static void
215destroy_speaker (void *cls)
216{
217 const char *origin = CLS_STR (cls);
218
219 LOG_DEBUG ("Speaker %s destroyed\n", origin);
220}
221
222
223static struct GNUNET_SPEAKER_Handle call1_speaker = { &enable_speaker,
224 &play,
225 &disable_speaker,
226 &destroy_speaker,
227 CALLER1 };
228
229
230static struct GNUNET_SPEAKER_Handle call2_speaker = { &enable_speaker,
231 &play,
232 &disable_speaker,
233 &destroy_speaker,
234 CALLER2 };
235
236
237static struct GNUNET_SPEAKER_Handle phone_speaker = { &enable_speaker,
238 &play,
239 &disable_speaker,
240 &destroy_speaker,
241 PHONE0 };
242
243
244static int
245enable_mic (void *cls,
246 GNUNET_MICROPHONE_RecordedDataCallback rdc,
247 void *rdc_cls)
248{
249 const char *origin = CLS_STR (cls);
250 struct MicContext *mc;
251
252 LOG_DEBUG ("Mic %s enabled\n", origin);
253 if (PHONE0 == cls)
254 {
255 phone_rdc = rdc;
256 phone_rdc_cls = rdc_cls;
257 GNUNET_break (NULL == phone_task);
258 phone_task = GNUNET_SCHEDULER_add_now (&phone_send, NULL);
259 return GNUNET_OK;
260 }
261 mc = (CALLER1 == cls) ? &call1_mic_ctx : &call2_mic_ctx;
262 mc->rdc = rdc;
263 mc->rdc_cls = rdc_cls;
264 GNUNET_break (NULL == mc->call_task);
265 mc->call_task = GNUNET_SCHEDULER_add_now (&call_send, mc);
266 return GNUNET_OK;
267}
268
269
270static void
271disable_mic (void *cls)
272{
273 const char *origin = CLS_STR (cls);
274 struct MicContext *mc;
275
276 LOG_DEBUG ("Mic %s disabled\n", origin);
277 if (PHONE0 == cls)
278 {
279 phone_rdc = NULL;
280 phone_rdc_cls = NULL;
281 GNUNET_SCHEDULER_cancel (phone_task);
282 phone_task = NULL;
283 return;
284 }
285 mc = (CALLER1 == cls) ? &call1_mic_ctx : &call2_mic_ctx;
286 mc->rdc = NULL;
287 mc->rdc_cls = NULL;
288 GNUNET_SCHEDULER_cancel (mc->call_task);
289 mc->call_task = NULL;
290}
291
292
293static void
294destroy_mic (void *cls)
295{
296 const char *origin = CLS_STR (cls);
297
298 LOG_DEBUG ("Mic %s destroyed\n", origin);
299}
300
301
302static struct GNUNET_MICROPHONE_Handle call1_mic = { &enable_mic,
303 &disable_mic,
304 &destroy_mic,
305 CALLER1 };
306
307
308static struct GNUNET_MICROPHONE_Handle call2_mic = { &enable_mic,
309 &disable_mic,
310 &destroy_mic,
311 CALLER2 };
312
313
314static struct GNUNET_MICROPHONE_Handle phone_mic = { &enable_mic,
315 &disable_mic,
316 &destroy_mic,
317 PHONE0 };
318
319
320/**
321 * Function run on timeout.
322 *
323 * @param cls closure
324 */
325static void
326end_test (void *cls)
327{
328 (void) cls;
329 timeout_task = NULL;
330 fprintf (stderr, "Timeout!\n");
331 GNUNET_SCHEDULER_shutdown ();
332}
333
334
335/**
336 * Function run on shutdown.
337 *
338 * @param cls closure
339 */
340static void
341do_shutdown (void *cls)
342{
343 (void) cls;
344 if (NULL != timeout_task)
345 {
346 GNUNET_SCHEDULER_cancel (timeout_task);
347 timeout_task = NULL;
348 }
349 if (NULL != op)
350 {
351 GNUNET_IDENTITY_cancel (op);
352 op = NULL;
353 }
354 if (NULL != call1)
355 {
356 GNUNET_CONVERSATION_call_stop (call1);
357 call1 = NULL;
358 }
359 if (NULL != call2)
360 {
361 GNUNET_CONVERSATION_call_stop (call2);
362 call2 = NULL;
363 }
364 if (NULL != phone)
365 {
366 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from PHONE service.\n");
367 GNUNET_CONVERSATION_phone_destroy (phone);
368 phone = NULL;
369 }
370 if (NULL != id)
371 {
372 GNUNET_IDENTITY_disconnect (id);
373 id = NULL;
374 }
375 if (NULL != qe)
376 {
377 GNUNET_NAMESTORE_cancel (qe);
378 qe = NULL;
379 }
380 if (NULL != ns)
381 {
382 GNUNET_NAMESTORE_disconnect (ns);
383 ns = NULL;
384 }
385}
386
387
388static void
389caller_event_handler (void *cls, enum GNUNET_CONVERSATION_CallerEventCode code)
390{
391 (void) cls;
392 switch (code)
393 {
394 case GNUNET_CONVERSATION_EC_CALLER_SUSPEND:
395 case GNUNET_CONVERSATION_EC_CALLER_RESUME:
396 LOG (GNUNET_ERROR_TYPE_WARNING, "Unexpected caller code: %d\n", code);
397 break;
398 }
399}
400
401
402static void
403phone_event_handler (void *cls,
404 enum GNUNET_CONVERSATION_PhoneEventCode code,
405 struct GNUNET_CONVERSATION_Caller *caller,
406 const struct GNUNET_CRYPTO_PublicKey *caller_id)
407{
408 const char *cid;
409
410 (void) cls;
411 (void) caller_id;
412
413 switch (code)
414 {
415 case GNUNET_CONVERSATION_EC_PHONE_RING:
416 if (NULL == active_caller1)
417 {
418 active_caller1 = caller;
419 cid = "caller1";
420 GNUNET_CONVERSATION_caller_pick_up (caller,
421 &caller_event_handler,
422 (void *) cid,
423 &phone_speaker,
424 &phone_mic);
425 }
426 else
427 {
428 GNUNET_CONVERSATION_caller_suspend (active_caller1);
429 active_caller2 = caller;
430 cid = "caller2";
431 GNUNET_CONVERSATION_caller_pick_up (caller,
432 &caller_event_handler,
433 (void *) cid,
434 &phone_speaker,
435 &phone_mic);
436 }
437 break;
438
439 case GNUNET_CONVERSATION_EC_PHONE_HUNG_UP:
440 if (caller == active_caller2)
441 {
442 active_caller2 = NULL;
443 GNUNET_CONVERSATION_caller_resume (active_caller1,
444 &phone_speaker,
445 &phone_mic);
446 }
447 else if (caller == active_caller1)
448 {
449 active_caller1 = NULL;
450 GNUNET_break (NULL == active_caller2);
451 GNUNET_SCHEDULER_shutdown ();
452 }
453 break;
454
455 default:
456 LOG (GNUNET_ERROR_TYPE_WARNING, "Unexpected phone code: %d\n", code);
457 break;
458 }
459}
460
461
462static void
463call_event_handler (void *cls, enum GNUNET_CONVERSATION_CallEventCode code)
464{
465 const char *cid = cls;
466
467 switch (code)
468 {
469 case GNUNET_CONVERSATION_EC_CALL_RINGING:
470 break;
471
472 case GNUNET_CONVERSATION_EC_CALL_PICKED_UP:
473 LOG_DEBUG ("Call %s picked\n", cid);
474 break;
475
476 case GNUNET_CONVERSATION_EC_CALL_GNS_FAIL:
477 LOG_DEBUG ("Call %s GNS lookup failed \n", cid);
478 break;
479
480 case GNUNET_CONVERSATION_EC_CALL_HUNG_UP:
481 LOG_DEBUG ("Call %s hungup\n", cid);
482 if (0 == strcmp (cid, "call1"))
483 call1 = NULL;
484 else
485 call2 = NULL;
486 break;
487
488 case GNUNET_CONVERSATION_EC_CALL_SUSPENDED:
489 LOG_DEBUG ("Call %s suspended\n", cid);
490 break;
491
492 case GNUNET_CONVERSATION_EC_CALL_RESUMED:
493 LOG_DEBUG ("Call %s resumed\n", cid);
494 break;
495
496 case GNUNET_CONVERSATION_EC_CALL_ERROR:
497 GNUNET_break (0);
498 if (0 == strcmp (cid, "call1"))
499 call1 = NULL;
500 else
501 call2 = NULL;
502 GNUNET_SCHEDULER_shutdown ();
503 break;
504 }
505}
506
507
508static void
509caller_ego_create_cont (void *cls,
510 const struct GNUNET_CRYPTO_PrivateKey *pk,
511 enum GNUNET_ErrorCode ec)
512{
513 (void) cls;
514 op = NULL;
515 GNUNET_assert (GNUNET_EC_NONE == ec);
516}
517
518
519static void
520namestore_put_cont (void *cls, enum GNUNET_ErrorCode ec)
521{
522 (void) cls;
523 qe = NULL;
524 GNUNET_assert (GNUNET_EC_NONE == ec);
525 GNUNET_assert (NULL == op);
526 op = GNUNET_IDENTITY_create (id, "caller-ego", NULL,
527 GNUNET_PUBLIC_KEY_TYPE_ECDSA,
528 &caller_ego_create_cont,
529 NULL);
530}
531
532
533static void
534identity_cb (void *cls,
535 struct GNUNET_IDENTITY_Ego *ego,
536 void **ctx,
537 const char *name)
538{
539 struct GNUNET_GNSRECORD_Data rd;
540 struct GNUNET_CRYPTO_PublicKey pub;
541
542 (void) cls;
543 (void) ctx;
544 if (NULL == name)
545 return;
546 if (NULL == ego)
547 return;
548 if (0 == strcmp (name, "phone-ego"))
549 {
550 GNUNET_IDENTITY_ego_get_public_key (ego, &pub);
551 GNUNET_asprintf (&gns_name,
552 "phone.%s",
553 GNUNET_GNSRECORD_pkey_to_zkey (&pub));
554 phone =
555 GNUNET_CONVERSATION_phone_create (cfg, ego, &phone_event_handler, NULL);
556 GNUNET_assert (NULL != phone);
557 memset (&rd, 0, sizeof(rd));
558 GNUNET_CONVERSATION_phone_get_record (phone, &rd);
559 GNUNET_assert (rd.record_type == GNUNET_GNSRECORD_TYPE_PHONE);
560 rd.expiration_time = UINT64_MAX;
561 qe =
562 GNUNET_NAMESTORE_record_set_store (ns,
563 GNUNET_IDENTITY_ego_get_private_key (ego),
564 "phone" /* GNS label */,
565 1,
566 &rd,
567 &namestore_put_cont,
568 NULL);
569 return;
570 }
571 if (0 == strcmp (name, "caller-ego"))
572 {
573 GNUNET_IDENTITY_ego_get_public_key (ego, &pub);
574 GNUNET_asprintf (&gns_caller_id,
575 "%s",
576 GNUNET_GNSRECORD_pkey_to_zkey (&pub));
577 call1 = GNUNET_CONVERSATION_call_start (cfg,
578 ego,
579 gns_name,
580 &call1_speaker,
581 &call1_mic,
582 &call_event_handler,
583 (void *) "call1");
584 call2 = GNUNET_CONVERSATION_call_start (cfg,
585 ego,
586 gns_name,
587 &call2_speaker,
588 &call2_mic,
589 &call_event_handler,
590 (void *) "call2");
591 return;
592 }
593}
594
595
596static void
597phone_ego_create_cont (void *cls,
598 const struct GNUNET_CRYPTO_PrivateKey *pk,
599 enum GNUNET_ErrorCode ec)
600{
601 (void) cls;
602 op = NULL;
603 GNUNET_assert (GNUNET_EC_NONE == ec);
604}
605
606
607static void
608run (void *cls,
609 const struct GNUNET_CONFIGURATION_Handle *c,
610 struct GNUNET_TESTING_Peer *peer)
611{
612 (void) cls;
613 (void) peer;
614 cfg = c;
615 timeout_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &end_test, NULL);
616 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL);
617 id = GNUNET_IDENTITY_connect (cfg, &identity_cb, NULL);
618 op = GNUNET_IDENTITY_create (id, "phone-ego", NULL,
619 GNUNET_PUBLIC_KEY_TYPE_ECDSA,
620 &phone_ego_create_cont,
621 NULL);
622 ns = GNUNET_NAMESTORE_connect (cfg);
623}
624
625
626int
627main (int argc, char *argv[])
628{
629 (void) argc;
630 (void) argv;
631 if (0 != GNUNET_TESTING_peer_run ("test_conversation_api_twocalls",
632 "test_conversation.conf",
633 &run,
634 NULL))
635 return 1;
636 if (call1_finished && call2_finished)
637 return 0;
638 return 1;
639}
640
641
642/* end of test_conversation_api_twocalls.c */