diff options
Diffstat (limited to 'src/contrib/service/conversation')
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 @@ | |||
1 | gnunet-service-conversation | ||
2 | gnunet-conversation | ||
3 | gnunet-conversation-test | ||
4 | gnunet-helper-audio-playback | ||
5 | gnunet-helper-audio-record | ||
6 | test_conversation_api | ||
7 | test_conversation_api_reject | ||
8 | test_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 | ||
2 | SUBDIRS = . | ||
3 | |||
4 | plugindir = $(libdir)/gnunet | ||
5 | |||
6 | AM_CPPFLAGS = \ | ||
7 | $(GNUNET_CPPFLAGS) \ | ||
8 | -I$(top_srcdir)/src/include \ | ||
9 | -I$(top_srcdir) | ||
10 | |||
11 | |||
12 | if BUILD_CONVERSATION | ||
13 | lib_LTLIBRARIES = \ | ||
14 | libgnunetmicrophone.la \ | ||
15 | libgnunetspeaker.la \ | ||
16 | libgnunetconversation.la | ||
17 | |||
18 | bin_PROGRAMS = \ | ||
19 | gnunet-conversation-test \ | ||
20 | gnunet-conversation | ||
21 | |||
22 | libexec_PROGRAMS = \ | ||
23 | gnunet-service-conversation | ||
24 | |||
25 | check_PROGRAMS = \ | ||
26 | test_conversation_api \ | ||
27 | test_conversation_api_reject \ | ||
28 | test_conversation_api_twocalls | ||
29 | |||
30 | pkgcfg_DATA = conversation.conf | ||
31 | endif | ||
32 | |||
33 | pkgcfgdir= $(pkgdatadir)/config.d/ | ||
34 | |||
35 | libexecdir= $(pkglibdir)/libexec/ | ||
36 | |||
37 | plugin_LTLIBRARIES = \ | ||
38 | libgnunet_plugin_gnsrecord_conversation.la | ||
39 | |||
40 | |||
41 | libgnunet_plugin_gnsrecord_conversation_la_SOURCES = \ | ||
42 | plugin_gnsrecord_conversation.c | ||
43 | libgnunet_plugin_gnsrecord_conversation_la_LIBADD = \ | ||
44 | $(top_builddir)/src/lib/util/libgnunetutil.la \ | ||
45 | $(LTLIBINTL) | ||
46 | libgnunet_plugin_gnsrecord_conversation_la_LDFLAGS = \ | ||
47 | $(GN_PLUGIN_LDFLAGS) | ||
48 | |||
49 | |||
50 | libgnunetmicrophone_la_SOURCES = \ | ||
51 | microphone.c | ||
52 | libgnunetmicrophone_la_LIBADD = \ | ||
53 | $(top_builddir)/src/lib/util/libgnunetutil.la | ||
54 | libgnunetmicrophone_la_LDFLAGS = \ | ||
55 | $(GN_LIB_LDFLAGS) \ | ||
56 | -version-info 0:0:0 | ||
57 | |||
58 | libgnunetspeaker_la_SOURCES = \ | ||
59 | speaker.c | ||
60 | libgnunetspeaker_la_LIBADD = \ | ||
61 | $(top_builddir)/src/lib/util/libgnunetutil.la | ||
62 | libgnunetspeaker_la_LDFLAGS = \ | ||
63 | $(GN_LIB_LDFLAGS) \ | ||
64 | -version-info 0:0:0 | ||
65 | |||
66 | |||
67 | libgnunetconversation_la_SOURCES = \ | ||
68 | conversation_api.c \ | ||
69 | conversation_api_call.c \ | ||
70 | conversation.h | ||
71 | libgnunetconversation_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 | |||
78 | libgnunetconversation_la_LDFLAGS = \ | ||
79 | $(GN_LIB_LDFLAGS) \ | ||
80 | -version-info 0:0:0 | ||
81 | |||
82 | |||
83 | if BUILD_PULSE_HELPERS | ||
84 | AUDIO_HELPER_RECD=gnunet-helper-audio-record | ||
85 | AUDIO_HELPER_PLAY=gnunet-helper-audio-playback | ||
86 | AUDIO_TESTS=$(check_PROGRAMS) | ||
87 | else | ||
88 | if BUILD_GST_HELPERS | ||
89 | AUDIO_HELPER_RECD=gnunet-helper-audio-record | ||
90 | AUDIO_HELPER_PLAY=gnunet-helper-audio-playback | ||
91 | AUDIO_TESTS=$(check_PROGRAMS) | ||
92 | else | ||
93 | if BUILD_EXPERIMENTAL_HELPERS | ||
94 | AUDIO_HELPER_RECD=gnunet-helper-audio-record | ||
95 | AUDIO_HELPER_PLAY=gnunet-helper-audio-playback | ||
96 | AUDIO_TESTS=$(check_PROGRAMS) | ||
97 | endif | ||
98 | endif | ||
99 | endif | ||
100 | |||
101 | if BUILD_CONVERSATION | ||
102 | libexec_PROGRAMS += \ | ||
103 | $(AUDIO_HELPER_RECD) \ | ||
104 | $(AUDIO_HELPER_PLAY) | ||
105 | endif | ||
106 | |||
107 | AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; | ||
108 | if ENABLE_TEST_RUN | ||
109 | TESTS = $(AUDIO_TESTS) | ||
110 | endif | ||
111 | |||
112 | if BUILD_PULSE_HELPERS | ||
113 | gnunet_helper_audio_record_SOURCES = \ | ||
114 | gnunet-helper-audio-record.c | ||
115 | gnunet_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 | |||
122 | gnunet_helper_audio_playback_SOURCES = \ | ||
123 | gnunet-helper-audio-playback.c | ||
124 | gnunet_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 | # | ||
130 | else | ||
131 | if BUILD_GST_HELPERS | ||
132 | gnunet_helper_audio_record_SOURCES = \ | ||
133 | gnunet-helper-audio-record-gst.c | ||
134 | gnunet_helper_audio_record_LDADD = \ | ||
135 | $(top_builddir)/src/lib/util/libgnunetutil.la \ | ||
136 | $(GST_LIBS) \ | ||
137 | $(INTLLIBS) | ||
138 | gnunet_helper_audio_record_LDFLAGS = \ | ||
139 | $(GST_LDFLAGS) | ||
140 | gnunet_helper_audio_record_CFLAGS = \ | ||
141 | $(GST_CFLAGS) | ||
142 | |||
143 | gnunet_helper_audio_playback_SOURCES = \ | ||
144 | gnunet-helper-audio-playback-gst.c | ||
145 | gnunet_helper_audio_playback_LDADD = \ | ||
146 | $(top_builddir)/src/lib/util/libgnunetutil.la \ | ||
147 | $(GST_LIBS) \ | ||
148 | $(INTLLIBS) | ||
149 | gnunet_helper_audio_playback_LDFLAGS = \ | ||
150 | $(GST_LDFLAGS) | ||
151 | gnunet_helper_audio_playback_CFLAGS = \ | ||
152 | $(GST_CFLAGS) -DIS_SPEAKER | ||
153 | else | ||
154 | if BUILD_EXPERIMENTAL_HELPERS | ||
155 | gnunet_helper_audio_record_SOURCES = \ | ||
156 | gnunet_gst_test.c gnunet_gst.c | ||
157 | gnunet_helper_audio_record_LDADD = \ | ||
158 | $(top_builddir)/src/lib/util/libgnunetutil.la \ | ||
159 | $(GST_LIBS) \ | ||
160 | $(INTLLIBS) | ||
161 | gnunet_helper_audio_record_LDFLAGS = \ | ||
162 | $(GST_LDFLAGS) | ||
163 | gnunet_helper_audio_record_CFLAGS = \ | ||
164 | $(GST_CFLAGS) -DIS_MIC | ||
165 | |||
166 | gnunet_helper_audio_playback_SOURCES = \ | ||
167 | gnunet_gst_test.c gnunet_gst.c | ||
168 | gnunet_helper_audio_playback_LDADD = \ | ||
169 | $(top_builddir)/src/lib/util/libgnunetutil.la \ | ||
170 | $(GST_LIBS) \ | ||
171 | $(INTLLIBS) | ||
172 | gnunet_helper_audio_playback_LDFLAGS = \ | ||
173 | $(GST_LDFLAGS) | ||
174 | gnunet_helper_audio_playback_CFLAGS = \ | ||
175 | $(GST_CFLAGS) -DIS_SPEAKER | ||
176 | endif | ||
177 | endif | ||
178 | endif | ||
179 | |||
180 | gnunet_service_conversation_SOURCES = \ | ||
181 | gnunet-service-conversation.c | ||
182 | gnunet_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 | |||
193 | gnunet_conversation_SOURCES = \ | ||
194 | gnunet-conversation.c | ||
195 | gnunet_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 | |||
208 | gnunet_conversation_test_SOURCES = \ | ||
209 | gnunet-conversation-test.c | ||
210 | gnunet_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 | |||
219 | test_conversation_api_SOURCES = \ | ||
220 | test_conversation_api.c | ||
221 | test_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 | ||
230 | test_conversation_api_LDFLAGS = \ | ||
231 | -export-dynamic | ||
232 | |||
233 | test_conversation_api_twocalls_SOURCES = \ | ||
234 | test_conversation_api_twocalls.c | ||
235 | test_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 | ||
244 | test_conversation_api_twocalls_LDFLAGS = \ | ||
245 | -export-dynamic | ||
246 | |||
247 | test_conversation_api_reject_SOURCES = \ | ||
248 | test_conversation_api_reject.c | ||
249 | test_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 | ||
258 | test_conversation_api_reject_LDFLAGS = \ | ||
259 | -export-dynamic | ||
260 | |||
261 | |||
262 | |||
263 | EXTRA_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 | |||
3 | colorgcc -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 | |||
5 | colorgcc -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] | ||
2 | START_ON_DEMAND = @START_ON_DEMAND@ | ||
3 | BINARY = gnunet-service-conversation | ||
4 | UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-conversation.sock | ||
5 | HOSTNAME = 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. | ||
10 | LINE = 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). | ||
15 | RECORD_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. | ||
22 | RECORD_EXPIRATION = 1 day | ||
23 | |||
24 | |||
25 | ACCEPT_FROM = 127.0.0.1; | ||
26 | ACCEPT_FROM6 = ::1; | ||
27 | UNIX_MATCH_UID = NO | ||
28 | UNIX_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 | ||
33 | extern "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 | |||
52 | GNUNET_NETWORK_STRUCT_BEGIN | ||
53 | |||
54 | |||
55 | /** | ||
56 | * Message to transmit the audio (between client and helpers). | ||
57 | */ | ||
58 | struct 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 | */ | ||
72 | struct 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 | */ | ||
94 | struct 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 | */ | ||
121 | struct 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 | */ | ||
139 | struct 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 | */ | ||
157 | struct 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 | */ | ||
176 | struct 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 | */ | ||
194 | struct 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 | */ | ||
214 | struct 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 | */ | ||
251 | struct 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 | */ | ||
270 | struct 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 | */ | ||
299 | struct 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 | */ | ||
336 | struct 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 | */ | ||
348 | struct 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 | */ | ||
360 | struct 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 | */ | ||
372 | struct 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 | */ | ||
384 | struct 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 | |||
395 | GNUNET_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 | */ | ||
36 | enum 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 | */ | ||
68 | struct 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 | */ | ||
125 | enum 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 | */ | ||
150 | struct 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 | */ | ||
209 | static void | ||
210 | reconnect_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 | */ | ||
220 | static void | ||
221 | transmit_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 | */ | ||
247 | static enum GNUNET_GenericReturnValue | ||
248 | check_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 | */ | ||
260 | static void | ||
261 | handle_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 | */ | ||
312 | static struct GNUNET_CONVERSATION_Caller * | ||
313 | find_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 | */ | ||
331 | static void | ||
332 | handle_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 | */ | ||
391 | static void | ||
392 | handle_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 | */ | ||
436 | static void | ||
437 | handle_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 | */ | ||
484 | static int | ||
485 | check_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 | */ | ||
502 | static void | ||
503 | handle_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 | */ | ||
540 | static void | ||
541 | phone_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 | */ | ||
559 | static void | ||
560 | clean_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 | */ | ||
587 | static void | ||
588 | reconnect_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 | */ | ||
649 | struct GNUNET_CONVERSATION_Phone * | ||
650 | GNUNET_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 | */ | ||
712 | void | ||
713 | GNUNET_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 | */ | ||
734 | void | ||
735 | GNUNET_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 | */ | ||
770 | void | ||
771 | GNUNET_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 | */ | ||
804 | void | ||
805 | GNUNET_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 | */ | ||
829 | void | ||
830 | GNUNET_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 | */ | ||
864 | void | ||
865 | GNUNET_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 | */ | ||
38 | enum 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 | */ | ||
80 | struct 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 | */ | ||
149 | static void | ||
150 | fail_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 | */ | ||
160 | static void | ||
161 | transmit_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 | */ | ||
187 | static void | ||
188 | handle_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 | */ | ||
238 | static void | ||
239 | handle_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 | */ | ||
294 | static void | ||
295 | handle_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 | */ | ||
339 | static void | ||
340 | handle_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 | */ | ||
381 | static int | ||
382 | check_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 | */ | ||
398 | static void | ||
399 | handle_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 | */ | ||
449 | static void | ||
450 | handle_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 | */ | ||
507 | static void | ||
508 | call_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 | */ | ||
531 | static void | ||
532 | fail_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 | */ | ||
566 | struct GNUNET_CONVERSATION_Call * | ||
567 | GNUNET_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 | */ | ||
647 | void | ||
648 | GNUNET_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 | */ | ||
686 | void | ||
687 | GNUNET_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 | */ | ||
720 | void | ||
721 | GNUNET_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 | ||
2 | dot -Tpng `ls -tr1 /tmp/*rec*.dot | tail -1` | display /dev/stdin & | ||
3 | dot -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 | */ | ||
40 | struct 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 | */ | ||
62 | static int ret; | ||
63 | |||
64 | /** | ||
65 | * Handle to the microphone. | ||
66 | */ | ||
67 | static struct GNUNET_MICROPHONE_Handle *microphone; | ||
68 | |||
69 | /** | ||
70 | * Handle to the speaker. | ||
71 | */ | ||
72 | static struct GNUNET_SPEAKER_Handle *speaker; | ||
73 | |||
74 | /** | ||
75 | * Task scheduled to switch from recording to playback. | ||
76 | */ | ||
77 | static struct GNUNET_SCHEDULER_Task *switch_task; | ||
78 | |||
79 | /** | ||
80 | * The shutdown task. | ||
81 | */ | ||
82 | static struct GNUNET_SCHEDULER_Task *st; | ||
83 | |||
84 | /** | ||
85 | * Head of DLL with recorded frames. | ||
86 | */ | ||
87 | static struct Recording *rec_head; | ||
88 | |||
89 | /** | ||
90 | * Tail of DLL with recorded frames. | ||
91 | */ | ||
92 | static struct Recording *rec_tail; | ||
93 | |||
94 | |||
95 | /** | ||
96 | * Terminate test. | ||
97 | * | ||
98 | * @param cls NULL | ||
99 | */ | ||
100 | static void | ||
101 | do_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 | */ | ||
129 | static void | ||
130 | switch_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 | */ | ||
170 | static void | ||
171 | record (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 | */ | ||
198 | static void | ||
199 | run (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 | */ | ||
240 | int | ||
241 | main (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 | */ | ||
45 | enum 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 | */ | ||
72 | enum 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 | */ | ||
99 | struct 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 | */ | ||
131 | static struct GNUNET_CONVERSATION_Phone *phone; | ||
132 | |||
133 | /** | ||
134 | * Call handle (for active outgoing call). | ||
135 | */ | ||
136 | static 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 | */ | ||
142 | static struct CallList *cl_active; | ||
143 | |||
144 | /** | ||
145 | * Head of calls waiting to be accepted. | ||
146 | */ | ||
147 | static struct CallList *cl_head; | ||
148 | |||
149 | /** | ||
150 | * Tail of calls waiting to be accepted. | ||
151 | */ | ||
152 | static struct CallList *cl_tail; | ||
153 | |||
154 | /** | ||
155 | * Desired phone line (string to be converted to a hash). | ||
156 | */ | ||
157 | static char *line; | ||
158 | |||
159 | /** | ||
160 | * Task which handles the commands | ||
161 | */ | ||
162 | static struct GNUNET_SCHEDULER_Task *handle_cmd_task; | ||
163 | |||
164 | /** | ||
165 | * Our speaker. | ||
166 | */ | ||
167 | static struct GNUNET_SPEAKER_Handle *speaker; | ||
168 | |||
169 | /** | ||
170 | * Our microphone. | ||
171 | */ | ||
172 | static struct GNUNET_MICROPHONE_Handle *mic; | ||
173 | |||
174 | /** | ||
175 | * Our configuration. | ||
176 | */ | ||
177 | static struct GNUNET_CONFIGURATION_Handle *cfg; | ||
178 | |||
179 | /** | ||
180 | * Our ego. | ||
181 | */ | ||
182 | static struct GNUNET_IDENTITY_Ego *my_caller_id; | ||
183 | |||
184 | /** | ||
185 | * Handle to identity service. | ||
186 | */ | ||
187 | static struct GNUNET_IDENTITY_Handle *id; | ||
188 | |||
189 | /** | ||
190 | * Name of our ego. | ||
191 | */ | ||
192 | static char *ego_name; | ||
193 | |||
194 | /** | ||
195 | * Public key of active conversation partner (if any). | ||
196 | */ | ||
197 | static struct GNUNET_CRYPTO_PublicKey peer_key; | ||
198 | |||
199 | /** | ||
200 | * Name of active conversation partner (if any). | ||
201 | */ | ||
202 | static char *peer_name; | ||
203 | |||
204 | /** | ||
205 | * File handle for stdin. | ||
206 | */ | ||
207 | static struct GNUNET_DISK_FileHandle *stdin_fh; | ||
208 | |||
209 | /** | ||
210 | * Our phone's current state. | ||
211 | */ | ||
212 | static enum PhoneState phone_state; | ||
213 | |||
214 | /** | ||
215 | * Our call's current state. | ||
216 | */ | ||
217 | static enum CallState call_state; | ||
218 | |||
219 | /** | ||
220 | * Counts the number of incoming calls we have had so far. | ||
221 | */ | ||
222 | static unsigned int caller_num_gen; | ||
223 | |||
224 | /** | ||
225 | * GNS address for this phone. | ||
226 | */ | ||
227 | static char *address; | ||
228 | |||
229 | /** | ||
230 | * Be verbose. | ||
231 | */ | ||
232 | static 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 | */ | ||
243 | static void | ||
244 | phone_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 | */ | ||
310 | static void | ||
311 | caller_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 | */ | ||
335 | static void | ||
336 | start_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 | */ | ||
381 | static void | ||
382 | call_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 | */ | ||
446 | typedef void (*ActionFunction) (const char *arguments); | ||
447 | |||
448 | |||
449 | /** | ||
450 | * Structure which defines a command | ||
451 | */ | ||
452 | struct 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 | */ | ||
476 | static void | ||
477 | do_help (const char *args); | ||
478 | |||
479 | |||
480 | /** | ||
481 | * Terminate the client | ||
482 | * | ||
483 | * @param args arguments given to the command | ||
484 | */ | ||
485 | static void | ||
486 | do_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 | */ | ||
498 | static void | ||
499 | do_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 | */ | ||
510 | static void | ||
511 | do_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 | */ | ||
570 | static void | ||
571 | do_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 | */ | ||
643 | static void | ||
644 | do_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 | */ | ||
661 | static void | ||
662 | do_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 | */ | ||
748 | static void | ||
749 | do_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 | */ | ||
796 | static void | ||
797 | do_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 | */ | ||
873 | static void | ||
874 | do_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 | */ | ||
937 | static 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 | */ | ||
975 | static void | ||
976 | do_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 | */ | ||
1008 | static void | ||
1009 | do_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 | */ | ||
1050 | static void | ||
1051 | handle_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 | */ | ||
1084 | static void | ||
1085 | handle_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 | */ | ||
1111 | static void | ||
1112 | identity_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 | */ | ||
1152 | static void | ||
1153 | run (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 | */ | ||
1185 | int | ||
1186 | main (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 | */ | ||
59 | struct GNUNET_MessageStreamTokenizer *stdin_mst; | ||
60 | |||
61 | /** | ||
62 | * Main pipeline. | ||
63 | */ | ||
64 | static GstElement *pipeline; | ||
65 | |||
66 | /** | ||
67 | * Appsrc instance into which we write data for the pipeline. | ||
68 | */ | ||
69 | static GstElement *source; | ||
70 | |||
71 | static GstElement *demuxer; | ||
72 | static GstElement *decoder; | ||
73 | static GstElement *conv; | ||
74 | static GstElement *resampler; | ||
75 | static GstElement *sink; | ||
76 | |||
77 | /** | ||
78 | * Set to 1 to break the reading loop | ||
79 | */ | ||
80 | static int abort_read; | ||
81 | |||
82 | |||
83 | static void | ||
84 | sink_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 | |||
97 | static void | ||
98 | ogg_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 | |||
116 | static void | ||
117 | quit () | ||
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 | |||
127 | static gboolean | ||
128 | bus_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 | |||
165 | static void | ||
166 | signalhandler (int s) | ||
167 | { | ||
168 | quit (); | ||
169 | } | ||
170 | |||
171 | |||
172 | static int | ||
173 | feed_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 | */ | ||
234 | static int | ||
235 | stdin_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 | |||
257 | int | ||
258 | main (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 | */ | ||
58 | static 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 | ||
65 | static int dump_to_stdout; | ||
66 | #endif | ||
67 | |||
68 | /** | ||
69 | * Pulseaudio mainloop api | ||
70 | */ | ||
71 | static pa_mainloop_api *mainloop_api; | ||
72 | |||
73 | /** | ||
74 | * Pulseaudio threaded mainloop | ||
75 | */ | ||
76 | static pa_threaded_mainloop *m; | ||
77 | |||
78 | /** | ||
79 | * Pulseaudio context | ||
80 | */ | ||
81 | static pa_context *context; | ||
82 | |||
83 | /** | ||
84 | * Pulseaudio output stream | ||
85 | */ | ||
86 | static pa_stream *stream_out; | ||
87 | |||
88 | /** | ||
89 | * OPUS decoder | ||
90 | */ | ||
91 | static OpusDecoder *dec; | ||
92 | |||
93 | /** | ||
94 | * PCM data buffer | ||
95 | */ | ||
96 | static float *pcm_buffer; | ||
97 | |||
98 | /** | ||
99 | * Number of samples for one frame | ||
100 | */ | ||
101 | static int frame_size; | ||
102 | |||
103 | /** | ||
104 | * Pipe we use to signal the main loop that we are ready to receive. | ||
105 | */ | ||
106 | static int ready_pipe[2]; | ||
107 | |||
108 | /** | ||
109 | * Ogg I/O state. | ||
110 | */ | ||
111 | static ogg_sync_state oy; | ||
112 | |||
113 | /** | ||
114 | * Ogg stream state. | ||
115 | */ | ||
116 | static ogg_stream_state os; | ||
117 | |||
118 | static int channels; | ||
119 | |||
120 | static int preskip; | ||
121 | |||
122 | static float gain; | ||
123 | |||
124 | GNUNET_NETWORK_STRUCT_BEGIN | ||
125 | |||
126 | /* OggOpus spec says the numbers must be in little-endian order */ | ||
127 | struct 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 | |||
138 | GNUNET_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 | */ | ||
145 | static OpusDecoder * | ||
146 | process_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 | ||
217 | static size_t | ||
218 | fwrite_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 | |||
230 | static size_t | ||
231 | fwrite_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 | |||
241 | static int | ||
242 | write_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 | |||
269 | static int64_t | ||
270 | audio_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 | */ | ||
349 | static void | ||
350 | quit (int ret) | ||
351 | { | ||
352 | mainloop_api->quit (mainloop_api, | ||
353 | ret); | ||
354 | exit (ret); | ||
355 | } | ||
356 | |||
357 | |||
358 | static void | ||
359 | ogg_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 | */ | ||
583 | static int | ||
584 | stdin_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 | */ | ||
617 | static void | ||
618 | stream_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 | */ | ||
638 | static void | ||
639 | exit_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 | */ | ||
657 | static void | ||
658 | context_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; | ||
716 | fail: | ||
717 | quit (1); | ||
718 | } | ||
719 | |||
720 | |||
721 | /** | ||
722 | * Pulseaudio initialization | ||
723 | */ | ||
724 | static void | ||
725 | pa_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 | |||
770 | static void | ||
771 | ogg_init () | ||
772 | { | ||
773 | ogg_sync_init (&oy); | ||
774 | } | ||
775 | |||
776 | |||
777 | static void | ||
778 | drain_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 | */ | ||
795 | int | ||
796 | main (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 | */ | ||
98 | static GstElement *pipeline; | ||
99 | |||
100 | #ifdef DEBUG_RECORD_PURE_OGG | ||
101 | static int dump_pure_ogg; | ||
102 | #endif | ||
103 | |||
104 | static void | ||
105 | quit () | ||
106 | { | ||
107 | if (NULL != pipeline) | ||
108 | gst_element_set_state (pipeline, GST_STATE_NULL); | ||
109 | } | ||
110 | |||
111 | |||
112 | static gboolean | ||
113 | bus_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 | |||
146 | void | ||
147 | source_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 | |||
156 | static void | ||
157 | signalhandler (int s) | ||
158 | { | ||
159 | quit (); | ||
160 | } | ||
161 | |||
162 | |||
163 | int | ||
164 | main (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 | */ | ||
143 | static pa_sample_spec sample_spec = { | ||
144 | .format = PA_SAMPLE_FLOAT32LE, | ||
145 | .rate = SAMPLING_RATE, | ||
146 | .channels = CHANNELS | ||
147 | }; | ||
148 | |||
149 | GNUNET_NETWORK_STRUCT_BEGIN | ||
150 | |||
151 | /* OggOpus spec says the numbers must be in little-endian order */ | ||
152 | struct 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 | |||
163 | struct 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 | |||
176 | GNUNET_NETWORK_STRUCT_END | ||
177 | |||
178 | /** | ||
179 | * Pulseaudio mainloop api | ||
180 | */ | ||
181 | static pa_mainloop_api *mainloop_api; | ||
182 | |||
183 | /** | ||
184 | * Pulseaudio mainloop | ||
185 | */ | ||
186 | static pa_mainloop *m; | ||
187 | |||
188 | /** | ||
189 | * Pulseaudio context | ||
190 | */ | ||
191 | static pa_context *context; | ||
192 | |||
193 | /** | ||
194 | * Pulseaudio recording stream | ||
195 | */ | ||
196 | static pa_stream *stream_in; | ||
197 | |||
198 | /** | ||
199 | * Pulseaudio io events | ||
200 | */ | ||
201 | static pa_io_event *stdio_event; | ||
202 | |||
203 | /** | ||
204 | * OPUS encoder | ||
205 | */ | ||
206 | static OpusEncoder *enc; | ||
207 | |||
208 | /** | ||
209 | * Buffer for encoded data | ||
210 | */ | ||
211 | static unsigned char *opus_data; | ||
212 | |||
213 | /** | ||
214 | * PCM data buffer for one OPUS frame | ||
215 | */ | ||
216 | static float *pcm_buffer; | ||
217 | |||
218 | /** | ||
219 | * Length of the pcm data needed for one OPUS frame | ||
220 | */ | ||
221 | static int pcm_length; | ||
222 | |||
223 | /** | ||
224 | * Audio buffer | ||
225 | */ | ||
226 | static char *transmit_buffer; | ||
227 | |||
228 | /** | ||
229 | * Length of audio buffer | ||
230 | */ | ||
231 | static size_t transmit_buffer_length; | ||
232 | |||
233 | /** | ||
234 | * Read index for transmit buffer | ||
235 | */ | ||
236 | static size_t transmit_buffer_index; | ||
237 | |||
238 | /** | ||
239 | * Audio message skeleton | ||
240 | */ | ||
241 | static struct AudioMessage *audio_message; | ||
242 | |||
243 | /** | ||
244 | * Ogg muxer state | ||
245 | */ | ||
246 | static ogg_stream_state os; | ||
247 | |||
248 | /** | ||
249 | * Ogg packet id | ||
250 | */ | ||
251 | static int32_t packet_id; | ||
252 | |||
253 | /** | ||
254 | * Ogg granule for current packet | ||
255 | */ | ||
256 | static 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 | */ | ||
263 | static int dump_pure_ogg; | ||
264 | #endif | ||
265 | |||
266 | /** | ||
267 | * Pulseaudio shutdown task | ||
268 | */ | ||
269 | static void | ||
270 | quit (int ret) | ||
271 | { | ||
272 | mainloop_api->quit (mainloop_api, | ||
273 | ret); | ||
274 | exit (ret); | ||
275 | } | ||
276 | |||
277 | |||
278 | static void | ||
279 | write_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 | |||
303 | static void | ||
304 | write_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 | */ | ||
334 | static void | ||
335 | packetizer () | ||
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 | */ | ||
414 | static void | ||
415 | stream_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 | */ | ||
466 | static void | ||
467 | exit_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 | */ | ||
485 | static void | ||
486 | stream_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 | */ | ||
547 | static void | ||
548 | context_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 | |||
607 | fail: | ||
608 | quit (1); | ||
609 | } | ||
610 | |||
611 | |||
612 | /** | ||
613 | * Pulsaudio init | ||
614 | */ | ||
615 | static void | ||
616 | pa_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 | */ | ||
665 | static void | ||
666 | opus_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 | |||
689 | static void | ||
690 | ogg_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 | */ | ||
785 | int | ||
786 | main (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 | */ | ||
50 | struct Line; | ||
51 | |||
52 | /** | ||
53 | * The possible connection status | ||
54 | */ | ||
55 | enum 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 | */ | ||
100 | struct 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 | */ | ||
157 | struct 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 | */ | ||
200 | static const struct GNUNET_CONFIGURATION_Handle *cfg; | ||
201 | |||
202 | /** | ||
203 | * Handle for cadet | ||
204 | */ | ||
205 | static struct GNUNET_CADET_Handle *cadet; | ||
206 | |||
207 | /** | ||
208 | * Identity of this peer. | ||
209 | */ | ||
210 | static 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 | */ | ||
221 | static struct Channel * | ||
222 | find_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 | */ | ||
237 | static void | ||
238 | handle_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 | */ | ||
306 | static void | ||
307 | clean_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 | */ | ||
344 | static void | ||
345 | destroy_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 | */ | ||
363 | static void | ||
364 | mq_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 | */ | ||
407 | static void | ||
408 | handle_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 | */ | ||
478 | static void | ||
479 | handle_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 | */ | ||
556 | static void | ||
557 | handle_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 | */ | ||
632 | static void | ||
633 | channel_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 | */ | ||
648 | static int | ||
649 | check_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 | */ | ||
663 | static void | ||
664 | handle_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 | */ | ||
738 | static enum GNUNET_GenericReturnValue | ||
739 | check_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 | */ | ||
751 | static void | ||
752 | handle_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 | */ | ||
837 | static void | ||
838 | handle_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 | */ | ||
886 | static void | ||
887 | handle_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 | */ | ||
938 | static void | ||
939 | handle_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 | */ | ||
991 | static void | ||
992 | handle_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 | */ | ||
1053 | static int | ||
1054 | check_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 | */ | ||
1068 | static void | ||
1069 | handle_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 | */ | ||
1106 | static void | ||
1107 | inbound_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 | */ | ||
1125 | static enum GNUNET_GenericReturnValue | ||
1126 | check_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 | */ | ||
1138 | static void | ||
1139 | handle_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 | */ | ||
1238 | static void * | ||
1239 | inbound_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 | */ | ||
1269 | static void * | ||
1270 | client_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 | */ | ||
1291 | static void | ||
1292 | client_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 | */ | ||
1323 | static void | ||
1324 | handle_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 | */ | ||
1380 | static void | ||
1381 | do_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 | */ | ||
1399 | static void | ||
1400 | run (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 | */ | ||
1423 | GNUNET_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 | */ | ||
31 | static struct GNUNET_CONFIGURATION_Handle *cfg; | ||
32 | |||
33 | |||
34 | void | ||
35 | dump_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 | } | ||
62 | BREAKOUT: | ||
63 | return; | ||
64 | } | ||
65 | |||
66 | |||
67 | /*** | ||
68 | * load gnunet configuration | ||
69 | */ | ||
70 | void | ||
71 | gg_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 | |||
148 | static void | ||
149 | write_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 | |||
169 | extern GstFlowReturn | ||
170 | on_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 | */ | ||
232 | extern void | ||
233 | pl_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 | |||
251 | extern gboolean | ||
252 | gnunet_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 */ | ||
290 | extern void | ||
291 | state_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 | |||
330 | static void | ||
331 | application_cb (GstBus *bus, GstMessage *msg, GNUNET_gstData *data) | ||
332 | { | ||
333 | // printf("application cb"); | ||
334 | return; | ||
335 | } | ||
336 | |||
337 | |||
338 | static void | ||
339 | error_cb (GstBus *bus, GstMessage *msg, GNUNET_gstData *data) | ||
340 | { | ||
341 | // printf("error cb"); | ||
342 | return; | ||
343 | } | ||
344 | |||
345 | |||
346 | static void | ||
347 | eos_cb (GstBus *bus, GstMessage *msg, GNUNET_gstData *data) | ||
348 | { | ||
349 | // printf("eos cb"); | ||
350 | return; | ||
351 | } | ||
352 | |||
353 | |||
354 | extern void | ||
355 | gg_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 | |||
495 | extern int | ||
496 | feed_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 | */ | ||
554 | extern GstElement * | ||
555 | gst_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 | */ | ||
586 | extern void | ||
587 | lf (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 | */ | ||
597 | static void | ||
598 | autoaudiosink_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 | */ | ||
614 | static void | ||
615 | autoaudiosource_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 | |||
624 | GstElement * | ||
625 | get_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 | |||
635 | static void | ||
636 | decoder_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 | |||
654 | int | ||
655 | gnunet_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 | */ | ||
702 | static int | ||
703 | stdin_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 | |||
731 | GstBin * | ||
732 | get_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 | |||
815 | extern GstBin * | ||
816 | get_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 | |||
949 | extern GstBin * | ||
950 | get_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 | |||
39 | extern void pl_graph (); | ||
40 | |||
41 | |||
42 | extern GstElement * | ||
43 | gst_element_factory_make_debug (gchar *, gchar *); | ||
44 | |||
45 | extern GstBin * | ||
46 | get_audiobin (GNUNET_gstData *, int); | ||
47 | |||
48 | extern GstBin * | ||
49 | get_coder (GNUNET_gstData *, int); | ||
50 | |||
51 | |||
52 | extern gboolean | ||
53 | gnunet_gst_bus_call (GstBus *bus, GstMessage *msg, gpointer data); | ||
54 | |||
55 | extern void | ||
56 | gg_setup_gst_bus (GNUNET_gstData *d); | ||
57 | |||
58 | extern void | ||
59 | gg_load_configuration (GNUNET_gstData *d); | ||
60 | |||
61 | extern GstFlowReturn | ||
62 | on_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 | |||
87 | typedef struct GNUNET_gstData GNUNET_gstData; | ||
88 | struct 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 | |||
188 | enum | ||
189 | { | ||
190 | AUTO, | ||
191 | JACK, | ||
192 | ALSA, | ||
193 | FAKE, | ||
194 | TEST | ||
195 | }; | ||
196 | |||
197 | enum | ||
198 | { | ||
199 | SOURCE, | ||
200 | SINK | ||
201 | }; | ||
202 | |||
203 | enum | ||
204 | { | ||
205 | ENCODER, | ||
206 | DECODER | ||
207 | }; | ||
208 | |||
209 | enum | ||
210 | { | ||
211 | FAIL, | ||
212 | OK | ||
213 | }; | ||
214 | |||
215 | enum | ||
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 | |||
30 | int | ||
31 | main (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] | ||
2 | AUDIOBACKEND = JACK | ||
3 | REMOVESILENCE = NO | ||
4 | USERTP = NO | ||
5 | NO_GN_HEADERS = NO | ||
6 | JACK_PP_IN = mocp | ||
7 | JACK_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 @@ | |||
1 | libgnunetconversation_src = ['conversation_api.c', 'conversation_api_call.c'] | ||
2 | |||
3 | gnunetserviceconversation_src = ['gnunet-service-conversation.c'] | ||
4 | |||
5 | gnunetconversationproxy_src = ['gnunet-conversation-proxy.c'] | ||
6 | |||
7 | configure_file(input : 'conversation.conf.in', | ||
8 | output : 'conversation.conf', | ||
9 | configuration : cdata, | ||
10 | install: true, | ||
11 | install_dir: pkgcfgdir) | ||
12 | |||
13 | |||
14 | if get_option('monolith') | ||
15 | foreach p : libgnunetconversation_src + gnunetserviceconversation_src | ||
16 | gnunet_src += 'conversation/' + p | ||
17 | endforeach | ||
18 | endif | ||
19 | |||
20 | libgnunetmicrophone = 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')) | ||
28 | libgnunetmicrophone_dep = declare_dependency(link_with : libgnunetmicrophone) | ||
29 | pkg.generate(libgnunetmicrophone, url: 'https://www.gnunet.org', | ||
30 | description : 'Provides API to access to microphone') | ||
31 | |||
32 | libgnunetspeaker = 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')) | ||
40 | libgnunetspeaker_dep = declare_dependency(link_with : libgnunetspeaker) | ||
41 | pkg.generate(libgnunetspeaker, url: 'https://www.gnunet.org', | ||
42 | description : 'Provides API to access to speaker') | ||
43 | |||
44 | libgnunetconversation = 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')) | ||
56 | libgnunetconversation_dep = declare_dependency(link_with : libgnunetconversation) | ||
57 | pkg.generate(libgnunetconversation, url: 'https://www.gnunet.org', | ||
58 | description : 'Provides API to access to conversation') | ||
59 | |||
60 | shared_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 | |||
71 | executable ('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 | |||
85 | executable ('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 | |||
95 | executable ('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 | |||
107 | helperrecord_src = ['gnunet-helper-audio-record.c'] | ||
108 | helperplayback_src = ['gnunet-helper-audio-record.c'] | ||
109 | if conversation_backend == 'gst' | ||
110 | helperrecord_src = ['gnunet-helper-audio-record-gst.c'] | ||
111 | helperplayback_src = ['gnunet-helper-audio-playback-gst.c'] | ||
112 | endif | ||
113 | |||
114 | executable ('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 | |||
131 | executable ('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 | |||
148 | testconvapi = 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 | |||
162 | testconvapitwo = 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 | |||
176 | testconvapireject = 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 | |||
190 | configure_file(input : 'test_conversation.conf', | ||
191 | output : 'test_conversation.conf', | ||
192 | copy: true, | ||
193 | install: false) | ||
194 | |||
195 | test('test_conversation_api', testconvapi, workdir: meson.current_build_dir(), | ||
196 | suite: ['conversation', 'contrib']) | ||
197 | |||
198 | test('test_conversation_api_twocalls', testconvapitwo, workdir: meson.current_build_dir(), | ||
199 | suite: ['conversation', 'contrib']) | ||
200 | |||
201 | test('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 | */ | ||
37 | struct 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 | */ | ||
70 | static int | ||
71 | process_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 | */ | ||
97 | static int | ||
98 | enable (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 | */ | ||
130 | static void | ||
131 | disable (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 | */ | ||
152 | static void | ||
153 | destroy (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 | */ | ||
169 | struct GNUNET_MICROPHONE_Handle * | ||
170 | GNUNET_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 | */ | ||
192 | void | ||
193 | GNUNET_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 | */ | ||
46 | static char * | ||
47 | conversation_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 | */ | ||
106 | static int | ||
107 | conversation_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 | */ | ||
171 | static 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 | */ | ||
188 | static uint32_t | ||
189 | conversation_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 | */ | ||
210 | static const char * | ||
211 | conversation_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 | */ | ||
231 | void * | ||
232 | libgnunet_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 | */ | ||
252 | void * | ||
253 | libgnunet_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 | */ | ||
36 | struct 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 | */ | ||
56 | static int | ||
57 | enable (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 | */ | ||
85 | static void | ||
86 | disable (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 | */ | ||
107 | static void | ||
108 | destroy (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 | */ | ||
125 | static void | ||
126 | play (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 | */ | ||
157 | struct GNUNET_SPEAKER_Handle * | ||
158 | GNUNET_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 | */ | ||
181 | void | ||
182 | GNUNET_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 | |||
3 | export GST_DEBUG_DUMP_DOT_DIR=/tmp/ | ||
4 | GST_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] | ||
4 | LINE=1 | ||
5 | #PREFIX = valgrind | ||
6 | |||
7 | [zonemaster] | ||
8 | IMMEDIATE_START = YES | ||
9 | START_ON_DEMAND = YES | ||
10 | |||
11 | [nse] | ||
12 | WORKBITS = 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 | |||
40 | static int ok = 1; | ||
41 | |||
42 | static const struct GNUNET_CONFIGURATION_Handle *cfg; | ||
43 | |||
44 | static struct GNUNET_IDENTITY_Handle *id; | ||
45 | |||
46 | static struct GNUNET_IDENTITY_Operation *op; | ||
47 | |||
48 | static struct GNUNET_CONVERSATION_Phone *phone; | ||
49 | |||
50 | static struct GNUNET_NAMESTORE_Handle *ns; | ||
51 | |||
52 | static struct GNUNET_CONVERSATION_Call *call; | ||
53 | |||
54 | static struct GNUNET_NAMESTORE_QueueEntry *qe; | ||
55 | |||
56 | static struct GNUNET_CONVERSATION_Caller *active_caller; | ||
57 | |||
58 | static char *gns_name; | ||
59 | |||
60 | static char *gns_caller_id; | ||
61 | |||
62 | static GNUNET_MICROPHONE_RecordedDataCallback phone_rdc; | ||
63 | |||
64 | static void *phone_rdc_cls; | ||
65 | |||
66 | static GNUNET_MICROPHONE_RecordedDataCallback call_rdc; | ||
67 | |||
68 | static void *call_rdc_cls; | ||
69 | |||
70 | static struct GNUNET_SCHEDULER_Task *phone_task; | ||
71 | |||
72 | static struct GNUNET_SCHEDULER_Task *call_task; | ||
73 | |||
74 | |||
75 | static void | ||
76 | phone_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 | |||
89 | static void | ||
90 | call_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 | |||
103 | static int | ||
104 | enable_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 | |||
113 | static void | ||
114 | disable_speaker (void *cls) | ||
115 | { | ||
116 | const char *origin = cls; | ||
117 | |||
118 | fprintf (stderr, "Speaker %s disabled\n", origin); | ||
119 | } | ||
120 | |||
121 | |||
122 | static void | ||
123 | play (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 | |||
155 | static void | ||
156 | destroy_speaker (void *cls) | ||
157 | { | ||
158 | const char *origin = cls; | ||
159 | |||
160 | fprintf (stderr, "Speaker %s destroyed\n", origin); | ||
161 | } | ||
162 | |||
163 | |||
164 | static struct GNUNET_SPEAKER_Handle call_speaker = { &enable_speaker, | ||
165 | &play, | ||
166 | &disable_speaker, | ||
167 | &destroy_speaker, | ||
168 | "caller" }; | ||
169 | |||
170 | |||
171 | static struct GNUNET_SPEAKER_Handle phone_speaker = { &enable_speaker, | ||
172 | &play, | ||
173 | &disable_speaker, | ||
174 | &destroy_speaker, | ||
175 | "phone" }; | ||
176 | |||
177 | |||
178 | static int | ||
179 | enable_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 | |||
202 | static void | ||
203 | disable_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 | |||
225 | static void | ||
226 | destroy_mic (void *cls) | ||
227 | { | ||
228 | const char *origin = cls; | ||
229 | |||
230 | fprintf (stderr, "Mic %s destroyed\n", origin); | ||
231 | } | ||
232 | |||
233 | |||
234 | static struct GNUNET_MICROPHONE_Handle call_mic = { &enable_mic, | ||
235 | &disable_mic, | ||
236 | &destroy_mic, | ||
237 | "caller" }; | ||
238 | |||
239 | |||
240 | static 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 | */ | ||
251 | static void | ||
252 | end_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 | |||
290 | static void | ||
291 | caller_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 | |||
304 | static void | ||
305 | phone_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 | |||
343 | static void | ||
344 | call_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 | |||
386 | static void | ||
387 | caller_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 | |||
397 | static void | ||
398 | namestore_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 | |||
411 | static void | ||
412 | identity_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 | |||
467 | static void | ||
468 | phone_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 | |||
478 | static void | ||
479 | run (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 | |||
496 | int | ||
497 | main (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 | |||
38 | static int ok = 1; | ||
39 | |||
40 | static const struct GNUNET_CONFIGURATION_Handle *cfg; | ||
41 | |||
42 | static struct GNUNET_IDENTITY_Handle *id; | ||
43 | |||
44 | static struct GNUNET_IDENTITY_Operation *op; | ||
45 | |||
46 | static struct GNUNET_CONVERSATION_Phone *phone; | ||
47 | |||
48 | static struct GNUNET_NAMESTORE_Handle *ns; | ||
49 | |||
50 | static struct GNUNET_CONVERSATION_Call *call; | ||
51 | |||
52 | static struct GNUNET_NAMESTORE_QueueEntry *qe; | ||
53 | |||
54 | static char *gns_name; | ||
55 | |||
56 | static char *gns_caller_id; | ||
57 | |||
58 | |||
59 | static int | ||
60 | enable_speaker (void *cls) | ||
61 | { | ||
62 | (void) cls; | ||
63 | GNUNET_break (0); | ||
64 | return GNUNET_SYSERR; | ||
65 | } | ||
66 | |||
67 | |||
68 | static void | ||
69 | disable_speaker (void *cls) | ||
70 | { | ||
71 | (void) cls; | ||
72 | GNUNET_break (0); | ||
73 | } | ||
74 | |||
75 | |||
76 | static void | ||
77 | play (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 | |||
86 | static void | ||
87 | destroy_speaker (void *cls) | ||
88 | { | ||
89 | (void) cls; | ||
90 | } | ||
91 | |||
92 | |||
93 | static struct GNUNET_SPEAKER_Handle call_speaker = { &enable_speaker, | ||
94 | &play, | ||
95 | &disable_speaker, | ||
96 | &destroy_speaker, | ||
97 | "caller" }; | ||
98 | |||
99 | |||
100 | static int | ||
101 | enable_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 | |||
113 | static void | ||
114 | disable_mic (void *cls) | ||
115 | { | ||
116 | (void) cls; | ||
117 | GNUNET_break (0); | ||
118 | } | ||
119 | |||
120 | |||
121 | static void | ||
122 | destroy_mic (void *cls) | ||
123 | { | ||
124 | (void) cls; | ||
125 | } | ||
126 | |||
127 | |||
128 | static 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 | */ | ||
139 | static void | ||
140 | end_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 | |||
178 | static void | ||
179 | phone_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 | |||
203 | static void | ||
204 | call_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 | |||
239 | static void | ||
240 | caller_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 | |||
250 | static void | ||
251 | namestore_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 | |||
264 | static void | ||
265 | identity_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 | |||
320 | static void | ||
321 | phone_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 | |||
331 | static void | ||
332 | run (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 | |||
349 | int | ||
350 | main (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 | |||
46 | static const struct GNUNET_CONFIGURATION_Handle *cfg; | ||
47 | |||
48 | static struct GNUNET_IDENTITY_Handle *id; | ||
49 | |||
50 | static struct GNUNET_IDENTITY_Operation *op; | ||
51 | |||
52 | static struct GNUNET_CONVERSATION_Phone *phone; | ||
53 | |||
54 | static struct GNUNET_NAMESTORE_Handle *ns; | ||
55 | |||
56 | static struct GNUNET_CONVERSATION_Call *call1; | ||
57 | |||
58 | static struct GNUNET_CONVERSATION_Call *call2; | ||
59 | |||
60 | static struct GNUNET_NAMESTORE_QueueEntry *qe; | ||
61 | |||
62 | static struct GNUNET_CONVERSATION_Caller *active_caller1; | ||
63 | |||
64 | static struct GNUNET_CONVERSATION_Caller *active_caller2; | ||
65 | |||
66 | static char *gns_name; | ||
67 | |||
68 | static char *gns_caller_id; | ||
69 | |||
70 | static GNUNET_MICROPHONE_RecordedDataCallback phone_rdc; | ||
71 | |||
72 | static void *phone_rdc_cls; | ||
73 | |||
74 | static struct GNUNET_SCHEDULER_Task *phone_task; | ||
75 | |||
76 | static struct GNUNET_SCHEDULER_Task *timeout_task; | ||
77 | |||
78 | /** | ||
79 | * Variable for recognizing caller1 | ||
80 | */ | ||
81 | static const char *caller1 = "caller1"; | ||
82 | |||
83 | /** | ||
84 | * Variable for recognizing caller2 | ||
85 | */ | ||
86 | static const char *caller2 = "caller2"; | ||
87 | |||
88 | /** | ||
89 | * Variable for recognizing callee | ||
90 | */ | ||
91 | static 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 | */ | ||
104 | static int call1_finished; | ||
105 | |||
106 | /** | ||
107 | * Did caller2 call finish successfully | ||
108 | */ | ||
109 | static int call2_finished; | ||
110 | |||
111 | struct MicContext | ||
112 | { | ||
113 | GNUNET_MICROPHONE_RecordedDataCallback rdc; | ||
114 | |||
115 | void *rdc_cls; | ||
116 | |||
117 | struct GNUNET_SCHEDULER_Task *call_task; | ||
118 | }; | ||
119 | |||
120 | static struct MicContext call1_mic_ctx; | ||
121 | static struct MicContext call2_mic_ctx; | ||
122 | // static struct MicContext phone_mic_ctx; | ||
123 | |||
124 | |||
125 | static void | ||
126 | phone_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 | |||
138 | static void | ||
139 | call_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 | |||
152 | static int | ||
153 | enable_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 | |||
163 | static void | ||
164 | disable_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 | |||
173 | static void | ||
174 | play (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 | |||
214 | static void | ||
215 | destroy_speaker (void *cls) | ||
216 | { | ||
217 | const char *origin = CLS_STR (cls); | ||
218 | |||
219 | LOG_DEBUG ("Speaker %s destroyed\n", origin); | ||
220 | } | ||
221 | |||
222 | |||
223 | static struct GNUNET_SPEAKER_Handle call1_speaker = { &enable_speaker, | ||
224 | &play, | ||
225 | &disable_speaker, | ||
226 | &destroy_speaker, | ||
227 | CALLER1 }; | ||
228 | |||
229 | |||
230 | static struct GNUNET_SPEAKER_Handle call2_speaker = { &enable_speaker, | ||
231 | &play, | ||
232 | &disable_speaker, | ||
233 | &destroy_speaker, | ||
234 | CALLER2 }; | ||
235 | |||
236 | |||
237 | static struct GNUNET_SPEAKER_Handle phone_speaker = { &enable_speaker, | ||
238 | &play, | ||
239 | &disable_speaker, | ||
240 | &destroy_speaker, | ||
241 | PHONE0 }; | ||
242 | |||
243 | |||
244 | static int | ||
245 | enable_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 | |||
270 | static void | ||
271 | disable_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 | |||
293 | static void | ||
294 | destroy_mic (void *cls) | ||
295 | { | ||
296 | const char *origin = CLS_STR (cls); | ||
297 | |||
298 | LOG_DEBUG ("Mic %s destroyed\n", origin); | ||
299 | } | ||
300 | |||
301 | |||
302 | static struct GNUNET_MICROPHONE_Handle call1_mic = { &enable_mic, | ||
303 | &disable_mic, | ||
304 | &destroy_mic, | ||
305 | CALLER1 }; | ||
306 | |||
307 | |||
308 | static struct GNUNET_MICROPHONE_Handle call2_mic = { &enable_mic, | ||
309 | &disable_mic, | ||
310 | &destroy_mic, | ||
311 | CALLER2 }; | ||
312 | |||
313 | |||
314 | static 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 | */ | ||
325 | static void | ||
326 | end_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 | */ | ||
340 | static void | ||
341 | do_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 | |||
388 | static void | ||
389 | caller_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 | |||
402 | static void | ||
403 | phone_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 | |||
462 | static void | ||
463 | call_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 | |||
508 | static void | ||
509 | caller_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 | |||
519 | static void | ||
520 | namestore_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 | |||
533 | static void | ||
534 | identity_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 | |||
596 | static void | ||
597 | phone_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 | |||
607 | static void | ||
608 | run (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 | |||
626 | int | ||
627 | main (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 */ | ||